Compare commits

...
Author SHA1 Message Date
Cedric VerstraetenandClaude Opus 5.5 9695209b0e feat(hub): configurable hub-api session lifetime, hub-api v1.11.42 (0.186.0)
Add kerberoshub.api.session.{accessTokenTimeout,idleTimeout,maxLifetime}
(defaults 15m / 12h / 24h), passed to hub-api as ACCESS_TOKEN_TIMEOUT,
SESSION_IDLE_TIMEOUT and SESSION_MAX_LIFETIME, and default hub-api to
v1.11.42, which enforces them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 09:07:56 +00:00
Cédric Verstraeten 3dec5944f2 Merge pull request #242 from kerberos-io/feat/hub-trusted-registry-own-images
feat(hub): trusted-registry reports only UUG.AI images (0.185.1)
2026-11-12 10:06:24 +01:00
Cédric VerstraetenandClaude Opus 5.5 02ab752118 feat(hub): trusted-registry reports only UUG.AI images (0.185.1)
trusted-registry matched every pod in the namespace and reported
workloads the chart doesn't deploy (agents, cert-manager solvers, jobs).
Report only UUG.AI images (uugai/, uug-ai/) not pulled from ghcr.io/uug-ai.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 09:03:47 +00:00
Cédric Verstraeten f2d462e95e Merge pull request #241 from kerberos-io/feat/hub-reactivate-ghcr
feat(hub): hardened defaults for every UUG.AI image, reactivate on GHCR (0.185.0)
2026-11-12 09:56:12 +01:00
Cédric VerstraetenandClaude Opus 5.5 6730f5c756 feat(hub): hardened defaults for every UUG.AI image, reactivate on GHCR (0.185.0)
Default to the signed, hardened releases of admin, hub-audit and the
dominantcolors, event, export, notification, notification-test, sprite and
thumbnail pipelines; run hub-reactivatesubscriptions from
ghcr.io/uug-ai (v1.0.3). Their policies now follow the configured
validationFailureAction; only hub-objecttracking (unsigned -gpu image)
and trusted-registry stay in Audit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 08:50:09 +00:00
Cédric Verstraeten caab6f6f55 Merge pull request #239 from kerberos-io/feat/hub-kyverno-missing-images
feat(hub): Kyverno policies for every image, trust only ghcr.io/uug-ai, hardened Admin default; remove hub-proxy and vault-forwarder (0.184.0)
2026-11-11 21:55:02 +01:00
Cédric VerstraetenandClaude Opus 5.5 3949cb4bac feat(hub): default Admin to signed, hardened ghcr.io/uug-ai/admin v1.4.5
Only ghcr.io/uug-ai images are trusted, so Admin now defaults to the
signed GHCR release with the hardened runtime production already uses
(8080, 65532 non-root, read-only root, memory /tmp, /healthz probes).
A legacy image can be restored with the documented opt-out values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 20:52:01 +00:00
Cédric VerstraetenandClaude Opus 5.5 1fe8fe9727 feat(hub): trust only ghcr.io/uug-ai images
- admin and hub-reactivatesubscriptions signature/hardening policies
  match ghcr.io/uug-ai only (Docker Hub references removed).
- Remove the oauth2-proxy (quay.io) signature policy.
- Add trusted-registry.yaml (category "Image registry", Audit): reports
  every container, init and ephemeral container image that is not from
  ghcr.io/uug-ai/. It flags the remaining non-GHCR images the chart can
  deploy: uugai/admin and uugai/hub-reactivatesubscriptions (Docker Hub)
  and quay.io/oauth2-proxy/oauth2-proxy.
- README updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 20:46:34 +00:00
Cédric VerstraetenandClaude Opus 5.5 5eb0d41aa7 feat(hub): keep the reactivate-subscriptions service
hub-reactivate-subscription is still needed: restore its template,
kerberoshub.reactivate values, README rows and its Kyverno signature and
hardening policies (Audit until the chart uses the signed
ghcr.io/uug-ai/hub-reactivatesubscriptions image). Only hub-proxy and
vault-forwarder are removed. The reactivate output is unchanged from main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 20:40:03 +00:00
Cédric VerstraetenandClaude Opus 5.5 5a445e0179 feat(hub): remove unused hub-proxy, reactivate-subscriptions and vault-forwarder
These services are no longer used. Remove their templates
(kerberos-vault/vault-proxy.yaml, kerberos-vault/vault-forwarder.yaml,
kerberos-hub/hub-reactivate-subscription.yaml), their values
(kerberoshub.proxy, kerberoshub.forwarder, kerberoshub.reactivate), their
README sections, and the Kyverno policies added for them in this change.

Installs that still set these values keep rendering; the keys are ignored.
Upgrading removes the hub-reactivate-subscription Deployment (and the
vault-proxy / vault-forwarder ones where enabled).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 20:37:09 +00:00
Cédric Verstraeten a34a82138a Merge pull request #238 from kerberos-io/feat/hub-frontend-v1.19.35-default
feat(hub): default hub-frontend v1.19.35, the first attested hardened release (0.183.1)
2026-11-11 21:36:15 +01:00
Cédric VerstraetenandClaude Opus 5.5 714e59f6fe feat(hub): Kyverno policies for every container image the chart deploys (0.184.0)
A scan of every container and init container the chart can render (all
modes and optional components) found 26 images; 21 had a signature
policy and 11 a hardening policy. Add the missing ones so Policy
Reporter shows every gap instead of "no policy":

- Hardening policies for the 10 ghcr.io/uug-ai images without a hardened
  release yet (audit, objecttracking, vlm, dominantcolors, event, export,
  notification, notification-test, sprite, thumbnail).
- Signature and hardening policies for the Docker Hub images uugai/admin,
  uugai/hub-proxy, uugai/hub-reactivatesubscriptions (also matching their
  ghcr.io/uug-ai releases) and the legacy kerberos/vault-forwarder.
- A visibility-only signature policy for the third-party oauth2-proxy.

All new policies stay in Audit regardless of the configured mode, so
they report gaps without ever blocking. README tables updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 20:31:32 +00:00
Cédric VerstraetenandClaude Opus 5.5 73106bfa10 feat(hub): default hub-frontend v1.19.35, the first attested hardened release (0.183.1)
hub-frontend v1.19.35 is a hardened release with the signed
image-hardening attestation (v1.19.34 was hardened before attestations
existed). With it, every chart default with a hardening policy is an
attested hardened release. Frontend runtime check and README updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 20:17:49 +00:00
Cédric Verstraeten 284765c401 Merge pull request #237 from kerberos-io/feat/hub-signed-image-defaults
feat(hub): default to signed and hardened image releases (0.183.0)
2026-11-11 19:18:58 +01:00
Cédric VerstraetenandClaude Opus 5.5 90be00011d Update frontend runtime check and README defaults for the new image tags
check-frontend-runtime.sh asserted the old default hub-frontend tag
(v1.19.28); README parameter table defaults were stale.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 18:14:58 +00:00
Cédric VerstraetenandClaude Opus 5.5 2d0ad37170 feat(hub): default to signed and hardened image releases (0.183.0)
Update every ghcr.io/uug-ai default image tag to the release running in
production: all signed by the UUG.AI release workflow for their own
repository, and hardened releases with the image-hardening attestation
for every image that has a hardening policy except hub-frontend. Older
defaults (e.g. hub-api v1.11.31, hub-workflows v1.0.0) were unsigned and
failed the chart's own Kyverno policies. Commented examples (loitering,
objecttracking, vlm) updated too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 18:10:44 +00:00
Cédric Verstraeten 113aa1fe34 Merge pull request #236 from kerberos-io/feat/hub-kyverno-categories
feat(hub): separate Kyverno categories for image signing and hardening (0.182.1)
2026-11-11 16:58:49 +01:00
Cédric VerstraetenandClaude Opus 5.5 2274c29e95 feat(hub): separate Kyverno categories for image signing and hardening (0.182.1)
All 32 image policies used the category "Supply chain", so Policy
Reporter and its dashboards could not tell signing from hardening
results. Signature policies (*-signed-images) now use "Image signing"
and hardening policies (*-hardened-images) "Image hardening". No change
to what the policies check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 15:44:51 +00:00
Cédric Verstraeten 13cb6b27b1 Merge pull request #235 from kerberos-io/feat/hub-signed-images-0.182.0
feat(hub): signature policies follow the configured mode for newly signed images (0.182.0)
2026-11-11 14:45:43 +01:00
Cédric VerstraetenandClaude Opus 5.5 96f3c6db68 feat(hub): signature policies follow the configured mode for newly signed images (0.182.0)
hub-audit, hub-vlm and the dominantcolors, event, export, notification,
notification-test, sprite and thumbnail pipelines now sign every release
(signed from the releases noted in the README). Their signature
policies drop the forced Audit and follow
imageVerification.validationFailureAction like the others.

hub-objecttracking stays in Audit: its releases are signed from v1.0.10,
but its GPU image (<tag>-gpu) is not, and the policy matches every tag.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 13:32:42 +00:00
Cédric Verstraeten 8192abd1f8 Merge pull request #234 from kerberos-io/feat/hub-kyverno-hardening
feat(hub): Kyverno policies requiring hardened UUG.AI releases (0.181.0)
2026-11-11 08:31:57 +01:00
Cédric VerstraetenandClaude Opus 5.5 fc2d904db4 feat(hub): Kyverno policies requiring hardened UUG.AI releases (0.181.0)
One namespaced Policy per image whose repository makes hardened
releases (11), requiring the signed image-hardening attestation
(https://github.com/uug-ai/workflows/attestations/image-hardening/v1)
from the UUG.AI release workflow for the image's own repository, with
predicate result "passed" and repository "uug-ai/<image>".

New opt-in imageHardening.enabled / validationFailureAction, separate
from imageVerification, since only recent hardened releases carry the
attestation. Schema and README updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 07:25:21 +00:00
Cédric Verstraeten cb8ce32992 Merge pull request #233 from kerberos-io/feat/hub-kyverno-all-images
feat(hub): Kyverno signature policies for every UUG.AI image (0.180.0)
2026-11-10 22:08:18 +01:00
Cédric VerstraetenandClaude Opus 5.5 3c4f8335ee feat(hub): Kyverno signature policies for every UUG.AI image (0.180.0)
One namespaced Policy per ghcr.io/uug-ai image the chart can deploy (21),
each requiring a keyless cosign signature from the UUG.AI release
workflow for the image's own uug-ai/<image> repository.

The 11 images whose releases are signed follow
imageVerification.validationFailureAction. The 10 whose releases are not
signed yet (their release workflows lack sign/id-token) always stay in
Audit, so Enforce never blocks them; each file says how to switch it.
Docker Hub images (uugai/*) and third-party oauth2-proxy are not covered.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 21:02:56 +00:00
Cédric Verstraeten c4227c44f9 Merge pull request #232 from kerberos-io/feat/hub-image-verification-opt-in
feat(hub): image verification opt-in by default (0.179.1)
2026-11-10 21:28:52 +01:00
Cédric VerstraetenandClaude Opus 5.5 900e7a97e9 feat(hub): image verification opt-in by default (0.179.1)
imageVerification.enabled now defaults to false: the Kyverno
image-verification policies are created only when enabled and Kyverno
is installed. README updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 20:24:57 +00:00
Cédric Verstraeten 9982557391 Merge pull request #231 from kerberos-io/feat/hub-kyverno-image-verification
feat(hub): verify UUG.AI image signatures with Kyverno (0.179.0)
2026-11-10 21:20:36 +01:00
Cédric VerstraetenandClaude Opus 5.5 e282793309 feat(hub): configurable Audit/Enforce for image verification
imageVerification.validationFailureAction (Audit by default, or Enforce)
sets the mode of the Kyverno image-verification policies. The schema
accepts only Audit or Enforce. README updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 20:14:32 +00:00
Cédric VerstraetenandClaude Opus 5.5 0c56301d8e refactor(hub): rely on Kyverno defaults in the hub-api policy
Drop validationFailureAction (Audit), background and required, which
equal Kyverno's defaults. Keep only failurePolicy: Ignore,
webhookTimeoutSeconds: 30 and mutateDigest/verifyDigest: false, whose
defaults would change behaviour, each with a comment saying why.
Verified on bijlokehof that the effective policy is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 20:07:18 +00:00
Cédric VerstraetenandClaude Opus 5.5 876f91b033 refactor(hub): imageVerification.enabled is the only setting
The Kyverno policy files are static: Audit, failurePolicy Ignore and no
digest mutation are written in the template. Templating remains only for
the guard (imageVerification.enabled and Kyverno installed). Moving to
Enforce becomes a change in the policy files, shipped in a chart
release. Per-policy values, schema and README updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 20:05:50 +00:00
Cédric VerstraetenandClaude Opus 5.5 e79ce0fd4e refactor(hub): static namespaced Kyverno Policy, no helpers
Make templates/kyverno/hub-api-signed-images.yaml a plain namespaced
kyverno.io/v1 Policy: it applies only to the release namespace, so it
needs no per-release name or namespace match, and the image, signer,
repository and Rekor settings are written out in the file. Templating is
left only for the Kyverno/enabled guard and the per-policy
validationFailureAction, failurePolicy and mutateDigest. Remove
templates/kyverno/_helpers.tpl and the global signer values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 19:54:09 +00:00
Cédric VerstraetenandClaude Opus 5.5 dcfcf28050 refactor(hub): one Kyverno policy per template in templates/kyverno
Move the image-verification policy to templates/kyverno/, one
ClusterPolicy per file (hub-api-signed-images.yaml), with the shared
parts (Kyverno check, names, labels, policy-wide settings and the UUG.AI
signer) in templates/kyverno/_helpers.tpl.

imageVerification.images becomes imageVerification.policies.<name>,
each with its own enabled switch and optional validationFailureAction,
failurePolicy and mutateDigest overrides. Schema and README updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 19:50:43 +00:00
Cédric VerstraetenandClaude Opus 5.5 49d6226a5d feat(hub): verify UUG.AI image signatures with Kyverno (0.179.0)
When Kyverno is installed (kyverno.io/v1 served), create a ClusterPolicy
per release, scoped to the release namespace, that verifies keyless
cosign signatures of UUG.AI images: issued by GitHub Actions, made by the
uug-ai/workflows release workflow, and for the image's own repository
(githubWorkflowRepository). Without Kyverno nothing is rendered.

Enabled by default in Audit mode with failurePolicy Ignore and no digest
mutation; Enforce, Fail and mutateDigest are opt-in. hub-api is the only
listed image until other releases are signed. Values schema and README
updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 19:47:22 +00:00
Cédric VerstraetenandClaude Opus 5.5 b0242acfd8 feat(hub): configurable analysis thumbnail regeneration threshold (0.178.0) (#230)
Add kerberospipeline.analysis.thumbnailRegeneration.minSeconds (default
3), passed to pipe-analysis as THUMBNAIL_REGENERATION_MIN_SECONDS. Analysis
only regenerates a thumbnail when the classified object first appears at
least this many seconds into the recording; 0 regenerates for every
object. Requires an analysis image that reads the variable.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 16:50:52 +01:00
Cédric VerstraetenandCopilot d4a8479a9b Add configurable Hub API MCP endpoint (0.177.0)
Expose MCP enabled/origin settings in Hub chart values and API deployment, with schema validation, documentation and rendering checks.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-08 20:15:29 +01:00
KilianandClaude Opus 5.5 8b96a8c7b8 feat(hub): recording check email template (0.176.0) (#228)
hub-monitor-device's recording check (a continuously recording camera that
is online but uploads no recordings) sends the new recording email template.
email.templates.recording / recordingTitle set RECORDING_TEMPLATE and
RECORDING_TITLE on the monitor when given; empty keeps the monitor defaults,
so SendGrid and Mailgun keep their device template id. The README documents
the email and its placeholders, and custom-layout/templates gets
recording.html / recording.txt.

custom-layout/templates/device.txt was a copy of the welcome email; it now
describes the device change like device.html.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 17:03:19 +01:00
KilianandClaude Opus 5.5 2e018357d8 hub: audit destination credentials inline only (0.175.0) (#227)
Destinations take credentials only as inline values: s3.accessKey /
s3.secretKey, bearerToken and secretHeaders.<name>.value. credentialsSecret,
bearerTokenSecret and secretHeaders secretName/secretKey are removed from the
values and README and fail the render with the inline field to use.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 12:03:44 +01:00
KilianandClaude Opus 5.5 0b9d07339f hub: audit destination field reference, enabled by default (0.174.0) (#226)
- README documents every destination field in its own row: required or
  optional, default and limits. Adds kerberoshub.audit.resources and
  topologySpreadConstraints.
- values.yaml examples mark each field required/optional with its default;
  kerberoshub.audit.resources is a real key next to replicas.
- A destination without `enabled` is now enabled (it rendered null, which
  hub-audit reads as false). `enabled: false` still pauses it.
- Empty or one-sided s3.accessKey/s3.secretKey fail the render instead of
  falling back to the pod's AWS identity, like an empty bearerToken.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 11:29:59 +01:00
KilianandClaude Opus 5.5 78cf3c7e2b hub: flag for the Users (preview) organisation tab (0.173.0) (#225)
Adds kerberoshub.frontend.features.organisations.membersAccessPreviewEnabled
(default "false"), setting FEATURE_MEMBERS_ACCESS_PREVIEW_ENABLED on the
frontend and the demo frontend.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 10:45:38 +01:00
KilianandClaude Opus 5.5 f0b79bea0c docs(hub): plain wording for audit destination credentials (0.172.1) (#224)
Describe inline audit destination credentials on their own terms in the
values comments and the README.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 10:44:59 +01:00
KilianandClaude Opus 5.5 535ddc847d hub: inline credentials for every audit destination (0.172.0) (#223)
Audit destinations now take credentials as plain values, like the other
Hub services: s3.accessKey / s3.secretKey on S3 destinations, bearerToken
and secretHeaders.<name>.value on webhooks. The chart stores them in a
Secret hub-audit-<id>-credentials and mounts it where hub-audit already
reads credential files, so no hub-audit change is needed and changed
values are picked up without a restart.

A shared hub.audit.destinations helper resolves inline values for the
ConfigMap, the Deployment volumes and the Secret template. Referencing an
existing Secret and credentialsSecret.create keep working and render
unchanged.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 09:26:05 +01:00
KilianandClaude Opus 5.5 1c1593a8a0 hub: optionally create the hub-audit S3 credentials Secret (0.171.0) (#222)
credentialsSecret.create: true makes the chart render the Secret from
credentialsSecret.accessKeyId / secretAccessKey, so the audit S3 keys can
live in values like the chart's other credentials. The default stays
"bring your own Secret".

Inline keys without create: true now fail instead of being ignored, two
destinations creating the same Secret fail, and the docs spell out that
accessKeyIdKey / secretAccessKeyKey name keys inside the Secret.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 08:33:48 +01:00
Cédric Verstraeten 5dfaaf0363 Merge pull request #221 from kerberos-io/fix/hub-api-security-context
Add hardened Hub API security contexts (0.170.1)
2026-11-04 15:47:29 +01:00
Cédric VerstraetenandCopilot 56ea65c6e7 Add hardened Hub API security contexts and writable scratch (0.170.1)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-04 14:05:22 +00:00
uug.ai af4e1c36a9 A new public release - 1793786033 (#219) 2026-11-04 11:19:24 +01:00
Cédric VerstraetenandCopilot 166b4a31e6 Default Hub frontends to hardened images on port 8080 (0.170.0) (#220)
Coordinate frontend v1.19.28, port 8080, non-root/read-only security defaults and writable scratch for primary and demo. Preserve Service port 80, Admin legacy defaults, and explicit legacy rollback settings. Validate real image startup, health and generated configuration in Kind.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-04 11:16:48 +01:00
Cédric VerstraetenandCopilot e938cb01b5 Release Hub chart 0.169.1 (#218)
Publish HTTP port recovery guidance from #217 without changing application images or runtime defaults.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-04 10:48:17 +01:00
Cédric VerstraetenandCopilot bd4e118a7a Add safe recovery for hardened HTTP port upgrades (#217)
Add guarded, dry-run-first HTTP port migration and rollback recovery for Admin and both frontends. Preserve unrelated ports and containers; validate strategic-merge duplicates and repaired specifications against Kubernetes in CI.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-04 10:19:05 +01:00
KilianandClaude Opus 5.5 fe658cce05 feat(hub): configure audit export events per file (0.169.0) (#216)
* feat(hub): configure the audit export limit (0.169.0)

kerberoshub.api.auditExportMaxEvents sets AUDIT_EXPORT_MAX_EVENTS on the
Hub API only: the most audit events one export file holds. Empty keeps
the API default of 100000; the schema accepts 1 to 1000000.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(hub): name the audit export setting per file

kerberoshub.api.auditExportMaxEventsPerFile sets
AUDIT_EXPORT_MAX_EVENTS_PER_FILE: it sizes each export file and does not
refuse larger exports, which the Audit page splits into parts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 09:00:17 +01:00
KilianandClaude Opus 5.5 1288266666 hub: default hub-audit v1.0.10 and provider-neutral audit docs (0.168.4) (#215)
The default kerberoshub.audit.tag was v1.0.1, which predates S3
destinations, so following the chart docs without overriding the tag
gave a rejected config. Defaults to v1.0.10 (S3 + allowUnlockedBucket).
The values example drops PR references for versions, adds maxAttempts,
names Ceph RGW/MinIO/Garage for path-style, notes that pod identity is
AWS-only, and links the per-provider settings on learn.uug.ai.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 17:06:49 +01:00
KilianandClaude Opus 5.5 9c1e37aba4 docs(hub): grouped device alerts as of hub-monitor-device v1.4.10 (0.168.3) (#214)
- custom-layout device_group.html: use the status-neutral sentence the
  built-in template got in hub-pipeline-notification#150.
- values.yaml / README: the grouping hold is 20 s
  (DEVICE_GROUPING_HOLD_SECONDS, monitor v1.4.10), no longer one scan.
- README: device {{text}} per change and the new grouped sections
  (monitor v1.4.9).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 16:44:05 +01:00
KilianandClaude Opus 5.5 b186ce94ba hub: pass allowUnlockedBucket through to hub-audit s3 destinations (#213)
The audit ConfigMap copies an allowlist of s3 fields, so the new
hub-audit allowUnlockedBucket option (uug-ai/hub-audit#16) was dropped.
Chart 0.168.2.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 15:32:29 +01:00
Cédric Verstraeten 584ea33ce6 Merge pull request #212 from kerberos-io/fix/hub-publish-0.168.1-20261030
Publish hub chart 0.168.1 (passkey flag, S3 Object Lock audit)
2026-10-30 13:55:18 +01:00
Cedric VerstraetenandClaude Opus 5.5 4442c9c201 fix(hub): publish 0.168.1 with passkey flag and S3 Object Lock audit
#211, #210 and #209 all bumped the chart to 0.168.0. The release was
cut from #211, so chart-releaser skipped the later merges and the
passkey feature flag (#210) and S3 Object Lock audit destinations (#209)
were never published. Bump to 0.168.1 to release them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 12:40:12 +00:00
KilianandClaude Opus 5.5 c1e15368c7 feat(hub): S3 Object Lock audit destinations (0.168.0) (#209)
Render kerberoshub.audit.destinations per type: webhook as before, or s3
with bucket, region, prefix, endpoint and forcePathStyle. An optional
credentialsSecret mounts the access key files hub-audit reads; without it
the pod's AWS identity is used. kerberoshub.audit.serviceAccount adds an
optional ServiceAccount for hub-audit alone, so IRSA access to the audit
bucket isn't granted to every Hub workload. Invalid combinations fail at
render time. Webhook-only output is unchanged.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 13:23:28 +01:00
Cédric Verstraeten 8d86eefba5 Merge pull request #210 from kerberos-io/feat/hub-passkeys-feature-flag-20261030
Set FEATURE_PASSKEYS_ENABLED from passkeys.enabled (0.168.0)
2026-10-30 13:22:48 +01:00
KilianandClaude Opus 5.5 78d83e368f feat(hub): frontend flag for the media detail Analysis block (0.168.0) (#211)
kerberoshub.frontend.features.media.detail.analysis.enabled sets
FEATURE_MEDIA_DETAIL_ANALYSIS_ENABLED on hub-frontend and
hub-frontend-demo. Defaults to "true", matching the frontend default.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 13:21:02 +01:00
Cedric VerstraetenandClaude Opus 5.5 e899f070f8 feat(hub): set FEATURE_PASSKEYS_ENABLED from passkeys.enabled (0.168.0)
Hub API and frontend now gate passkeys behind FEATURE_PASSKEYS_ENABLED.
kerberoshub.api.passkeys.enabled sets it to "true" on Hub API alongside
the passkey configuration, and passes the same value to the frontend and
demo frontend, so one switch turns passkeys on or off everywhere.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 11:01:05 +00:00
Cédric Verstraeten 4a643aa5c2 Merge pull request #203 from kerberos-io/feat/media-forward-flag
feat(hub): frontend flag for the Forward media action (0.167.0)
2026-10-28 19:36:04 +01:00
Cédric Verstraeten fdca7b1196 Merge branch 'main' into feat/media-forward-flag 2026-10-28 19:35:47 +01:00
Cédric VerstraetenandCopilot 5a130b7301 Merge pull request #208 from kerberos-io/fix/admin-runtime-release
Publish Hub chart 0.167.3 for the hardened Admin deployment rollout.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-28 19:30:32 +01:00
Cédric VerstraetenandCopilot 6f1dcf8f00 Release Hub 0.167.3 with hardened Admin routing and probes
Publish opt-in hardened runtime support with named Admin Service routing, configurable health probes, legacy defaults and runtime regression coverage.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-28 18:27:10 +00:00
Cédric Verstraeten 371408e5eb Merge pull request #207 from kerberos-io/security/image-hardening-rollout
Add opt-in hardened Hub worker runtime contexts
2026-10-27 14:43:33 +01:00
Copilot 0eff4bf2ee feat(hub): support opt-in hardened Admin and notification images
Retain default Admin port 80 while allowing 8080 and explicit container security settings. Add notification-only pod and container contexts without changing its provider or FFmpeg contract. Cover routing, tmpfs, TLS, identity, default preservation and per-component isolation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-26 22:35:20 +00:00
Copilot 18e456d490 feat(hub): add opt-in hardened worker runtime contexts
Preserve legacy image and deployment defaults while supporting verified per-worker container and pod security contexts alongside existing writable volumes. Document draft image contracts and workload-sized scratch storage; validate all/pipeline/ui renders and TLS compatibility.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-26 22:21:31 +00:00
Cédric Verstraeten 374d40aa41 Merge pull request #206 from kerberos-io/fix/hardened-frontend-demo-routing
Fix hardened frontend runtime settings for demo deployments
2026-10-26 21:37:24 +01:00
Copilot 073c12f8eb Fix hardened frontend runtime settings for demo deployments
Apply the shared port and security context to the demo frontend so both deployments support the shell-free v1.19.21 image. Release chart 0.167.2.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-26 20:33:49 +00:00
Cédric VerstraetenandCopilot 001c835068 Merge pull request #205 from kerberos-io/security/frontend-image-runtime
Add opt-in frontend port and security context support for hardened images.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-26 20:18:36 +01:00
Cédric VerstraetenandCopilot b520e2ecc1 Support hardened frontend ports and security context
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-26 18:20:57 +00:00
Cédric Verstraeten c42388b96f Merge pull request #204 from kerberos-io/feature/hub-passkey-configuration
Add Hub API passkey configuration and bump chart to 0.167.0
2026-10-26 14:47:35 +01:00
Cédric VerstraetenandCopilot 04d6990460 feat(hub): configure API passkeys and bump chart to 0.167.0
Add opt-in API-only WebAuthn configuration, value validation, deployment documentation and rendering checks.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-26 13:33:22 +00:00
Kilian BouteandClaude Opus 5.5 e228fd36fb feat(hub): frontend flag for the Forward media action (0.167.0)
Add kerberoshub.frontend.features.media.forward.enabled (default "true"),
rendered as FEATURE_MEDIA_FORWARD_ENABLED on hub-frontend and the demo
frontend. Setting it to "false" hides the bulk Forward media action in
the Recordings Actions menu.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 12:52:02 +00:00
KilianandClaude Opus 5.5 5af6472826 feat(hub): grouped device alerts — scan interval and device_group email (0.166.0) (#201)
* feat(hub): configurable device scan interval for hub-monitor-device (0.166.0)

kerberoshub.monitordevice.scanIntervalSeconds (default 60) is passed as
DEVICE_SCAN_INTERVAL_SECONDS: the pause between device status scans,
which also sets how long grouped device notifications are held.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(hub): device_group email for grouped camera status notifications

- custom-layout/templates/device_group.html|.txt: the email sent instead of
  device when several cameras of an account change status in one scan and
  the account groups device notifications.
- email.templates.deviceGroup / deviceGroupTitle, passed to
  hub-monitor-device as DEVICE_GROUP_TEMPLATE / DEVICE_GROUP_TITLE, plus
  PUBLIC_URL for the link to the cameras page.
- README: full reference for the device_group email (placeholders, how
  sections are built and rendered, custom templates, webhook
  device_statuses) and the correct {{status}} values of the device email.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(hub): device_group webhook data keys and whole-group section titles

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(hub): device_sections on grouped device webhooks

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(hub): name the device grouping toggle Group notifications

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(hub): drop device_statuses/device_sections webhook fields

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(hub): grouped device notifications skip webhooks and IFTTT; SMTP-only email

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 13:54:16 +02:00
KilianandClaude Opus 5.5 e0a273422e feat(hub): one sampling model — follow-only downstream, per-class hub-api ratios (#200)
* feat(hub): per-class trace sampling ratios for hub-api

Add opentelemetry.apiSamplingRatios (operator, default), rendered for hub-api
as TRACE_SAMPLER_RATIO_<CLASS> (trace library v1.2.5+): operator covers user
and API-client requests, default everything else hub-api starts (device
heartbeats, agent uploads and live segments, background work). Empty values
are not rendered, so defaults leave the manifests unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(hub): one sampling model, follow-only downstream

Downstream services (hub-workflows, pipe-*, workflow stages) only continue
traces started at the Vault or hub-api, so they now always render
OTEL_TRACES_SAMPLER=parentbased_always_off when OpenTelemetry is enabled
instead of a free-form opentelemetry.sampler, which a non-parent-based value
would have turned into independent re-sampling. hub-api is configured only
through opentelemetry.apiSamplingRatios (TRACE_SAMPLER_RATIO_<CLASS>);
opentelemetry.apiSampler is removed. Neither removed value is used by any
environment except production's sampler.type: parentbased_always_off, which
is now the fixed behaviour, so production renders unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(hub): rename hub-api's catch-all sampling class to other

trace v1.2.6 renames the catch-all class from default to other and
rejects TRACE_SAMPLER_RATIO_DEFAULT.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 13:45:08 +02:00
Cédric Verstraeten 71e2db095e Merge pull request #202 from kerberos-io/feature/pipeline-sequence-log-level
feature/pipeline-sequence-log-level
2026-10-23 10:54:42 +02:00
Cédric Verstraeten 356f510ba9 Merge branch 'main' into feature/pipeline-sequence-log-level 2026-10-23 10:45:44 +02:00
Cédric Verstraeten 95ffe47edb Configure sequence logs and thumbnail keyframes
Expose Sequence LOG_LEVEL with an info default and document its configuration. Add thumbnail skipNonKeyframes, enabled by default for faster extraction. Bump the Hub chart to 0.159.1.
2026-10-23 10:44:59 +02:00
KilianandClaude Opus 5.5 abb22276ca feat(hub): split the OTel sampler for hub-api and trace workflow stages (#199)
opentelemetry.sampler now applies only to the services downstream of the Vault
(hub-workflows, pipe-*, workflow stages), which continue the Vault's trace and
should run parentbased_always_off; hub-api, which starts its own traces for
user requests, gets opentelemetry.apiSampler. Workflow stages (hub-stage.yaml)
had no OTEL_EXPORTER_OTLP_ENDPOINT, so their tracing ran in no-op mode; they
now get the endpoint and the downstream sampler when opentelemetry is enabled.

Defaults are empty, so the only rendered change is the stage endpoint.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 13:58:44 +02:00
KilianandClaude Opus 5.5 331df9d4af docs(hub): document email template variables (#198)
Explain how email templates are selected (SMTP files under
/mail/templates vs built-in vs Mailgun), how the base and tenant URLs
are built from kerberoshub.frontend, and which value each {{variable}}
gets in the alert, camera status, account and sharing emails.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 16:08:18 +02:00
KilianandClaude Opus 5.5 17dd600872 feat(hub): make analysis thumbnail regeneration opt-in (0.163.0) (#197)
Add kerberospipeline.analysis.thumbnailRegeneration.enabled (default
false), passed to pipe-analysis as THUMBNAIL_REGENERATION_ENABLED. When
enabled, analysis regenerates thumbnails from the first frame of a
classified moving object, adding one thumbnail job per classified
recording. Requires an analysis image that reads the flag.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 18:22:29 +02:00
KilianandClaude Opus 5.5 4880dc1197 feat(hub): allow defining the classification catalog inline in values (0.162.0) (#196)
Restore the optional classificationCatalog values key removed in 0.155.0.
A nonempty list ({key, label, icon?}, unique keys) replaces the bundled
defaults in the chart's classification-catalog ConfigMap, so Hub API and
the bundled catalogs/classifications.json share it. Omitted or null keeps
the defaults; [] is rejected. Combining it with classificationCatalogEnabled:
false, classificationCatalogExistingConfigMap or a classifications.json
replacement fails rendering.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 15:24:00 +02:00
KilianandClaude Opus 5.5 805dfe405b feat(hub): add frontend Google Places API selector (0.161.0) (#195)
Add kerberoshub.frontend.googlemaps.placesApi, rendered as
GOOGLEMAPS_PLACES_API on the hub-frontend and hub-frontend-demo
deployments. Defaults to "legacy" so existing keys keep working; "new"
switches address autocomplete to Places API (New) with the same apikey.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 15:14:23 +02:00
Kilian e861dbecc3 Merge pull request #194 from kerberos-io/feat/workflow-label-triggers
feat(hub): device system tags and system-tag global workflows (0.160.0)
2026-10-16 13:57:39 +02:00
Kilian BouteandClaude Opus 5.5 a35e701cb2 feat(hub): align the system tag catalog with the chart's catalog conventions
- Catalog entries use the Hub shared choice catalog format (key, label) plus
  an optional description, as catalogs/classifications.json does.
- values.schema.json now validates deviceTags (enabled boolean; entries with
  a stored-form key, a label, an optional description; no unknown keys), so
  mistakes fail like other values; templates keep the uniqueness and
  workflow cross-checks.
- The catalog stays inline in values (Helm checks workflow systemTags against
  it) but reaches hub-api like the classification catalog: the
  device-system-tags ConfigMap mounted at /etc/kerberos/device-tags, named by
  DEVICE_SYSTEM_TAGS_FILE, with a checksum annotation. The DEVICE_SYSTEM_TAGS
  JSON env var is gone (hub-api#672 reads the file).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 08:24:21 +00:00
Kilian BouteandClaude Opus 5.5 7cddafbe93 feat(hub): harden the system tag gate for workflow definitions
The engine reads JSON keys case-insensitively and ignores unknown ones, so
the previous exact-path checks could be bypassed (SystemTags, nested or
shared conditions, nested stage needs) and a typo (systemTag:, trigger:)
silently removed a workflow's device scope.

- While deviceTags.system.enabled is false, any mention of system tags in a
  definition (case-insensitive, any depth) fails rendering.
- Definitions may only use id, enabled, triggers and stages, and triggers
  only the models.WorkflowTrigger fields, spelled exactly.
- deviceTags.system.enabled must be a boolean; the string "false" enabled it.

Production's definitions still render.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 08:07:41 +00:00
Kilian BouteandClaude Opus 5.5 5c6198eff2 feat(hub): rename device labels to typed device tags (system tags)
Root deviceLabelsEnabled/deviceLabels become deviceTags.system.enabled and
deviceTags.system.catalog, leaving room for more tag types. hub-api gets
DEVICE_SYSTEM_TAGS, and workflow triggers select devices with systemTags
(condition path device.systemTags). Matches uug-ai/models#284.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 08:47:47 +00:00
Kilian BouteandClaude Opus 5.5 f2ab8c8505 feat(hub): gate device labels with a root deviceLabelsEnabled flag
Replace kerberoshub.workflows.labelTriggers (and hub-workflows'
WORKFLOW_LABEL_TRIGGERS_ENABLED) with one deployment-wide switch next to
the catalog. While it is off, hub-api gets an empty catalog and rendering
fails on any workflow that selects devices by label (trigger labels or
device.labels conditions).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 08:12:52 +00:00
Kilian BouteandClaude Opus 5.5 7472d44683 feat(hub): add the device label catalog
Root deviceLabels is the deployment's device label catalog: the only
labels owners can assign to devices (value, name, description). It is
rendered as DEVICE_LABELS on hub-api; empty (the default) disables
device labels.

Labels are a device concept, so the catalog lives at the root next to
the classification catalog; workflows only refer to it. Rendering fails
when a catalog value is invalid or duplicated, or when a workflow
trigger selects a label that is not in the catalog, so a typo cannot
silently match no device. labelTriggers stays the separate trust switch
for global workflows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 07:47:14 +00:00
Kilian BouteandClaude Opus 5.5 004377e69d feat(hub): allow label-scoped global workflows (0.160.0)
Add kerberoshub.workflows.labelTriggers (default false), rendered as
WORKFLOW_LABEL_TRIGGERS_ENABLED on hub-workflows. It lets global workflow
definitions select devices across organisations by owner-set device
label (trigger `labels`); the engine refuses such definitions until it
is enabled. Owners set labels, so enable it only where owners are
trusted administrators (self-hosted), never on multi-tenant SaaS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 07:16:11 +00:00
KilianandClaude Opus 5.5 664c587300 feat(hub): make the dominant color stage opt-in (#193)
* feat(hub): make the dominant color stage opt-in

Add kerberospipeline.dominantColor.enabled (default false). It gates the
pipe-dominantcolor deployment and is projected to pipe-analysis as
DOMINANTCOLOR_ENABLED, so the producer and consumer follow one value.

Requires a hub-pipeline-analysis image that reads DOMINANTCOLOR_ENABLED:
older images always publish to kcloud-dominantcolor-queue.fifo, which
would back up once the consumer is removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(hub): bump analysis to v1.8.22 for DOMINANTCOLOR_ENABLED

v1.8.22 is the first hub-pipeline-analysis release that reads the flag
(uug-ai/hub-pipeline-analysis#114). Chart version moves to 0.159.0 since
0.158.0 was taken on main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 14:52:23 +02:00
95 changed files with 6484 additions and 384 deletions
@@ -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
+1 -1
View File
@@ -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.158.0
version: 0.186.0
# 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
+860 -57
View File
File diff suppressed because it is too large Load Diff
@@ -2,15 +2,15 @@ Kerberos.io
------------
Hey, {{user}}
Welcome to Kerberos Hub
Activate your account ->
One of your Kerberos Agents changed
{{text}}
Agents go offline due to a variety of reasons. The machine, node, micro controller on which your Agent runs, gets corrupted or disconnected from the internet. The camera itself is broken, damaged or in the worst case tampered. Have a look into your Kerberos Hub account for the latest recordings and/or verify the connection and status of your Kerberos Agent.
Go to Kerberos Hub ->
{{link}}
Kerberos Hub in a nutshell
With Kerberos Hub you can access your surveillance media remotely. By subscribing to a plan, you get access to a set of features, from advanced filtering, notifications to machine learning.
However before you get started this amazing applications, please activate your account.
Get in touch
------------
support@kerberos.io
@@ -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.
@@ -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;">Camera recordings</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">Cameras</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;">These cameras record continuously, so a new recording normally arrives every few minutes. When a camera is online but its recordings stop arriving, check that the account has not reached its upload limit, that the storage the recordings are sent to is reachable and not full, and that the Agent's cloud settings and credentials are still valid. Cameras that are recording again need no action.</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,24 @@
Kerberos.io
------------
Hey, {{user}}
Camera recordings
{{text}}
{{sectionsText}}
These cameras record continuously, so a new recording normally arrives every few minutes. When a camera is online but its recordings stop arriving, check that the account has not reached its upload limit, that the storage the recordings are sent to is reachable and not full, and that the Agent's cloud settings and credentials are still valid. Cameras that are recording again need no action.
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
+11 -3
View File
@@ -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 -}}
+51
View File
@@ -0,0 +1,51 @@
{{/*
The system tag catalog (deviceTags.system.catalog) uses the Hub shared choice
catalog format: {key, label, description?}. values.schema.json validates each
entry (key in stored device tag form, models.NormalizeDeviceTags: 1-64
characters of a-z, 0-9, ".", "_", ":" and "-", starting and ending with a
letter or digit) and that enabled is a boolean; keys must also be unique.
*/}}
{{- define "kerberoshub.deviceTags.system.validate" -}}
{{- $seen := dict -}}
{{- range $tag := (include "kerberoshub.deviceTags.system.catalog" . | fromJsonArray) -}}
{{- $key := toString $tag.key -}}
{{- if hasKey $seen $key -}}
{{- fail (printf "deviceTags.system.catalog: duplicate key %q" $key) -}}
{{- end -}}
{{- $_ := set $seen $key true -}}
{{- end -}}
{{- end -}}
{{/*
The configured system tag catalog as a JSON array, whether or not system tags
are enabled (missing values read as empty).
*/}}
{{- define "kerberoshub.deviceTags.system.catalog" -}}
{{- $deviceTags := .Values.deviceTags | default dict -}}
{{- $system := $deviceTags.system | default dict -}}
{{- $system.catalog | default list | toJson -}}
{{- end -}}
{{/*
Whether system tags are enabled (deviceTags.system.enabled): "true" or "".
*/}}
{{- define "kerberoshub.deviceTags.system.enabled" -}}
{{- $deviceTags := .Values.deviceTags | default dict -}}
{{- $system := $deviceTags.system | default dict -}}
{{- if and (kindIs "bool" $system.enabled) $system.enabled -}}true{{- end -}}
{{- end -}}
{{/*
The system tag catalog file hub-api reads (DEVICE_SYSTEM_TAGS_FILE), rendered
from deviceTags.system.catalog into the device-system-tags ConfigMap.
*/}}
{{- define "kerberoshub.deviceTags.system.json" -}}
{{- include "kerberoshub.deviceTags.system.validate" . -}}
{{- $tags := list -}}
{{- range $tag := (include "kerberoshub.deviceTags.system.catalog" . | fromJsonArray) -}}
{{- $entry := dict "key" $tag.key "label" $tag.label -}}
{{- with $tag.description }}{{- $_ := set $entry "description" . -}}{{- end }}
{{- $tags = append $tags $entry -}}
{{- end -}}
{{- $tags | toJson -}}
{{- end -}}
+78 -7
View File
@@ -84,17 +84,88 @@ 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 -}}
{{/*
hub.audit.destinations resolves the audit destinations into JSON. Inline
credentials (s3.accessKey / s3.secretKey on S3 destinations, bearerToken and
secretHeaders.<name>.value on webhooks) are the only way to pass credentials.
They are stored in a chart-created Secret named hub-audit-<id>-credentials;
the internal credentialsSecret / bearerTokenSecret / secretHeaders entries
below describe that Secret for the other templates.
Use: fromJsonArray (include "hub.audit.destinations" .)
*/}}
{{- define "hub.audit.destinations" -}}
{{- $resolved := list -}}
{{- range $index, $original := .Values.kerberoshub.audit.destinations -}}
{{- $destination := deepCopy $original -}}
{{- $label := default (printf "%d" $index) $destination.id -}}
{{- if hasKey $destination "credentialsSecret" -}}
{{- fail (printf "audit destination %s: credentialsSecret is not supported; set s3.accessKey and s3.secretKey" $label) -}}
{{- end -}}
{{- if hasKey $destination "bearerTokenSecret" -}}
{{- fail (printf "audit destination %s: bearerTokenSecret is not supported; set bearerToken" $label) -}}
{{- end -}}
{{- $s3 := default dict $destination.s3 -}}
{{- if or (hasKey $s3 "accessKey") (hasKey $s3 "secretKey") -}}
{{- $id := required (printf "id is required for audit destination %d" $index) $destination.id -}}
{{- if not (and $s3.accessKey $s3.secretKey) -}}
{{- fail (printf "audit destination %s must set both s3.accessKey and s3.secretKey, and neither may be empty" $id) -}}
{{- end -}}
{{- $_ := set $destination "credentialsSecret" (dict
"name" (printf "hub-audit-%s-credentials" $id)
"create" true
"accessKeyId" $s3.accessKey
"secretAccessKey" $s3.secretKey) -}}
{{- $_ := unset $s3 "accessKey" -}}
{{- $_ := unset $s3 "secretKey" -}}
{{- end -}}
{{- if hasKey $destination "bearerToken" -}}
{{- $id := required (printf "id is required for audit destination %d" $index) $destination.id -}}
{{- if not $destination.bearerToken -}}
{{- fail (printf "audit destination %s sets an empty bearerToken" $id) -}}
{{- end -}}
{{- $_ := set $destination "bearerTokenSecret" (dict
"name" (printf "hub-audit-%s-credentials" $id)
"key" "bearer-token"
"create" true
"value" $destination.bearerToken) -}}
{{- $_ := unset $destination "bearerToken" -}}
{{- end -}}
{{- range $header, $secret := (default dict $destination.secretHeaders) -}}
{{- if or (not (kindIs "map" $secret)) (not (hasKey $secret "value")) (hasKey $secret "secretName") (hasKey $secret "secretKey") -}}
{{- fail (printf "audit destination %s header %s: set secretHeaders.%s.value" $label $header $header) -}}
{{- end -}}
{{- $id := required (printf "id is required for audit destination %d" $index) $destination.id -}}
{{- if not $secret.value -}}
{{- fail (printf "audit destination %s header %s sets an empty value" $id $header) -}}
{{- end -}}
{{- $_ := set $secret "secretName" (printf "hub-audit-%s-credentials" $id) -}}
{{- $_ := set $secret "secretKey" (printf "header-%s" (sha256sum $header | trunc 12)) -}}
{{- $_ := set $secret "create" true -}}
{{- end -}}
{{- $resolved = append $resolved $destination -}}
{{- end -}}
{{- toJson $resolved -}}
{{- end -}}
+13 -1
View File
@@ -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 }}"
+1 -1
View File
@@ -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 -}}
@@ -0,0 +1,12 @@
{{- if include "kerberoshub.deviceTags.system.enabled" . }}
apiVersion: v1
kind: ConfigMap
metadata:
name: device-system-tags
namespace: {{ .Release.Namespace }}
data:
# Rendered from deviceTags.system.catalog; hub-api reads it through
# DEVICE_SYSTEM_TAGS_FILE.
system-tags.json: |-
{{- include "kerberoshub.deviceTags.system.json" . | mustFromJson | toPrettyJson | nindent 4 }}
{{- end -}}
@@ -1,7 +1,7 @@
{{- if .Values.audit.enabled -}}
{{- $destinations := list -}}
{{- $destinationIDs := dict -}}
{{- range $index, $destination := .Values.kerberoshub.audit.destinations -}}
{{- range $index, $destination := fromJsonArray (include "hub.audit.destinations" .) -}}
{{- $id := required (printf "id is required for audit destination %d" $index) $destination.id -}}
{{- if not (regexMatch "^[a-z][a-z0-9-]{0,62}$" $id) -}}
{{- fail (printf "audit destination id %q must match ^[a-z][a-z0-9-]{0,62}$" $id) -}}
@@ -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)
"enabled" $destination.enabled
"url" $url
"type" $type
"enabled" (ternary $destination.enabled true (hasKey $destination "enabled"))
"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, bearerToken, 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 -}}
+57 -3
View File
@@ -125,6 +125,9 @@ spec:
{{- if and .Values.classificationCatalogEnabled (not .Values.classificationCatalogExistingConfigMap) (not $catalogReplacement) }}
checksum/classification-catalog: {{ include (print $.Template.BasePath "/configMaps/catalogs/classifications.yaml") . | sha256sum }}
{{- end }}
{{- if include "kerberoshub.deviceTags.system.enabled" . }}
checksum/device-system-tags: {{ include (print $.Template.BasePath "/configMaps/catalogs/device-system-tags.yaml") . | sha256sum }}
{{- end }}
{{- if and $workflowConfiguration.enabled $workflowConfiguration.includeDefaults (not (hasKey $workflowConfiguration.replacements "workflow-contracts/start.yaml")) }}
checksum/workflow-configuration: {{ include (print $.Template.BasePath "/configMaps/workflow-contracts/start.yaml") . | sha256sum }}
{{- end }}
@@ -137,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 }}
@@ -147,7 +154,8 @@ spec:
{{- $serverTLS := .Values.kerberoshub.api.serverTLS }}
{{- $mongodbTLS := .Values.mongodb.tls }}
{{- $classificationCatalogEnabled := .Values.classificationCatalogEnabled }}
{{- if or $workflowConfiguration.enabled $classificationCatalogEnabled .Values.kerberoshub.api.volumes (and $serverTLS.enabled $serverTLS.secretName) (and $mongodbTLS.enabled $mongodbTLS.existingSecret) }}
{{- $deviceSystemTagsEnabled := include "kerberoshub.deviceTags.system.enabled" . }}
{{- if or $workflowConfiguration.enabled $classificationCatalogEnabled $deviceSystemTagsEnabled .Values.kerberoshub.api.volumes (and $serverTLS.enabled $serverTLS.secretName) (and $mongodbTLS.enabled $mongodbTLS.existingSecret) }}
volumes:
{{- if $workflowConfiguration.enabled }}
- name: workflow-configuration
@@ -200,6 +208,11 @@ spec:
path: classifications.json
{{- end }}
{{- end }}
{{- if $deviceSystemTagsEnabled }}
- name: device-system-tags
configMap:
name: device-system-tags
{{- end }}
{{- with .Values.kerberoshub.api.volumes }}
{{- toYaml . | nindent 8 }}
{{- end }}
@@ -220,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 }}
@@ -227,7 +244,7 @@ spec:
ports:
- containerPort: 80
name: http
{{- if or $workflowConfiguration.enabled $classificationCatalogEnabled .Values.kerberoshub.api.volumeMounts (and $serverTLS.enabled $serverTLS.secretName) (and $mongodbTLS.enabled $mongodbTLS.existingSecret) }}
{{- if or $workflowConfiguration.enabled $classificationCatalogEnabled $deviceSystemTagsEnabled .Values.kerberoshub.api.volumeMounts (and $serverTLS.enabled $serverTLS.secretName) (and $mongodbTLS.enabled $mongodbTLS.existingSecret) }}
volumeMounts:
{{- if $workflowConfiguration.enabled }}
- name: workflow-configuration
@@ -239,6 +256,11 @@ spec:
mountPath: /etc/kerberos/classifications
readOnly: true
{{- end }}
{{- if $deviceSystemTagsEnabled }}
- name: device-system-tags
mountPath: /etc/kerberos/device-tags
readOnly: true
{{- end }}
{{- with .Values.kerberoshub.api.volumeMounts}}
{{- toYaml . | nindent 12 }}
{{- end }}
@@ -261,6 +283,12 @@ spec:
- name: CLASSIFICATION_CATALOG_FILE
value: /etc/kerberos/classifications/classifications.json
{{- end }}
{{- if $deviceSystemTagsEnabled }}
# System tag catalog (deviceTags.system.catalog): the only system
# tags owners can assign. Unset (disabled) disables system tags.
- name: DEVICE_SYSTEM_TAGS_FILE
value: /etc/kerberos/device-tags/system-tags.json
{{- end }}
- name: ENVIRONMENT
value: "{{ .Values.environment }}"
- name: READ_ONLY
@@ -275,6 +303,12 @@ spec:
value: "{{ .Values.kerberoshub.api.schema }}://{{ .Values.kerberoshub.api.url }}"
- name: PUBLIC_URL
value: "{{ .Values.kerberoshub.frontend.schema }}://{{ .Values.kerberoshub.frontend.url }}"
- name: ACCESS_TOKEN_TIMEOUT
value: {{ .Values.kerberoshub.api.session.accessTokenTimeout | quote }}
- name: SESSION_IDLE_TIMEOUT
value: {{ .Values.kerberoshub.api.session.idleTimeout | quote }}
- name: SESSION_MAX_LIFETIME
value: {{ .Values.kerberoshub.api.session.maxLifetime | quote }}
- name: REFRESH_COOKIE_SECURE
value: {{ eq .Values.kerberoshub.api.schema "https" | quote }}
{{- $corsOrigins := list (printf "%s://%s" .Values.kerberoshub.frontend.schema .Values.kerberoshub.frontend.url) }}
@@ -295,6 +329,11 @@ spec:
{{- end }}
- name: CORS_ALLOWED_ORIGINS
value: {{ join "," $corsOrigins | quote }}
{{- $mcp := .Values.kerberoshub.api.mcp | default dict }}
- name: MCP_ENABLED
value: {{ $mcp.enabled | default false | quote }}
- name: MCP_ALLOWED_ORIGINS
value: {{ join "," ($mcp.allowedOrigins | default list) | quote }}
{{ if .Values.isPrivate }}
- name: KERBEROS_PRIVATE_CLOUD
value: "true"
@@ -416,6 +455,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 }}"
@@ -522,6 +572,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
@@ -577,6 +631,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,53 @@
{{- if .Values.audit.enabled }}
{{- /*
Secret the chart creates per audit destination from its inline credentials
(s3.accessKey / s3.secretKey, bearerToken, secretHeaders.<name>.value).
*/}}
{{- $created := dict }}
{{- range $index, $destination := fromJsonArray (include "hub.audit.destinations" .) }}
{{- $data := dict }}
{{- $name := "" }}
{{- $credentials := default dict $destination.credentialsSecret }}
{{- if $credentials.create }}
{{- $name = required (printf "credentialsSecret.name is required for destination %s" $destination.id) $credentials.name }}
{{- $accessKeyIdKey := default "access-key-id" $credentials.accessKeyIdKey }}
{{- $secretAccessKeyKey := default "secret-access-key" $credentials.secretAccessKeyKey }}
{{- if eq $accessKeyIdKey $secretAccessKeyKey }}
{{- fail (printf "credentialsSecret.accessKeyIdKey and secretAccessKeyKey must differ for destination %s" $destination.id) }}
{{- end }}
{{- $_ := set $data $accessKeyIdKey (required (printf "credentialsSecret.accessKeyId is required for destination %s when credentialsSecret.create is true" $destination.id) $credentials.accessKeyId) }}
{{- $_ := set $data $secretAccessKeyKey (required (printf "credentialsSecret.secretAccessKey is required for destination %s when credentialsSecret.create is true" $destination.id) $credentials.secretAccessKey) }}
{{- end }}
{{- $bearer := default dict $destination.bearerTokenSecret }}
{{- if $bearer.create }}
{{- $name = $bearer.name }}
{{- $_ := set $data $bearer.key $bearer.value }}
{{- end }}
{{- range $header, $secret := (default dict $destination.secretHeaders) }}
{{- if $secret.create }}
{{- $name = $secret.secretName }}
{{- $_ := set $data $secret.secretKey $secret.value }}
{{- end }}
{{- end }}
{{- if $name }}
{{- if hasKey $created $name }}
{{- fail (printf "audit destinations %s and %s both create Secret %q" (get $created $name) $destination.id $name) }}
{{- end }}
{{- $_ := set $created $name $destination.id }}
---
apiVersion: v1
kind: Secret
metadata:
name: {{ $name }}
namespace: {{ $.Release.Namespace }}
labels:
app: hub-audit
audit-destination: {{ $destination.id }}
type: Opaque
stringData:
{{- range $key, $value := $data }}
{{ $key | quote }}: {{ toString $value | quote }}
{{- end }}
{{- end }}
{{- 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 }}
@@ -38,13 +43,25 @@ spec:
- name: destinations
configMap:
name: hub-audit
{{- range $index, $destination := .Values.kerberoshub.audit.destinations }}
{{- range $index, $destination := fromJsonArray (include "hub.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 }}
@@ -95,10 +112,11 @@ spec:
- name: destinations
mountPath: /etc/hub-audit
readOnly: true
{{- range $index, $destination := .Values.kerberoshub.audit.destinations }}
{{- range $index, $destination := fromJsonArray (include "hub.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:
@@ -192,6 +196,8 @@ spec:
# Google maps for using and displaying cameras, sites on a map
- name: GOOGLEMAPS_KEY
value: "{{ .Values.kerberoshub.frontend.googlemaps.apikey }}"
- name: GOOGLEMAPS_PLACES_API
value: "{{ .Values.kerberoshub.frontend.googlemaps.placesApi }}"
# features > chart
- name: CHART_COLORS
@@ -235,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
@@ -251,6 +259,8 @@ spec:
value: "{{ .Values.kerberoshub.frontend.features.organisations.creationEnabled }}"
- name: FEATURE_ORGANISATION_SETTINGS_ENABLED
value: "{{ .Values.kerberoshub.frontend.features.organisations.settingsEnabled }}"
- name: FEATURE_MEMBERS_ACCESS_PREVIEW_ENABLED
value: "{{ .Values.kerberoshub.frontend.features.organisations.membersAccessPreviewEnabled }}"
- name: FEATURE_PROJECTS_ENABLED
value: "{{ .Values.kerberoshub.frontend.features.projects.enabled }}"
- name: FEATURE_PROJECT_SWITCHER_ENABLED
@@ -359,6 +369,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:
@@ -275,6 +279,8 @@ spec:
# Google maps for using and displaying cameras, sites on a map
- name: GOOGLEMAPS_KEY
value: "{{ .Values.kerberoshub.frontend.googlemaps.apikey }}"
- name: GOOGLEMAPS_PLACES_API
value: "{{ .Values.kerberoshub.frontend.googlemaps.placesApi }}"
# Zendesk for support
- name: ZENDESK_URL
@@ -314,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
@@ -330,6 +338,8 @@ spec:
value: "{{ .Values.kerberoshub.frontend.features.organisations.creationEnabled }}"
- name: FEATURE_ORGANISATION_SETTINGS_ENABLED
value: "{{ .Values.kerberoshub.frontend.features.organisations.settingsEnabled }}"
- name: FEATURE_MEMBERS_ACCESS_PREVIEW_ENABLED
value: "{{ .Values.kerberoshub.frontend.features.organisations.membersAccessPreviewEnabled }}"
- name: FEATURE_PROJECTS_ENABLED
value: "{{ .Values.kerberoshub.frontend.features.projects.enabled }}"
- name: FEATURE_PROJECT_SWITCHER_ENABLED
@@ -451,6 +461,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,21 @@ 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" }}"
{{- with .Values.email.templates.recording }}
- name: RECORDING_TEMPLATE
value: {{ . | quote }}
{{- end }}
{{- with .Values.email.templates.recordingTitle }}
- name: RECORDING_TITLE
value: {{ . | quote }}
{{- end }}
# 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
@@ -21,6 +21,64 @@ workflow later cannot silently alias another workflow's runs or references.
{{- end -}}
{{- $_ := set $identities $id $name -}}
{{- end -}}
{{- include "kerberoshub.workflows.validateSystemTags" . -}}
{{- end -}}
{{/*
Guard the definitions that can select devices by system tag. The engine reads
JSON keys case-insensitively and ignores unknown ones, so a misspelled or
re-cased key silently changes what a definition selects (a dropped
systemTags scope makes a global workflow run on every device). Therefore:
- definitions and triggers may only use the keys the engine knows, spelled
exactly (definition: id, enabled, triggers, stages; trigger: the
models.WorkflowTrigger fields);
- while deviceTags.system.enabled is false, no definition may mention system
tags anywhere (trigger systemTags, or a device.systemTags condition at any
depth, in triggers, shared conditions or stage needs), matched
case-insensitively on the whole definition;
- every system tag a trigger selects must be in deviceTags.system.catalog,
so a typo fails rendering instead of never matching a device.
Disabled definitions are checked too, so enabling one cannot fail later.
*/}}
{{- define "kerberoshub.workflows.validateSystemTags" -}}
{{- include "kerberoshub.deviceTags.system.validate" . -}}
{{- $catalog := dict -}}
{{- range $tag := (include "kerberoshub.deviceTags.system.catalog" . | fromJsonArray) -}}
{{- $_ := set $catalog (toString $tag.key) true -}}
{{- end -}}
{{- $enabled := include "kerberoshub.deviceTags.system.enabled" . -}}
{{- $definitionKeys := list "id" "enabled" "triggers" "stages" -}}
{{- $triggerKeys := list "type" "edgeId" "sharedConditions" "devices" "siteIds" "groupIds" "systemTags" "classifications" "conditionMode" "conditions" "weeklySchedule" "surfaces" -}}
{{- range $name, $wf := .Values.kerberoshub.workflows.definitions -}}
{{- if $wf -}}
{{- if not (kindIs "map" $wf) -}}
{{- fail (printf "kerberoshub.workflows.definitions[%q] must be an object" $name) -}}
{{- end -}}
{{- if and (not $enabled) (contains "systemtags" (lower (toJson $wf))) -}}
{{- fail (printf "kerberoshub.workflows.definitions[%q] selects devices by system tag (systemTags or a device.systemTags condition), but deviceTags.system.enabled is false" $name) -}}
{{- end -}}
{{- range $key, $_ := $wf -}}
{{- if not (has $key $definitionKeys) -}}
{{- fail (printf "kerberoshub.workflows.definitions[%q]: unknown key %q (allowed: %s). Keys are case-sensitive; a misspelled key would be ignored" $name $key (join ", " $definitionKeys)) -}}
{{- end -}}
{{- end -}}
{{- range $i, $trigger := ($wf.triggers | default list) -}}
{{- if not (kindIs "map" $trigger) -}}
{{- fail (printf "kerberoshub.workflows.definitions[%q].triggers[%d] must be an object" $name $i) -}}
{{- end -}}
{{- range $key, $_ := $trigger -}}
{{- if not (has $key $triggerKeys) -}}
{{- fail (printf "kerberoshub.workflows.definitions[%q].triggers[%d]: unknown key %q (allowed: %s). Keys are case-sensitive; a misspelled key would be ignored and widen the trigger" $name $i $key (join ", " $triggerKeys)) -}}
{{- end -}}
{{- end -}}
{{- range $tag := ($trigger.systemTags | default list) -}}
{{- if not (hasKey $catalog (toString $tag)) -}}
{{- fail (printf "kerberoshub.workflows.definitions[%q].triggers[%d].systemTags: %q is not in deviceTags.system.catalog; add it to the system tag catalog" $name $i (toString $tag)) -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{/*
@@ -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 }}
@@ -122,6 +122,18 @@ spec:
- name: SPRITE_ENABLED
value: "{{ .Values.kerberospipeline.sprite.enabled }}"
# Dominant colors
- 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 }}
# Skip regeneration when the object first appears within this many seconds
- name: THUMBNAIL_REGENERATION_MIN_SECONDS
{{- $regeneration := .Values.kerberospipeline.analysis.thumbnailRegeneration }}
value: {{ ternary $regeneration.minSeconds 3 (hasKey $regeneration "minSeconds") | 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 }}
@@ -1,4 +1,4 @@
{{- if or (eq .Values.mode "all") (eq .Values.mode "pipeline") -}}
{{- if and (or (eq .Values.mode "all") (eq .Values.mode "pipeline")) (eq .Values.kerberospipeline.dominantColor.enabled true) -}}
apiVersion: apps/v1
kind: Deployment
metadata:
@@ -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.
@@ -1,76 +0,0 @@
# If .Values.kerberoshub.forwarder.enabled is set to true
{{- if and (eq .Values.mode "all") .Values.kerberoshub.forwarder.enabled }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: vault-forwarder
namespace: {{ .Release.Namespace }}
spec:
replicas: 1
selector:
matchLabels:
app: vault-forwarder
minReadySeconds: 10
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
template:
metadata:
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configMaps/configmap-mongodb.yaml") . | sha256sum }}
labels:
app: vault-forwarder
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- $mongodbTLS := .Values.mongodb.tls }}
{{- if or .Values.kerberoshub.forwarder.volumes (and $mongodbTLS.enabled $mongodbTLS.existingSecret) }}
volumes:
{{- with .Values.kerberoshub.forwarder.volumes }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- if and $mongodbTLS.enabled $mongodbTLS.existingSecret }}
{{- include "hub.mongodb.tlsVolume" . | nindent 8 }}
{{- end }}
{{- end }}
{{- with .Values.kerberoshub.forwarder.topologySpreadConstraints }}
topologySpreadConstraints:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: vault-forwarder
image: "{{ .Values.global.imageRegistry }}{{ .Values.kerberoshub.forwarder.repository }}:{{ .Values.kerberoshub.forwarder.tag }}"
imagePullPolicy: {{ .Values.kerberoshub.forwarder.pullPolicy }}
resources:
requests:
memory: 10Mi
cpu: 10m
{{- if or .Values.kerberoshub.forwarder.volumeMounts (and $mongodbTLS.enabled $mongodbTLS.existingSecret) }}
volumeMounts:
{{- with .Values.kerberoshub.forwarder.volumeMounts }}
{{- toYaml . | nindent 12 }}
{{- end }}
{{- if and $mongodbTLS.enabled $mongodbTLS.existingSecret }}
{{- include "hub.mongodb.tlsVolumeMount" . | nindent 12 }}
{{- end }}
{{- end }}
envFrom:
- configMapRef:
name: mongodb-config
env:
{{- include "hub.mongodb.appUriEnv" (dict "root" $ "app" "vault-forwarder") | nindent 10 }}
- name: ITERATION_SPEED
value: "5"
- name: BUFFER_TIME
value: "3"
- name: MQTT_URI
value: "{{ .Values.mqtt.host }}"
- name: MQTT_USERNAME
value: "{{ .Values.mqtt.username }}"
- name: MQTT_PASSWORD
value: "{{ .Values.mqtt.password }}"
{{- end }}
@@ -1,78 +0,0 @@
{{- if and (eq .Values.mode "all") .Values.kerberoshub.proxy.enabled -}}
apiVersion: apps/v1
kind: Deployment
metadata:
name: vault-proxy
namespace: {{ .Release.Namespace }}
spec:
replicas: {{ .Values.kerberoshub.proxy.replicas }}
selector:
matchLabels:
app: vault-proxy
minReadySeconds: 10
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
template:
metadata:
labels:
app: vault-proxy
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.kerberoshub.proxy.volumes }}
volumes:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.kerberoshub.proxy.topologySpreadConstraints }}
topologySpreadConstraints:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: vault-proxy
image: "{{ .Values.global.imageRegistry }}{{ .Values.kerberoshub.proxy.repository }}:{{ .Values.kerberoshub.proxy.tag }}"
imagePullPolicy: {{ .Values.kerberoshub.proxy.pullPolicy }}
{{- with .Values.kerberoshub.proxy.volumeMounts }}
volumeMounts:
{{- toYaml . | nindent 12 }}
{{- end }}
ports:
- containerPort: 8080
name: http
env:
- name: LOG_LEVEL
value: "{{ .Values.kerberoshub.proxy.logLevel }}"
# Kerberos Hub API
- name: KERBEROS_API_CHECK_SUBSCRIPTION
value: "{{ .Values.kerberoshub.api.schema }}://{{ .Values.kerberoshub.api.url }}/user/has-subscription"
# Kerberos Vault
- name: KSTORAGE_URI
value: "{{ .Values.kerberosvault.uri }}/storage"
- name: KSTORAGE_ACCESSKEY
value: "{{ .Values.kerberosvault.accesskey }}"
- name: KSTORAGE_SECRET
value: "{{ .Values.kerberosvault.secretkey }}"
- name: KSTORAGE_PROVIDER
value: "{{ .Values.kerberosvault.provider }}"
---
apiVersion: v1
kind: Service
metadata:
name: vault-proxy-svc
namespace: {{ .Release.Namespace }}
labels:
app: vault-proxy-svc
spec:
type: LoadBalancer
ports:
- protocol: TCP
port: 80
targetPort: 8080
name: http
selector:
app: vault-proxy
{{- end }}
@@ -0,0 +1,65 @@
{{- /*
admin images must come from a hardened release: they carry the signed
image-hardening attestation (result "passed") that the UUG.AI release workflow
adds after all hardening gates pass, for uug-ai/admin.
The signature itself is checked by admin-signed-images.yaml.
A namespaced Policy, so it applies only to the release namespace.
Rendered only when imageHardening.enabled is true and Kyverno is installed.
*/ -}}
{{- if and .Values.imageHardening.enabled (.Capabilities.APIVersions.Has "kyverno.io/v1") }}
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: admin-hardened-images
annotations:
policies.kyverno.io/title: admin images must come from a hardened UUG.AI release
policies.kyverno.io/category: Image hardening
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Pod
# Kyverno defaults apply: background scans of running pods, attestation required.
# Only settings whose Kyverno default would change behaviour are set below.
spec:
# Audit: report images without a hardened release, don't block. Enforce: reject the pod.
validationFailureAction: {{ .Values.imageHardening.validationFailureAction }}
# Kyverno's default (Fail) would stop admin pods starting while Kyverno is down.
failurePolicy: Ignore
# Verifying against GHCR and Rekor can exceed the 10s default.
webhookTimeoutSeconds: 30
rules:
- name: verify-admin-hardened
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/uug-ai/admin:*"
- "ghcr.io/uug-ai/admin@*"
# Kyverno's defaults (true) would rewrite pod images to digests and
# report every tag-based image; enable together with Enforce.
mutateDigest: false
verifyDigest: false
attestations:
- type: https://github.com/uug-ai/workflows/attestations/image-hardening/v1
attestors:
- entries:
- keyless:
issuer: https://token.actions.githubusercontent.com
# The UUG.AI release workflow, at a branch/tag ref or a commit.
subjectRegExp: '^https://github\.com/uug-ai/workflows/\.github/workflows/release-create\.yml@(refs/.+|[0-9a-f]{40})$'
additionalExtensions:
githubWorkflowRepository: uug-ai/admin
rekor:
url: https://rekor.sigstore.dev
# Fields of the attestation's predicate. {{`{{ ... }}`}} is Kyverno
# syntax, escaped so Helm leaves it alone.
conditions:
- all:
- key: "{{`{{ result }}`}}"
operator: Equals
value: passed
- key: "{{`{{ repository }}`}}"
operator: Equals
value: uug-ai/admin
{{- end }}
@@ -0,0 +1,53 @@
{{- /*
admin images must be signed by UUG.AI: a keyless cosign signature issued by
GitHub Actions, made by the UUG.AI release workflow, for uug-ai/admin.
A namespaced Policy, so it applies only to the release namespace.
Rendered only when imageVerification.enabled is true and Kyverno is installed.
*/ -}}
{{- if and .Values.imageVerification.enabled (.Capabilities.APIVersions.Has "kyverno.io/v1") }}
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: admin-signed-images
annotations:
policies.kyverno.io/title: admin images must be signed by UUG.AI
policies.kyverno.io/category: Image signing
policies.kyverno.io/severity: high
policies.kyverno.io/subject: Pod
# Kyverno defaults apply: background scans of running pods, signature required.
# Only settings whose Kyverno default would change behaviour are set below.
spec:
# Audit: report unsigned images, don't block. Enforce: reject the pod.
validationFailureAction: {{ .Values.imageVerification.validationFailureAction }}
# Kyverno's default (Fail) would stop admin pods starting while Kyverno is down.
failurePolicy: Ignore
# Verifying against GHCR and Rekor can exceed the 10s default.
webhookTimeoutSeconds: 30
rules:
- name: verify-admin
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/uug-ai/admin:*"
- "ghcr.io/uug-ai/admin@*"
# Kyverno's defaults (true) would rewrite pod images to digests and
# report every tag-based image; enable together with Enforce.
mutateDigest: false
verifyDigest: false
attestors:
- entries:
- keyless:
issuer: https://token.actions.githubusercontent.com
# The UUG.AI release workflow, at a branch/tag ref or a commit.
subjectRegExp: '^https://github\.com/uug-ai/workflows/\.github/workflows/release-create\.yml@(refs/.+|[0-9a-f]{40})$'
additionalExtensions:
# That workflow signs for every UUG.AI repository; this
# binds the signature to admin's own repository.
githubWorkflowRepository: uug-ai/admin
rekor:
url: https://rekor.sigstore.dev
{{- end }}
@@ -0,0 +1,65 @@
{{- /*
hub-api images must come from a hardened release: they carry the signed
image-hardening attestation (result "passed") that the UUG.AI release workflow
adds after all hardening gates pass, for uug-ai/hub-api.
The signature itself is checked by hub-api-signed-images.yaml.
A namespaced Policy, so it applies only to the release namespace.
Rendered only when imageHardening.enabled is true and Kyverno is installed.
*/ -}}
{{- if and .Values.imageHardening.enabled (.Capabilities.APIVersions.Has "kyverno.io/v1") }}
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: hub-api-hardened-images
annotations:
policies.kyverno.io/title: hub-api images must come from a hardened UUG.AI release
policies.kyverno.io/category: Image hardening
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Pod
# Kyverno defaults apply: background scans of running pods, attestation required.
# Only settings whose Kyverno default would change behaviour are set below.
spec:
# Audit: report images without a hardened release, don't block. Enforce: reject the pod.
validationFailureAction: {{ .Values.imageHardening.validationFailureAction }}
# Kyverno's default (Fail) would stop hub-api pods starting while Kyverno is down.
failurePolicy: Ignore
# Verifying against GHCR and Rekor can exceed the 10s default.
webhookTimeoutSeconds: 30
rules:
- name: verify-hub-api-hardened
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/uug-ai/hub-api:*"
- "ghcr.io/uug-ai/hub-api@*"
# Kyverno's defaults (true) would rewrite pod images to digests and
# report every tag-based image; enable together with Enforce.
mutateDigest: false
verifyDigest: false
attestations:
- type: https://github.com/uug-ai/workflows/attestations/image-hardening/v1
attestors:
- entries:
- keyless:
issuer: https://token.actions.githubusercontent.com
# The UUG.AI release workflow, at a branch/tag ref or a commit.
subjectRegExp: '^https://github\.com/uug-ai/workflows/\.github/workflows/release-create\.yml@(refs/.+|[0-9a-f]{40})$'
additionalExtensions:
githubWorkflowRepository: uug-ai/hub-api
rekor:
url: https://rekor.sigstore.dev
# Fields of the attestation's predicate. {{`{{ ... }}`}} is Kyverno
# syntax, escaped so Helm leaves it alone.
conditions:
- all:
- key: "{{`{{ result }}`}}"
operator: Equals
value: passed
- key: "{{`{{ repository }}`}}"
operator: Equals
value: uug-ai/hub-api
{{- end }}
@@ -0,0 +1,53 @@
{{- /*
hub-api images must be signed by UUG.AI: a keyless cosign signature issued by
GitHub Actions, made by the UUG.AI release workflow, for uug-ai/hub-api.
A namespaced Policy, so it applies only to the release namespace.
Rendered only when imageVerification.enabled is true and Kyverno is installed.
*/ -}}
{{- if and .Values.imageVerification.enabled (.Capabilities.APIVersions.Has "kyverno.io/v1") }}
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: hub-api-signed-images
annotations:
policies.kyverno.io/title: hub-api images must be signed by UUG.AI
policies.kyverno.io/category: Image signing
policies.kyverno.io/severity: high
policies.kyverno.io/subject: Pod
# Kyverno defaults apply: background scans of running pods, signature required.
# Only settings whose Kyverno default would change behaviour are set below.
spec:
# Audit: report unsigned images, don't block. Enforce: reject the pod.
validationFailureAction: {{ .Values.imageVerification.validationFailureAction }}
# Kyverno's default (Fail) would stop hub-api pods starting while Kyverno is down.
failurePolicy: Ignore
# Verifying against GHCR and Rekor can exceed the 10s default.
webhookTimeoutSeconds: 30
rules:
- name: verify-hub-api
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/uug-ai/hub-api:*"
- "ghcr.io/uug-ai/hub-api@*"
# Kyverno's defaults (true) would rewrite pod images to digests and
# report every tag-based image; enable together with Enforce.
mutateDigest: false
verifyDigest: false
attestors:
- entries:
- keyless:
issuer: https://token.actions.githubusercontent.com
# The UUG.AI release workflow, at a branch/tag ref or a commit.
subjectRegExp: '^https://github\.com/uug-ai/workflows/\.github/workflows/release-create\.yml@(refs/.+|[0-9a-f]{40})$'
additionalExtensions:
# That workflow signs for every UUG.AI repository; this
# binds the signature to hub-api's own repository.
githubWorkflowRepository: uug-ai/hub-api
rekor:
url: https://rekor.sigstore.dev
{{- end }}
@@ -0,0 +1,65 @@
{{- /*
hub-audit images must come from a hardened release: they carry the signed
image-hardening attestation (result "passed") that the UUG.AI release workflow
adds after all hardening gates pass, for uug-ai/hub-audit.
The signature itself is checked by hub-audit-signed-images.yaml.
A namespaced Policy, so it applies only to the release namespace.
Rendered only when imageHardening.enabled is true and Kyverno is installed.
*/ -}}
{{- if and .Values.imageHardening.enabled (.Capabilities.APIVersions.Has "kyverno.io/v1") }}
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: hub-audit-hardened-images
annotations:
policies.kyverno.io/title: hub-audit images must come from a hardened UUG.AI release
policies.kyverno.io/category: Image hardening
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Pod
# Kyverno defaults apply: background scans of running pods, attestation required.
# Only settings whose Kyverno default would change behaviour are set below.
spec:
# Audit: report images without a hardened release, don't block. Enforce: reject the pod.
validationFailureAction: {{ .Values.imageHardening.validationFailureAction }}
# Kyverno's default (Fail) would stop hub-audit pods starting while Kyverno is down.
failurePolicy: Ignore
# Verifying against GHCR and Rekor can exceed the 10s default.
webhookTimeoutSeconds: 30
rules:
- name: verify-hub-audit-hardened
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/uug-ai/hub-audit:*"
- "ghcr.io/uug-ai/hub-audit@*"
# Kyverno's defaults (true) would rewrite pod images to digests and
# report every tag-based image; enable together with Enforce.
mutateDigest: false
verifyDigest: false
attestations:
- type: https://github.com/uug-ai/workflows/attestations/image-hardening/v1
attestors:
- entries:
- keyless:
issuer: https://token.actions.githubusercontent.com
# The UUG.AI release workflow, at a branch/tag ref or a commit.
subjectRegExp: '^https://github\.com/uug-ai/workflows/\.github/workflows/release-create\.yml@(refs/.+|[0-9a-f]{40})$'
additionalExtensions:
githubWorkflowRepository: uug-ai/hub-audit
rekor:
url: https://rekor.sigstore.dev
# Fields of the attestation's predicate. {{`{{ ... }}`}} is Kyverno
# syntax, escaped so Helm leaves it alone.
conditions:
- all:
- key: "{{`{{ result }}`}}"
operator: Equals
value: passed
- key: "{{`{{ repository }}`}}"
operator: Equals
value: uug-ai/hub-audit
{{- end }}
@@ -0,0 +1,53 @@
{{- /*
hub-audit images must be signed by UUG.AI: a keyless cosign signature issued by
GitHub Actions, made by the UUG.AI release workflow, for uug-ai/hub-audit.
A namespaced Policy, so it applies only to the release namespace.
Rendered only when imageVerification.enabled is true and Kyverno is installed.
*/ -}}
{{- if and .Values.imageVerification.enabled (.Capabilities.APIVersions.Has "kyverno.io/v1") }}
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: hub-audit-signed-images
annotations:
policies.kyverno.io/title: hub-audit images must be signed by UUG.AI
policies.kyverno.io/category: Image signing
policies.kyverno.io/severity: high
policies.kyverno.io/subject: Pod
# Kyverno defaults apply: background scans of running pods, signature required.
# Only settings whose Kyverno default would change behaviour are set below.
spec:
# Audit: report unsigned images, don't block. Enforce: reject the pod.
validationFailureAction: {{ .Values.imageVerification.validationFailureAction }}
# Kyverno's default (Fail) would stop hub-audit pods starting while Kyverno is down.
failurePolicy: Ignore
# Verifying against GHCR and Rekor can exceed the 10s default.
webhookTimeoutSeconds: 30
rules:
- name: verify-hub-audit
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/uug-ai/hub-audit:*"
- "ghcr.io/uug-ai/hub-audit@*"
# Kyverno's defaults (true) would rewrite pod images to digests and
# report every tag-based image; enable together with Enforce.
mutateDigest: false
verifyDigest: false
attestors:
- entries:
- keyless:
issuer: https://token.actions.githubusercontent.com
# The UUG.AI release workflow, at a branch/tag ref or a commit.
subjectRegExp: '^https://github\.com/uug-ai/workflows/\.github/workflows/release-create\.yml@(refs/.+|[0-9a-f]{40})$'
additionalExtensions:
# That workflow signs for every UUG.AI repository; this
# binds the signature to hub-audit's own repository.
githubWorkflowRepository: uug-ai/hub-audit
rekor:
url: https://rekor.sigstore.dev
{{- end }}
@@ -0,0 +1,65 @@
{{- /*
hub-cleanup images must come from a hardened release: they carry the signed
image-hardening attestation (result "passed") that the UUG.AI release workflow
adds after all hardening gates pass, for uug-ai/hub-cleanup.
The signature itself is checked by hub-cleanup-signed-images.yaml.
A namespaced Policy, so it applies only to the release namespace.
Rendered only when imageHardening.enabled is true and Kyverno is installed.
*/ -}}
{{- if and .Values.imageHardening.enabled (.Capabilities.APIVersions.Has "kyverno.io/v1") }}
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: hub-cleanup-hardened-images
annotations:
policies.kyverno.io/title: hub-cleanup images must come from a hardened UUG.AI release
policies.kyverno.io/category: Image hardening
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Pod
# Kyverno defaults apply: background scans of running pods, attestation required.
# Only settings whose Kyverno default would change behaviour are set below.
spec:
# Audit: report images without a hardened release, don't block. Enforce: reject the pod.
validationFailureAction: {{ .Values.imageHardening.validationFailureAction }}
# Kyverno's default (Fail) would stop hub-cleanup pods starting while Kyverno is down.
failurePolicy: Ignore
# Verifying against GHCR and Rekor can exceed the 10s default.
webhookTimeoutSeconds: 30
rules:
- name: verify-hub-cleanup-hardened
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/uug-ai/hub-cleanup:*"
- "ghcr.io/uug-ai/hub-cleanup@*"
# Kyverno's defaults (true) would rewrite pod images to digests and
# report every tag-based image; enable together with Enforce.
mutateDigest: false
verifyDigest: false
attestations:
- type: https://github.com/uug-ai/workflows/attestations/image-hardening/v1
attestors:
- entries:
- keyless:
issuer: https://token.actions.githubusercontent.com
# The UUG.AI release workflow, at a branch/tag ref or a commit.
subjectRegExp: '^https://github\.com/uug-ai/workflows/\.github/workflows/release-create\.yml@(refs/.+|[0-9a-f]{40})$'
additionalExtensions:
githubWorkflowRepository: uug-ai/hub-cleanup
rekor:
url: https://rekor.sigstore.dev
# Fields of the attestation's predicate. {{`{{ ... }}`}} is Kyverno
# syntax, escaped so Helm leaves it alone.
conditions:
- all:
- key: "{{`{{ result }}`}}"
operator: Equals
value: passed
- key: "{{`{{ repository }}`}}"
operator: Equals
value: uug-ai/hub-cleanup
{{- end }}
@@ -0,0 +1,53 @@
{{- /*
hub-cleanup images must be signed by UUG.AI: a keyless cosign signature issued by
GitHub Actions, made by the UUG.AI release workflow, for uug-ai/hub-cleanup.
A namespaced Policy, so it applies only to the release namespace.
Rendered only when imageVerification.enabled is true and Kyverno is installed.
*/ -}}
{{- if and .Values.imageVerification.enabled (.Capabilities.APIVersions.Has "kyverno.io/v1") }}
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: hub-cleanup-signed-images
annotations:
policies.kyverno.io/title: hub-cleanup images must be signed by UUG.AI
policies.kyverno.io/category: Image signing
policies.kyverno.io/severity: high
policies.kyverno.io/subject: Pod
# Kyverno defaults apply: background scans of running pods, signature required.
# Only settings whose Kyverno default would change behaviour are set below.
spec:
# Audit: report unsigned images, don't block. Enforce: reject the pod.
validationFailureAction: {{ .Values.imageVerification.validationFailureAction }}
# Kyverno's default (Fail) would stop hub-cleanup pods starting while Kyverno is down.
failurePolicy: Ignore
# Verifying against GHCR and Rekor can exceed the 10s default.
webhookTimeoutSeconds: 30
rules:
- name: verify-hub-cleanup
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/uug-ai/hub-cleanup:*"
- "ghcr.io/uug-ai/hub-cleanup@*"
# Kyverno's defaults (true) would rewrite pod images to digests and
# report every tag-based image; enable together with Enforce.
mutateDigest: false
verifyDigest: false
attestors:
- entries:
- keyless:
issuer: https://token.actions.githubusercontent.com
# The UUG.AI release workflow, at a branch/tag ref or a commit.
subjectRegExp: '^https://github\.com/uug-ai/workflows/\.github/workflows/release-create\.yml@(refs/.+|[0-9a-f]{40})$'
additionalExtensions:
# That workflow signs for every UUG.AI repository; this
# binds the signature to hub-cleanup's own repository.
githubWorkflowRepository: uug-ai/hub-cleanup
rekor:
url: https://rekor.sigstore.dev
{{- end }}
@@ -0,0 +1,65 @@
{{- /*
hub-frontend images must come from a hardened release: they carry the signed
image-hardening attestation (result "passed") that the UUG.AI release workflow
adds after all hardening gates pass, for uug-ai/hub-frontend.
The signature itself is checked by hub-frontend-signed-images.yaml.
A namespaced Policy, so it applies only to the release namespace.
Rendered only when imageHardening.enabled is true and Kyverno is installed.
*/ -}}
{{- if and .Values.imageHardening.enabled (.Capabilities.APIVersions.Has "kyverno.io/v1") }}
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: hub-frontend-hardened-images
annotations:
policies.kyverno.io/title: hub-frontend images must come from a hardened UUG.AI release
policies.kyverno.io/category: Image hardening
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Pod
# Kyverno defaults apply: background scans of running pods, attestation required.
# Only settings whose Kyverno default would change behaviour are set below.
spec:
# Audit: report images without a hardened release, don't block. Enforce: reject the pod.
validationFailureAction: {{ .Values.imageHardening.validationFailureAction }}
# Kyverno's default (Fail) would stop hub-frontend pods starting while Kyverno is down.
failurePolicy: Ignore
# Verifying against GHCR and Rekor can exceed the 10s default.
webhookTimeoutSeconds: 30
rules:
- name: verify-hub-frontend-hardened
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/uug-ai/hub-frontend:*"
- "ghcr.io/uug-ai/hub-frontend@*"
# Kyverno's defaults (true) would rewrite pod images to digests and
# report every tag-based image; enable together with Enforce.
mutateDigest: false
verifyDigest: false
attestations:
- type: https://github.com/uug-ai/workflows/attestations/image-hardening/v1
attestors:
- entries:
- keyless:
issuer: https://token.actions.githubusercontent.com
# The UUG.AI release workflow, at a branch/tag ref or a commit.
subjectRegExp: '^https://github\.com/uug-ai/workflows/\.github/workflows/release-create\.yml@(refs/.+|[0-9a-f]{40})$'
additionalExtensions:
githubWorkflowRepository: uug-ai/hub-frontend
rekor:
url: https://rekor.sigstore.dev
# Fields of the attestation's predicate. {{`{{ ... }}`}} is Kyverno
# syntax, escaped so Helm leaves it alone.
conditions:
- all:
- key: "{{`{{ result }}`}}"
operator: Equals
value: passed
- key: "{{`{{ repository }}`}}"
operator: Equals
value: uug-ai/hub-frontend
{{- end }}
@@ -0,0 +1,53 @@
{{- /*
hub-frontend images must be signed by UUG.AI: a keyless cosign signature issued by
GitHub Actions, made by the UUG.AI release workflow, for uug-ai/hub-frontend.
A namespaced Policy, so it applies only to the release namespace.
Rendered only when imageVerification.enabled is true and Kyverno is installed.
*/ -}}
{{- if and .Values.imageVerification.enabled (.Capabilities.APIVersions.Has "kyverno.io/v1") }}
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: hub-frontend-signed-images
annotations:
policies.kyverno.io/title: hub-frontend images must be signed by UUG.AI
policies.kyverno.io/category: Image signing
policies.kyverno.io/severity: high
policies.kyverno.io/subject: Pod
# Kyverno defaults apply: background scans of running pods, signature required.
# Only settings whose Kyverno default would change behaviour are set below.
spec:
# Audit: report unsigned images, don't block. Enforce: reject the pod.
validationFailureAction: {{ .Values.imageVerification.validationFailureAction }}
# Kyverno's default (Fail) would stop hub-frontend pods starting while Kyverno is down.
failurePolicy: Ignore
# Verifying against GHCR and Rekor can exceed the 10s default.
webhookTimeoutSeconds: 30
rules:
- name: verify-hub-frontend
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/uug-ai/hub-frontend:*"
- "ghcr.io/uug-ai/hub-frontend@*"
# Kyverno's defaults (true) would rewrite pod images to digests and
# report every tag-based image; enable together with Enforce.
mutateDigest: false
verifyDigest: false
attestors:
- entries:
- keyless:
issuer: https://token.actions.githubusercontent.com
# The UUG.AI release workflow, at a branch/tag ref or a commit.
subjectRegExp: '^https://github\.com/uug-ai/workflows/\.github/workflows/release-create\.yml@(refs/.+|[0-9a-f]{40})$'
additionalExtensions:
# That workflow signs for every UUG.AI repository; this
# binds the signature to hub-frontend's own repository.
githubWorkflowRepository: uug-ai/hub-frontend
rekor:
url: https://rekor.sigstore.dev
{{- end }}
@@ -0,0 +1,65 @@
{{- /*
hub-loitering images must come from a hardened release: they carry the signed
image-hardening attestation (result "passed") that the UUG.AI release workflow
adds after all hardening gates pass, for uug-ai/hub-loitering.
The signature itself is checked by hub-loitering-signed-images.yaml.
A namespaced Policy, so it applies only to the release namespace.
Rendered only when imageHardening.enabled is true and Kyverno is installed.
*/ -}}
{{- if and .Values.imageHardening.enabled (.Capabilities.APIVersions.Has "kyverno.io/v1") }}
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: hub-loitering-hardened-images
annotations:
policies.kyverno.io/title: hub-loitering images must come from a hardened UUG.AI release
policies.kyverno.io/category: Image hardening
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Pod
# Kyverno defaults apply: background scans of running pods, attestation required.
# Only settings whose Kyverno default would change behaviour are set below.
spec:
# Audit: report images without a hardened release, don't block. Enforce: reject the pod.
validationFailureAction: {{ .Values.imageHardening.validationFailureAction }}
# Kyverno's default (Fail) would stop hub-loitering pods starting while Kyverno is down.
failurePolicy: Ignore
# Verifying against GHCR and Rekor can exceed the 10s default.
webhookTimeoutSeconds: 30
rules:
- name: verify-hub-loitering-hardened
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/uug-ai/hub-loitering:*"
- "ghcr.io/uug-ai/hub-loitering@*"
# Kyverno's defaults (true) would rewrite pod images to digests and
# report every tag-based image; enable together with Enforce.
mutateDigest: false
verifyDigest: false
attestations:
- type: https://github.com/uug-ai/workflows/attestations/image-hardening/v1
attestors:
- entries:
- keyless:
issuer: https://token.actions.githubusercontent.com
# The UUG.AI release workflow, at a branch/tag ref or a commit.
subjectRegExp: '^https://github\.com/uug-ai/workflows/\.github/workflows/release-create\.yml@(refs/.+|[0-9a-f]{40})$'
additionalExtensions:
githubWorkflowRepository: uug-ai/hub-loitering
rekor:
url: https://rekor.sigstore.dev
# Fields of the attestation's predicate. {{`{{ ... }}`}} is Kyverno
# syntax, escaped so Helm leaves it alone.
conditions:
- all:
- key: "{{`{{ result }}`}}"
operator: Equals
value: passed
- key: "{{`{{ repository }}`}}"
operator: Equals
value: uug-ai/hub-loitering
{{- end }}
@@ -0,0 +1,53 @@
{{- /*
hub-loitering images must be signed by UUG.AI: a keyless cosign signature issued by
GitHub Actions, made by the UUG.AI release workflow, for uug-ai/hub-loitering.
A namespaced Policy, so it applies only to the release namespace.
Rendered only when imageVerification.enabled is true and Kyverno is installed.
*/ -}}
{{- if and .Values.imageVerification.enabled (.Capabilities.APIVersions.Has "kyverno.io/v1") }}
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: hub-loitering-signed-images
annotations:
policies.kyverno.io/title: hub-loitering images must be signed by UUG.AI
policies.kyverno.io/category: Image signing
policies.kyverno.io/severity: high
policies.kyverno.io/subject: Pod
# Kyverno defaults apply: background scans of running pods, signature required.
# Only settings whose Kyverno default would change behaviour are set below.
spec:
# Audit: report unsigned images, don't block. Enforce: reject the pod.
validationFailureAction: {{ .Values.imageVerification.validationFailureAction }}
# Kyverno's default (Fail) would stop hub-loitering pods starting while Kyverno is down.
failurePolicy: Ignore
# Verifying against GHCR and Rekor can exceed the 10s default.
webhookTimeoutSeconds: 30
rules:
- name: verify-hub-loitering
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/uug-ai/hub-loitering:*"
- "ghcr.io/uug-ai/hub-loitering@*"
# Kyverno's defaults (true) would rewrite pod images to digests and
# report every tag-based image; enable together with Enforce.
mutateDigest: false
verifyDigest: false
attestors:
- entries:
- keyless:
issuer: https://token.actions.githubusercontent.com
# The UUG.AI release workflow, at a branch/tag ref or a commit.
subjectRegExp: '^https://github\.com/uug-ai/workflows/\.github/workflows/release-create\.yml@(refs/.+|[0-9a-f]{40})$'
additionalExtensions:
# That workflow signs for every UUG.AI repository; this
# binds the signature to hub-loitering's own repository.
githubWorkflowRepository: uug-ai/hub-loitering
rekor:
url: https://rekor.sigstore.dev
{{- end }}
@@ -0,0 +1,65 @@
{{- /*
hub-monitor-device images must come from a hardened release: they carry the signed
image-hardening attestation (result "passed") that the UUG.AI release workflow
adds after all hardening gates pass, for uug-ai/hub-monitor-device.
The signature itself is checked by hub-monitor-device-signed-images.yaml.
A namespaced Policy, so it applies only to the release namespace.
Rendered only when imageHardening.enabled is true and Kyverno is installed.
*/ -}}
{{- if and .Values.imageHardening.enabled (.Capabilities.APIVersions.Has "kyverno.io/v1") }}
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: hub-monitor-device-hardened-images
annotations:
policies.kyverno.io/title: hub-monitor-device images must come from a hardened UUG.AI release
policies.kyverno.io/category: Image hardening
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Pod
# Kyverno defaults apply: background scans of running pods, attestation required.
# Only settings whose Kyverno default would change behaviour are set below.
spec:
# Audit: report images without a hardened release, don't block. Enforce: reject the pod.
validationFailureAction: {{ .Values.imageHardening.validationFailureAction }}
# Kyverno's default (Fail) would stop hub-monitor-device pods starting while Kyverno is down.
failurePolicy: Ignore
# Verifying against GHCR and Rekor can exceed the 10s default.
webhookTimeoutSeconds: 30
rules:
- name: verify-hub-monitor-device-hardened
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/uug-ai/hub-monitor-device:*"
- "ghcr.io/uug-ai/hub-monitor-device@*"
# Kyverno's defaults (true) would rewrite pod images to digests and
# report every tag-based image; enable together with Enforce.
mutateDigest: false
verifyDigest: false
attestations:
- type: https://github.com/uug-ai/workflows/attestations/image-hardening/v1
attestors:
- entries:
- keyless:
issuer: https://token.actions.githubusercontent.com
# The UUG.AI release workflow, at a branch/tag ref or a commit.
subjectRegExp: '^https://github\.com/uug-ai/workflows/\.github/workflows/release-create\.yml@(refs/.+|[0-9a-f]{40})$'
additionalExtensions:
githubWorkflowRepository: uug-ai/hub-monitor-device
rekor:
url: https://rekor.sigstore.dev
# Fields of the attestation's predicate. {{`{{ ... }}`}} is Kyverno
# syntax, escaped so Helm leaves it alone.
conditions:
- all:
- key: "{{`{{ result }}`}}"
operator: Equals
value: passed
- key: "{{`{{ repository }}`}}"
operator: Equals
value: uug-ai/hub-monitor-device
{{- end }}
@@ -0,0 +1,53 @@
{{- /*
hub-monitor-device images must be signed by UUG.AI: a keyless cosign signature issued by
GitHub Actions, made by the UUG.AI release workflow, for uug-ai/hub-monitor-device.
A namespaced Policy, so it applies only to the release namespace.
Rendered only when imageVerification.enabled is true and Kyverno is installed.
*/ -}}
{{- if and .Values.imageVerification.enabled (.Capabilities.APIVersions.Has "kyverno.io/v1") }}
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: hub-monitor-device-signed-images
annotations:
policies.kyverno.io/title: hub-monitor-device images must be signed by UUG.AI
policies.kyverno.io/category: Image signing
policies.kyverno.io/severity: high
policies.kyverno.io/subject: Pod
# Kyverno defaults apply: background scans of running pods, signature required.
# Only settings whose Kyverno default would change behaviour are set below.
spec:
# Audit: report unsigned images, don't block. Enforce: reject the pod.
validationFailureAction: {{ .Values.imageVerification.validationFailureAction }}
# Kyverno's default (Fail) would stop hub-monitor-device pods starting while Kyverno is down.
failurePolicy: Ignore
# Verifying against GHCR and Rekor can exceed the 10s default.
webhookTimeoutSeconds: 30
rules:
- name: verify-hub-monitor-device
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/uug-ai/hub-monitor-device:*"
- "ghcr.io/uug-ai/hub-monitor-device@*"
# Kyverno's defaults (true) would rewrite pod images to digests and
# report every tag-based image; enable together with Enforce.
mutateDigest: false
verifyDigest: false
attestors:
- entries:
- keyless:
issuer: https://token.actions.githubusercontent.com
# The UUG.AI release workflow, at a branch/tag ref or a commit.
subjectRegExp: '^https://github\.com/uug-ai/workflows/\.github/workflows/release-create\.yml@(refs/.+|[0-9a-f]{40})$'
additionalExtensions:
# That workflow signs for every UUG.AI repository; this
# binds the signature to hub-monitor-device's own repository.
githubWorkflowRepository: uug-ai/hub-monitor-device
rekor:
url: https://rekor.sigstore.dev
{{- end }}
@@ -0,0 +1,68 @@
{{- /*
hub-objecttracking images must come from a hardened release: they carry the signed
image-hardening attestation (result "passed") that the UUG.AI release workflow
adds after all hardening gates pass, for uug-ai/hub-objecttracking.
The signature itself is checked by hub-objecttracking-signed-images.yaml.
A namespaced Policy, so it applies only to the release namespace.
Rendered only when imageHardening.enabled is true and Kyverno is installed.
*/ -}}
{{- if and .Values.imageHardening.enabled (.Capabilities.APIVersions.Has "kyverno.io/v1") }}
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: hub-objecttracking-hardened-images
annotations:
policies.kyverno.io/title: hub-objecttracking images must come from a hardened UUG.AI release
policies.kyverno.io/category: Image hardening
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Pod
# Kyverno defaults apply: background scans of running pods, attestation required.
# Only settings whose Kyverno default would change behaviour are set below.
spec:
# The standard image is hardened (from v1.0.11), but its `-gpu` image is not:
# it is built outside the shared release workflow. This policy stays in Audit,
# even when imageHardening.validationFailureAction is Enforce. Once the `-gpu`
# image is hardened, use the same line as hub-api-hardened-images.yaml.
validationFailureAction: Audit
# Kyverno's default (Fail) would stop hub-objecttracking pods starting while Kyverno is down.
failurePolicy: Ignore
# Verifying against GHCR and Rekor can exceed the 10s default.
webhookTimeoutSeconds: 30
rules:
- name: verify-hub-objecttracking-hardened
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/uug-ai/hub-objecttracking:*"
- "ghcr.io/uug-ai/hub-objecttracking@*"
# Kyverno's defaults (true) would rewrite pod images to digests and
# report every tag-based image; enable together with Enforce.
mutateDigest: false
verifyDigest: false
attestations:
- type: https://github.com/uug-ai/workflows/attestations/image-hardening/v1
attestors:
- entries:
- keyless:
issuer: https://token.actions.githubusercontent.com
# The UUG.AI release workflow, at a branch/tag ref or a commit.
subjectRegExp: '^https://github\.com/uug-ai/workflows/\.github/workflows/release-create\.yml@(refs/.+|[0-9a-f]{40})$'
additionalExtensions:
githubWorkflowRepository: uug-ai/hub-objecttracking
rekor:
url: https://rekor.sigstore.dev
# Fields of the attestation's predicate. {{`{{ ... }}`}} is Kyverno
# syntax, escaped so Helm leaves it alone.
conditions:
- all:
- key: "{{`{{ result }}`}}"
operator: Equals
value: passed
- key: "{{`{{ repository }}`}}"
operator: Equals
value: uug-ai/hub-objecttracking
{{- end }}
@@ -0,0 +1,57 @@
{{- /*
hub-objecttracking images must be signed by UUG.AI: a keyless cosign signature issued by
GitHub Actions, made by the UUG.AI release workflow, for uug-ai/hub-objecttracking.
A namespaced Policy, so it applies only to the release namespace.
Rendered only when imageVerification.enabled is true and Kyverno is installed.
*/ -}}
{{- if and .Values.imageVerification.enabled (.Capabilities.APIVersions.Has "kyverno.io/v1") }}
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: hub-objecttracking-signed-images
annotations:
policies.kyverno.io/title: hub-objecttracking images must be signed by UUG.AI
policies.kyverno.io/category: Image signing
policies.kyverno.io/severity: high
policies.kyverno.io/subject: Pod
# Kyverno defaults apply: background scans of running pods, signature required.
# Only settings whose Kyverno default would change behaviour are set below.
spec:
# hub-objecttracking releases are signed (from v1.0.10), but the GPU image
# (<tag>-gpu, built by the repository's release-gpu-image.yml) is not, and this
# policy matches every hub-objecttracking tag. It therefore stays in Audit even
# when imageVerification.validationFailureAction is Enforce, until the GPU
# image is signed too. Then use the same line as hub-api-signed-images.yaml.
validationFailureAction: Audit
# Kyverno's default (Fail) would stop hub-objecttracking pods starting while Kyverno is down.
failurePolicy: Ignore
# Verifying against GHCR and Rekor can exceed the 10s default.
webhookTimeoutSeconds: 30
rules:
- name: verify-hub-objecttracking
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/uug-ai/hub-objecttracking:*"
- "ghcr.io/uug-ai/hub-objecttracking@*"
# Kyverno's defaults (true) would rewrite pod images to digests and
# report every tag-based image; enable together with Enforce.
mutateDigest: false
verifyDigest: false
attestors:
- entries:
- keyless:
issuer: https://token.actions.githubusercontent.com
# The UUG.AI release workflow, at a branch/tag ref or a commit.
subjectRegExp: '^https://github\.com/uug-ai/workflows/\.github/workflows/release-create\.yml@(refs/.+|[0-9a-f]{40})$'
additionalExtensions:
# That workflow signs for every UUG.AI repository; this
# binds the signature to hub-objecttracking's own repository.
githubWorkflowRepository: uug-ai/hub-objecttracking
rekor:
url: https://rekor.sigstore.dev
{{- end }}
@@ -0,0 +1,65 @@
{{- /*
hub-pipeline-analysis images must come from a hardened release: they carry the signed
image-hardening attestation (result "passed") that the UUG.AI release workflow
adds after all hardening gates pass, for uug-ai/hub-pipeline-analysis.
The signature itself is checked by hub-pipeline-analysis-signed-images.yaml.
A namespaced Policy, so it applies only to the release namespace.
Rendered only when imageHardening.enabled is true and Kyverno is installed.
*/ -}}
{{- if and .Values.imageHardening.enabled (.Capabilities.APIVersions.Has "kyverno.io/v1") }}
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: hub-pipeline-analysis-hardened-images
annotations:
policies.kyverno.io/title: hub-pipeline-analysis images must come from a hardened UUG.AI release
policies.kyverno.io/category: Image hardening
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Pod
# Kyverno defaults apply: background scans of running pods, attestation required.
# Only settings whose Kyverno default would change behaviour are set below.
spec:
# Audit: report images without a hardened release, don't block. Enforce: reject the pod.
validationFailureAction: {{ .Values.imageHardening.validationFailureAction }}
# Kyverno's default (Fail) would stop hub-pipeline-analysis pods starting while Kyverno is down.
failurePolicy: Ignore
# Verifying against GHCR and Rekor can exceed the 10s default.
webhookTimeoutSeconds: 30
rules:
- name: verify-hub-pipeline-analysis-hardened
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/uug-ai/hub-pipeline-analysis:*"
- "ghcr.io/uug-ai/hub-pipeline-analysis@*"
# Kyverno's defaults (true) would rewrite pod images to digests and
# report every tag-based image; enable together with Enforce.
mutateDigest: false
verifyDigest: false
attestations:
- type: https://github.com/uug-ai/workflows/attestations/image-hardening/v1
attestors:
- entries:
- keyless:
issuer: https://token.actions.githubusercontent.com
# The UUG.AI release workflow, at a branch/tag ref or a commit.
subjectRegExp: '^https://github\.com/uug-ai/workflows/\.github/workflows/release-create\.yml@(refs/.+|[0-9a-f]{40})$'
additionalExtensions:
githubWorkflowRepository: uug-ai/hub-pipeline-analysis
rekor:
url: https://rekor.sigstore.dev
# Fields of the attestation's predicate. {{`{{ ... }}`}} is Kyverno
# syntax, escaped so Helm leaves it alone.
conditions:
- all:
- key: "{{`{{ result }}`}}"
operator: Equals
value: passed
- key: "{{`{{ repository }}`}}"
operator: Equals
value: uug-ai/hub-pipeline-analysis
{{- end }}
@@ -0,0 +1,53 @@
{{- /*
hub-pipeline-analysis images must be signed by UUG.AI: a keyless cosign signature issued by
GitHub Actions, made by the UUG.AI release workflow, for uug-ai/hub-pipeline-analysis.
A namespaced Policy, so it applies only to the release namespace.
Rendered only when imageVerification.enabled is true and Kyverno is installed.
*/ -}}
{{- if and .Values.imageVerification.enabled (.Capabilities.APIVersions.Has "kyverno.io/v1") }}
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: hub-pipeline-analysis-signed-images
annotations:
policies.kyverno.io/title: hub-pipeline-analysis images must be signed by UUG.AI
policies.kyverno.io/category: Image signing
policies.kyverno.io/severity: high
policies.kyverno.io/subject: Pod
# Kyverno defaults apply: background scans of running pods, signature required.
# Only settings whose Kyverno default would change behaviour are set below.
spec:
# Audit: report unsigned images, don't block. Enforce: reject the pod.
validationFailureAction: {{ .Values.imageVerification.validationFailureAction }}
# Kyverno's default (Fail) would stop hub-pipeline-analysis pods starting while Kyverno is down.
failurePolicy: Ignore
# Verifying against GHCR and Rekor can exceed the 10s default.
webhookTimeoutSeconds: 30
rules:
- name: verify-hub-pipeline-analysis
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/uug-ai/hub-pipeline-analysis:*"
- "ghcr.io/uug-ai/hub-pipeline-analysis@*"
# Kyverno's defaults (true) would rewrite pod images to digests and
# report every tag-based image; enable together with Enforce.
mutateDigest: false
verifyDigest: false
attestors:
- entries:
- keyless:
issuer: https://token.actions.githubusercontent.com
# The UUG.AI release workflow, at a branch/tag ref or a commit.
subjectRegExp: '^https://github\.com/uug-ai/workflows/\.github/workflows/release-create\.yml@(refs/.+|[0-9a-f]{40})$'
additionalExtensions:
# That workflow signs for every UUG.AI repository; this
# binds the signature to hub-pipeline-analysis's own repository.
githubWorkflowRepository: uug-ai/hub-pipeline-analysis
rekor:
url: https://rekor.sigstore.dev
{{- end }}
@@ -0,0 +1,65 @@
{{- /*
hub-pipeline-counting images must come from a hardened release: they carry the signed
image-hardening attestation (result "passed") that the UUG.AI release workflow
adds after all hardening gates pass, for uug-ai/hub-pipeline-counting.
The signature itself is checked by hub-pipeline-counting-signed-images.yaml.
A namespaced Policy, so it applies only to the release namespace.
Rendered only when imageHardening.enabled is true and Kyverno is installed.
*/ -}}
{{- if and .Values.imageHardening.enabled (.Capabilities.APIVersions.Has "kyverno.io/v1") }}
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: hub-pipeline-counting-hardened-images
annotations:
policies.kyverno.io/title: hub-pipeline-counting images must come from a hardened UUG.AI release
policies.kyverno.io/category: Image hardening
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Pod
# Kyverno defaults apply: background scans of running pods, attestation required.
# Only settings whose Kyverno default would change behaviour are set below.
spec:
# Audit: report images without a hardened release, don't block. Enforce: reject the pod.
validationFailureAction: {{ .Values.imageHardening.validationFailureAction }}
# Kyverno's default (Fail) would stop hub-pipeline-counting pods starting while Kyverno is down.
failurePolicy: Ignore
# Verifying against GHCR and Rekor can exceed the 10s default.
webhookTimeoutSeconds: 30
rules:
- name: verify-hub-pipeline-counting-hardened
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/uug-ai/hub-pipeline-counting:*"
- "ghcr.io/uug-ai/hub-pipeline-counting@*"
# Kyverno's defaults (true) would rewrite pod images to digests and
# report every tag-based image; enable together with Enforce.
mutateDigest: false
verifyDigest: false
attestations:
- type: https://github.com/uug-ai/workflows/attestations/image-hardening/v1
attestors:
- entries:
- keyless:
issuer: https://token.actions.githubusercontent.com
# The UUG.AI release workflow, at a branch/tag ref or a commit.
subjectRegExp: '^https://github\.com/uug-ai/workflows/\.github/workflows/release-create\.yml@(refs/.+|[0-9a-f]{40})$'
additionalExtensions:
githubWorkflowRepository: uug-ai/hub-pipeline-counting
rekor:
url: https://rekor.sigstore.dev
# Fields of the attestation's predicate. {{`{{ ... }}`}} is Kyverno
# syntax, escaped so Helm leaves it alone.
conditions:
- all:
- key: "{{`{{ result }}`}}"
operator: Equals
value: passed
- key: "{{`{{ repository }}`}}"
operator: Equals
value: uug-ai/hub-pipeline-counting
{{- end }}
@@ -0,0 +1,53 @@
{{- /*
hub-pipeline-counting images must be signed by UUG.AI: a keyless cosign signature issued by
GitHub Actions, made by the UUG.AI release workflow, for uug-ai/hub-pipeline-counting.
A namespaced Policy, so it applies only to the release namespace.
Rendered only when imageVerification.enabled is true and Kyverno is installed.
*/ -}}
{{- if and .Values.imageVerification.enabled (.Capabilities.APIVersions.Has "kyverno.io/v1") }}
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: hub-pipeline-counting-signed-images
annotations:
policies.kyverno.io/title: hub-pipeline-counting images must be signed by UUG.AI
policies.kyverno.io/category: Image signing
policies.kyverno.io/severity: high
policies.kyverno.io/subject: Pod
# Kyverno defaults apply: background scans of running pods, signature required.
# Only settings whose Kyverno default would change behaviour are set below.
spec:
# Audit: report unsigned images, don't block. Enforce: reject the pod.
validationFailureAction: {{ .Values.imageVerification.validationFailureAction }}
# Kyverno's default (Fail) would stop hub-pipeline-counting pods starting while Kyverno is down.
failurePolicy: Ignore
# Verifying against GHCR and Rekor can exceed the 10s default.
webhookTimeoutSeconds: 30
rules:
- name: verify-hub-pipeline-counting
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/uug-ai/hub-pipeline-counting:*"
- "ghcr.io/uug-ai/hub-pipeline-counting@*"
# Kyverno's defaults (true) would rewrite pod images to digests and
# report every tag-based image; enable together with Enforce.
mutateDigest: false
verifyDigest: false
attestors:
- entries:
- keyless:
issuer: https://token.actions.githubusercontent.com
# The UUG.AI release workflow, at a branch/tag ref or a commit.
subjectRegExp: '^https://github\.com/uug-ai/workflows/\.github/workflows/release-create\.yml@(refs/.+|[0-9a-f]{40})$'
additionalExtensions:
# That workflow signs for every UUG.AI repository; this
# binds the signature to hub-pipeline-counting's own repository.
githubWorkflowRepository: uug-ai/hub-pipeline-counting
rekor:
url: https://rekor.sigstore.dev
{{- end }}
@@ -0,0 +1,65 @@
{{- /*
hub-pipeline-dominantcolors images must come from a hardened release: they carry the signed
image-hardening attestation (result "passed") that the UUG.AI release workflow
adds after all hardening gates pass, for uug-ai/hub-pipeline-dominantcolors.
The signature itself is checked by hub-pipeline-dominantcolors-signed-images.yaml.
A namespaced Policy, so it applies only to the release namespace.
Rendered only when imageHardening.enabled is true and Kyverno is installed.
*/ -}}
{{- if and .Values.imageHardening.enabled (.Capabilities.APIVersions.Has "kyverno.io/v1") }}
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: hub-pipeline-dominantcolors-hardened-images
annotations:
policies.kyverno.io/title: hub-pipeline-dominantcolors images must come from a hardened UUG.AI release
policies.kyverno.io/category: Image hardening
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Pod
# Kyverno defaults apply: background scans of running pods, attestation required.
# Only settings whose Kyverno default would change behaviour are set below.
spec:
# Audit: report images without a hardened release, don't block. Enforce: reject the pod.
validationFailureAction: {{ .Values.imageHardening.validationFailureAction }}
# Kyverno's default (Fail) would stop hub-pipeline-dominantcolors pods starting while Kyverno is down.
failurePolicy: Ignore
# Verifying against GHCR and Rekor can exceed the 10s default.
webhookTimeoutSeconds: 30
rules:
- name: verify-hub-pipeline-dominantcolors-hardened
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/uug-ai/hub-pipeline-dominantcolors:*"
- "ghcr.io/uug-ai/hub-pipeline-dominantcolors@*"
# Kyverno's defaults (true) would rewrite pod images to digests and
# report every tag-based image; enable together with Enforce.
mutateDigest: false
verifyDigest: false
attestations:
- type: https://github.com/uug-ai/workflows/attestations/image-hardening/v1
attestors:
- entries:
- keyless:
issuer: https://token.actions.githubusercontent.com
# The UUG.AI release workflow, at a branch/tag ref or a commit.
subjectRegExp: '^https://github\.com/uug-ai/workflows/\.github/workflows/release-create\.yml@(refs/.+|[0-9a-f]{40})$'
additionalExtensions:
githubWorkflowRepository: uug-ai/hub-pipeline-dominantcolors
rekor:
url: https://rekor.sigstore.dev
# Fields of the attestation's predicate. {{`{{ ... }}`}} is Kyverno
# syntax, escaped so Helm leaves it alone.
conditions:
- all:
- key: "{{`{{ result }}`}}"
operator: Equals
value: passed
- key: "{{`{{ repository }}`}}"
operator: Equals
value: uug-ai/hub-pipeline-dominantcolors
{{- end }}
@@ -0,0 +1,53 @@
{{- /*
hub-pipeline-dominantcolors images must be signed by UUG.AI: a keyless cosign signature issued by
GitHub Actions, made by the UUG.AI release workflow, for uug-ai/hub-pipeline-dominantcolors.
A namespaced Policy, so it applies only to the release namespace.
Rendered only when imageVerification.enabled is true and Kyverno is installed.
*/ -}}
{{- if and .Values.imageVerification.enabled (.Capabilities.APIVersions.Has "kyverno.io/v1") }}
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: hub-pipeline-dominantcolors-signed-images
annotations:
policies.kyverno.io/title: hub-pipeline-dominantcolors images must be signed by UUG.AI
policies.kyverno.io/category: Image signing
policies.kyverno.io/severity: high
policies.kyverno.io/subject: Pod
# Kyverno defaults apply: background scans of running pods, signature required.
# Only settings whose Kyverno default would change behaviour are set below.
spec:
# Audit: report unsigned images, don't block. Enforce: reject the pod.
validationFailureAction: {{ .Values.imageVerification.validationFailureAction }}
# Kyverno's default (Fail) would stop hub-pipeline-dominantcolors pods starting while Kyverno is down.
failurePolicy: Ignore
# Verifying against GHCR and Rekor can exceed the 10s default.
webhookTimeoutSeconds: 30
rules:
- name: verify-hub-pipeline-dominantcolors
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/uug-ai/hub-pipeline-dominantcolors:*"
- "ghcr.io/uug-ai/hub-pipeline-dominantcolors@*"
# Kyverno's defaults (true) would rewrite pod images to digests and
# report every tag-based image; enable together with Enforce.
mutateDigest: false
verifyDigest: false
attestors:
- entries:
- keyless:
issuer: https://token.actions.githubusercontent.com
# The UUG.AI release workflow, at a branch/tag ref or a commit.
subjectRegExp: '^https://github\.com/uug-ai/workflows/\.github/workflows/release-create\.yml@(refs/.+|[0-9a-f]{40})$'
additionalExtensions:
# That workflow signs for every UUG.AI repository; this
# binds the signature to hub-pipeline-dominantcolors's own repository.
githubWorkflowRepository: uug-ai/hub-pipeline-dominantcolors
rekor:
url: https://rekor.sigstore.dev
{{- end }}
@@ -0,0 +1,65 @@
{{- /*
hub-pipeline-event images must come from a hardened release: they carry the signed
image-hardening attestation (result "passed") that the UUG.AI release workflow
adds after all hardening gates pass, for uug-ai/hub-pipeline-event.
The signature itself is checked by hub-pipeline-event-signed-images.yaml.
A namespaced Policy, so it applies only to the release namespace.
Rendered only when imageHardening.enabled is true and Kyverno is installed.
*/ -}}
{{- if and .Values.imageHardening.enabled (.Capabilities.APIVersions.Has "kyverno.io/v1") }}
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: hub-pipeline-event-hardened-images
annotations:
policies.kyverno.io/title: hub-pipeline-event images must come from a hardened UUG.AI release
policies.kyverno.io/category: Image hardening
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Pod
# Kyverno defaults apply: background scans of running pods, attestation required.
# Only settings whose Kyverno default would change behaviour are set below.
spec:
# Audit: report images without a hardened release, don't block. Enforce: reject the pod.
validationFailureAction: {{ .Values.imageHardening.validationFailureAction }}
# Kyverno's default (Fail) would stop hub-pipeline-event pods starting while Kyverno is down.
failurePolicy: Ignore
# Verifying against GHCR and Rekor can exceed the 10s default.
webhookTimeoutSeconds: 30
rules:
- name: verify-hub-pipeline-event-hardened
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/uug-ai/hub-pipeline-event:*"
- "ghcr.io/uug-ai/hub-pipeline-event@*"
# Kyverno's defaults (true) would rewrite pod images to digests and
# report every tag-based image; enable together with Enforce.
mutateDigest: false
verifyDigest: false
attestations:
- type: https://github.com/uug-ai/workflows/attestations/image-hardening/v1
attestors:
- entries:
- keyless:
issuer: https://token.actions.githubusercontent.com
# The UUG.AI release workflow, at a branch/tag ref or a commit.
subjectRegExp: '^https://github\.com/uug-ai/workflows/\.github/workflows/release-create\.yml@(refs/.+|[0-9a-f]{40})$'
additionalExtensions:
githubWorkflowRepository: uug-ai/hub-pipeline-event
rekor:
url: https://rekor.sigstore.dev
# Fields of the attestation's predicate. {{`{{ ... }}`}} is Kyverno
# syntax, escaped so Helm leaves it alone.
conditions:
- all:
- key: "{{`{{ result }}`}}"
operator: Equals
value: passed
- key: "{{`{{ repository }}`}}"
operator: Equals
value: uug-ai/hub-pipeline-event
{{- end }}
@@ -0,0 +1,53 @@
{{- /*
hub-pipeline-event images must be signed by UUG.AI: a keyless cosign signature issued by
GitHub Actions, made by the UUG.AI release workflow, for uug-ai/hub-pipeline-event.
A namespaced Policy, so it applies only to the release namespace.
Rendered only when imageVerification.enabled is true and Kyverno is installed.
*/ -}}
{{- if and .Values.imageVerification.enabled (.Capabilities.APIVersions.Has "kyverno.io/v1") }}
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: hub-pipeline-event-signed-images
annotations:
policies.kyverno.io/title: hub-pipeline-event images must be signed by UUG.AI
policies.kyverno.io/category: Image signing
policies.kyverno.io/severity: high
policies.kyverno.io/subject: Pod
# Kyverno defaults apply: background scans of running pods, signature required.
# Only settings whose Kyverno default would change behaviour are set below.
spec:
# Audit: report unsigned images, don't block. Enforce: reject the pod.
validationFailureAction: {{ .Values.imageVerification.validationFailureAction }}
# Kyverno's default (Fail) would stop hub-pipeline-event pods starting while Kyverno is down.
failurePolicy: Ignore
# Verifying against GHCR and Rekor can exceed the 10s default.
webhookTimeoutSeconds: 30
rules:
- name: verify-hub-pipeline-event
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/uug-ai/hub-pipeline-event:*"
- "ghcr.io/uug-ai/hub-pipeline-event@*"
# Kyverno's defaults (true) would rewrite pod images to digests and
# report every tag-based image; enable together with Enforce.
mutateDigest: false
verifyDigest: false
attestors:
- entries:
- keyless:
issuer: https://token.actions.githubusercontent.com
# The UUG.AI release workflow, at a branch/tag ref or a commit.
subjectRegExp: '^https://github\.com/uug-ai/workflows/\.github/workflows/release-create\.yml@(refs/.+|[0-9a-f]{40})$'
additionalExtensions:
# That workflow signs for every UUG.AI repository; this
# binds the signature to hub-pipeline-event's own repository.
githubWorkflowRepository: uug-ai/hub-pipeline-event
rekor:
url: https://rekor.sigstore.dev
{{- end }}
@@ -0,0 +1,65 @@
{{- /*
hub-pipeline-export images must come from a hardened release: they carry the signed
image-hardening attestation (result "passed") that the UUG.AI release workflow
adds after all hardening gates pass, for uug-ai/hub-pipeline-export.
The signature itself is checked by hub-pipeline-export-signed-images.yaml.
A namespaced Policy, so it applies only to the release namespace.
Rendered only when imageHardening.enabled is true and Kyverno is installed.
*/ -}}
{{- if and .Values.imageHardening.enabled (.Capabilities.APIVersions.Has "kyverno.io/v1") }}
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: hub-pipeline-export-hardened-images
annotations:
policies.kyverno.io/title: hub-pipeline-export images must come from a hardened UUG.AI release
policies.kyverno.io/category: Image hardening
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Pod
# Kyverno defaults apply: background scans of running pods, attestation required.
# Only settings whose Kyverno default would change behaviour are set below.
spec:
# Audit: report images without a hardened release, don't block. Enforce: reject the pod.
validationFailureAction: {{ .Values.imageHardening.validationFailureAction }}
# Kyverno's default (Fail) would stop hub-pipeline-export pods starting while Kyverno is down.
failurePolicy: Ignore
# Verifying against GHCR and Rekor can exceed the 10s default.
webhookTimeoutSeconds: 30
rules:
- name: verify-hub-pipeline-export-hardened
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/uug-ai/hub-pipeline-export:*"
- "ghcr.io/uug-ai/hub-pipeline-export@*"
# Kyverno's defaults (true) would rewrite pod images to digests and
# report every tag-based image; enable together with Enforce.
mutateDigest: false
verifyDigest: false
attestations:
- type: https://github.com/uug-ai/workflows/attestations/image-hardening/v1
attestors:
- entries:
- keyless:
issuer: https://token.actions.githubusercontent.com
# The UUG.AI release workflow, at a branch/tag ref or a commit.
subjectRegExp: '^https://github\.com/uug-ai/workflows/\.github/workflows/release-create\.yml@(refs/.+|[0-9a-f]{40})$'
additionalExtensions:
githubWorkflowRepository: uug-ai/hub-pipeline-export
rekor:
url: https://rekor.sigstore.dev
# Fields of the attestation's predicate. {{`{{ ... }}`}} is Kyverno
# syntax, escaped so Helm leaves it alone.
conditions:
- all:
- key: "{{`{{ result }}`}}"
operator: Equals
value: passed
- key: "{{`{{ repository }}`}}"
operator: Equals
value: uug-ai/hub-pipeline-export
{{- end }}
@@ -0,0 +1,53 @@
{{- /*
hub-pipeline-export images must be signed by UUG.AI: a keyless cosign signature issued by
GitHub Actions, made by the UUG.AI release workflow, for uug-ai/hub-pipeline-export.
A namespaced Policy, so it applies only to the release namespace.
Rendered only when imageVerification.enabled is true and Kyverno is installed.
*/ -}}
{{- if and .Values.imageVerification.enabled (.Capabilities.APIVersions.Has "kyverno.io/v1") }}
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: hub-pipeline-export-signed-images
annotations:
policies.kyverno.io/title: hub-pipeline-export images must be signed by UUG.AI
policies.kyverno.io/category: Image signing
policies.kyverno.io/severity: high
policies.kyverno.io/subject: Pod
# Kyverno defaults apply: background scans of running pods, signature required.
# Only settings whose Kyverno default would change behaviour are set below.
spec:
# Audit: report unsigned images, don't block. Enforce: reject the pod.
validationFailureAction: {{ .Values.imageVerification.validationFailureAction }}
# Kyverno's default (Fail) would stop hub-pipeline-export pods starting while Kyverno is down.
failurePolicy: Ignore
# Verifying against GHCR and Rekor can exceed the 10s default.
webhookTimeoutSeconds: 30
rules:
- name: verify-hub-pipeline-export
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/uug-ai/hub-pipeline-export:*"
- "ghcr.io/uug-ai/hub-pipeline-export@*"
# Kyverno's defaults (true) would rewrite pod images to digests and
# report every tag-based image; enable together with Enforce.
mutateDigest: false
verifyDigest: false
attestors:
- entries:
- keyless:
issuer: https://token.actions.githubusercontent.com
# The UUG.AI release workflow, at a branch/tag ref or a commit.
subjectRegExp: '^https://github\.com/uug-ai/workflows/\.github/workflows/release-create\.yml@(refs/.+|[0-9a-f]{40})$'
additionalExtensions:
# That workflow signs for every UUG.AI repository; this
# binds the signature to hub-pipeline-export's own repository.
githubWorkflowRepository: uug-ai/hub-pipeline-export
rekor:
url: https://rekor.sigstore.dev
{{- end }}
@@ -0,0 +1,65 @@
{{- /*
hub-pipeline-monitor images must come from a hardened release: they carry the signed
image-hardening attestation (result "passed") that the UUG.AI release workflow
adds after all hardening gates pass, for uug-ai/hub-pipeline-monitor.
The signature itself is checked by hub-pipeline-monitor-signed-images.yaml.
A namespaced Policy, so it applies only to the release namespace.
Rendered only when imageHardening.enabled is true and Kyverno is installed.
*/ -}}
{{- if and .Values.imageHardening.enabled (.Capabilities.APIVersions.Has "kyverno.io/v1") }}
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: hub-pipeline-monitor-hardened-images
annotations:
policies.kyverno.io/title: hub-pipeline-monitor images must come from a hardened UUG.AI release
policies.kyverno.io/category: Image hardening
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Pod
# Kyverno defaults apply: background scans of running pods, attestation required.
# Only settings whose Kyverno default would change behaviour are set below.
spec:
# Audit: report images without a hardened release, don't block. Enforce: reject the pod.
validationFailureAction: {{ .Values.imageHardening.validationFailureAction }}
# Kyverno's default (Fail) would stop hub-pipeline-monitor pods starting while Kyverno is down.
failurePolicy: Ignore
# Verifying against GHCR and Rekor can exceed the 10s default.
webhookTimeoutSeconds: 30
rules:
- name: verify-hub-pipeline-monitor-hardened
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/uug-ai/hub-pipeline-monitor:*"
- "ghcr.io/uug-ai/hub-pipeline-monitor@*"
# Kyverno's defaults (true) would rewrite pod images to digests and
# report every tag-based image; enable together with Enforce.
mutateDigest: false
verifyDigest: false
attestations:
- type: https://github.com/uug-ai/workflows/attestations/image-hardening/v1
attestors:
- entries:
- keyless:
issuer: https://token.actions.githubusercontent.com
# The UUG.AI release workflow, at a branch/tag ref or a commit.
subjectRegExp: '^https://github\.com/uug-ai/workflows/\.github/workflows/release-create\.yml@(refs/.+|[0-9a-f]{40})$'
additionalExtensions:
githubWorkflowRepository: uug-ai/hub-pipeline-monitor
rekor:
url: https://rekor.sigstore.dev
# Fields of the attestation's predicate. {{`{{ ... }}`}} is Kyverno
# syntax, escaped so Helm leaves it alone.
conditions:
- all:
- key: "{{`{{ result }}`}}"
operator: Equals
value: passed
- key: "{{`{{ repository }}`}}"
operator: Equals
value: uug-ai/hub-pipeline-monitor
{{- end }}
@@ -0,0 +1,53 @@
{{- /*
hub-pipeline-monitor images must be signed by UUG.AI: a keyless cosign signature issued by
GitHub Actions, made by the UUG.AI release workflow, for uug-ai/hub-pipeline-monitor.
A namespaced Policy, so it applies only to the release namespace.
Rendered only when imageVerification.enabled is true and Kyverno is installed.
*/ -}}
{{- if and .Values.imageVerification.enabled (.Capabilities.APIVersions.Has "kyverno.io/v1") }}
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: hub-pipeline-monitor-signed-images
annotations:
policies.kyverno.io/title: hub-pipeline-monitor images must be signed by UUG.AI
policies.kyverno.io/category: Image signing
policies.kyverno.io/severity: high
policies.kyverno.io/subject: Pod
# Kyverno defaults apply: background scans of running pods, signature required.
# Only settings whose Kyverno default would change behaviour are set below.
spec:
# Audit: report unsigned images, don't block. Enforce: reject the pod.
validationFailureAction: {{ .Values.imageVerification.validationFailureAction }}
# Kyverno's default (Fail) would stop hub-pipeline-monitor pods starting while Kyverno is down.
failurePolicy: Ignore
# Verifying against GHCR and Rekor can exceed the 10s default.
webhookTimeoutSeconds: 30
rules:
- name: verify-hub-pipeline-monitor
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/uug-ai/hub-pipeline-monitor:*"
- "ghcr.io/uug-ai/hub-pipeline-monitor@*"
# Kyverno's defaults (true) would rewrite pod images to digests and
# report every tag-based image; enable together with Enforce.
mutateDigest: false
verifyDigest: false
attestors:
- entries:
- keyless:
issuer: https://token.actions.githubusercontent.com
# The UUG.AI release workflow, at a branch/tag ref or a commit.
subjectRegExp: '^https://github\.com/uug-ai/workflows/\.github/workflows/release-create\.yml@(refs/.+|[0-9a-f]{40})$'
additionalExtensions:
# That workflow signs for every UUG.AI repository; this
# binds the signature to hub-pipeline-monitor's own repository.
githubWorkflowRepository: uug-ai/hub-pipeline-monitor
rekor:
url: https://rekor.sigstore.dev
{{- end }}
@@ -0,0 +1,65 @@
{{- /*
hub-pipeline-notification images must come from a hardened release: they carry the signed
image-hardening attestation (result "passed") that the UUG.AI release workflow
adds after all hardening gates pass, for uug-ai/hub-pipeline-notification.
The signature itself is checked by hub-pipeline-notification-signed-images.yaml.
A namespaced Policy, so it applies only to the release namespace.
Rendered only when imageHardening.enabled is true and Kyverno is installed.
*/ -}}
{{- if and .Values.imageHardening.enabled (.Capabilities.APIVersions.Has "kyverno.io/v1") }}
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: hub-pipeline-notification-hardened-images
annotations:
policies.kyverno.io/title: hub-pipeline-notification images must come from a hardened UUG.AI release
policies.kyverno.io/category: Image hardening
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Pod
# Kyverno defaults apply: background scans of running pods, attestation required.
# Only settings whose Kyverno default would change behaviour are set below.
spec:
# Audit: report images without a hardened release, don't block. Enforce: reject the pod.
validationFailureAction: {{ .Values.imageHardening.validationFailureAction }}
# Kyverno's default (Fail) would stop hub-pipeline-notification pods starting while Kyverno is down.
failurePolicy: Ignore
# Verifying against GHCR and Rekor can exceed the 10s default.
webhookTimeoutSeconds: 30
rules:
- name: verify-hub-pipeline-notification-hardened
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/uug-ai/hub-pipeline-notification:*"
- "ghcr.io/uug-ai/hub-pipeline-notification@*"
# Kyverno's defaults (true) would rewrite pod images to digests and
# report every tag-based image; enable together with Enforce.
mutateDigest: false
verifyDigest: false
attestations:
- type: https://github.com/uug-ai/workflows/attestations/image-hardening/v1
attestors:
- entries:
- keyless:
issuer: https://token.actions.githubusercontent.com
# The UUG.AI release workflow, at a branch/tag ref or a commit.
subjectRegExp: '^https://github\.com/uug-ai/workflows/\.github/workflows/release-create\.yml@(refs/.+|[0-9a-f]{40})$'
additionalExtensions:
githubWorkflowRepository: uug-ai/hub-pipeline-notification
rekor:
url: https://rekor.sigstore.dev
# Fields of the attestation's predicate. {{`{{ ... }}`}} is Kyverno
# syntax, escaped so Helm leaves it alone.
conditions:
- all:
- key: "{{`{{ result }}`}}"
operator: Equals
value: passed
- key: "{{`{{ repository }}`}}"
operator: Equals
value: uug-ai/hub-pipeline-notification
{{- end }}
@@ -0,0 +1,53 @@
{{- /*
hub-pipeline-notification images must be signed by UUG.AI: a keyless cosign signature issued by
GitHub Actions, made by the UUG.AI release workflow, for uug-ai/hub-pipeline-notification.
A namespaced Policy, so it applies only to the release namespace.
Rendered only when imageVerification.enabled is true and Kyverno is installed.
*/ -}}
{{- if and .Values.imageVerification.enabled (.Capabilities.APIVersions.Has "kyverno.io/v1") }}
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: hub-pipeline-notification-signed-images
annotations:
policies.kyverno.io/title: hub-pipeline-notification images must be signed by UUG.AI
policies.kyverno.io/category: Image signing
policies.kyverno.io/severity: high
policies.kyverno.io/subject: Pod
# Kyverno defaults apply: background scans of running pods, signature required.
# Only settings whose Kyverno default would change behaviour are set below.
spec:
# Audit: report unsigned images, don't block. Enforce: reject the pod.
validationFailureAction: {{ .Values.imageVerification.validationFailureAction }}
# Kyverno's default (Fail) would stop hub-pipeline-notification pods starting while Kyverno is down.
failurePolicy: Ignore
# Verifying against GHCR and Rekor can exceed the 10s default.
webhookTimeoutSeconds: 30
rules:
- name: verify-hub-pipeline-notification
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/uug-ai/hub-pipeline-notification:*"
- "ghcr.io/uug-ai/hub-pipeline-notification@*"
# Kyverno's defaults (true) would rewrite pod images to digests and
# report every tag-based image; enable together with Enforce.
mutateDigest: false
verifyDigest: false
attestors:
- entries:
- keyless:
issuer: https://token.actions.githubusercontent.com
# The UUG.AI release workflow, at a branch/tag ref or a commit.
subjectRegExp: '^https://github\.com/uug-ai/workflows/\.github/workflows/release-create\.yml@(refs/.+|[0-9a-f]{40})$'
additionalExtensions:
# That workflow signs for every UUG.AI repository; this
# binds the signature to hub-pipeline-notification's own repository.
githubWorkflowRepository: uug-ai/hub-pipeline-notification
rekor:
url: https://rekor.sigstore.dev
{{- end }}
@@ -0,0 +1,65 @@
{{- /*
hub-pipeline-notification-test images must come from a hardened release: they carry the signed
image-hardening attestation (result "passed") that the UUG.AI release workflow
adds after all hardening gates pass, for uug-ai/hub-pipeline-notification-test.
The signature itself is checked by hub-pipeline-notification-test-signed-images.yaml.
A namespaced Policy, so it applies only to the release namespace.
Rendered only when imageHardening.enabled is true and Kyverno is installed.
*/ -}}
{{- if and .Values.imageHardening.enabled (.Capabilities.APIVersions.Has "kyverno.io/v1") }}
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: hub-pipeline-notification-test-hardened-images
annotations:
policies.kyverno.io/title: hub-pipeline-notification-test images must come from a hardened UUG.AI release
policies.kyverno.io/category: Image hardening
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Pod
# Kyverno defaults apply: background scans of running pods, attestation required.
# Only settings whose Kyverno default would change behaviour are set below.
spec:
# Audit: report images without a hardened release, don't block. Enforce: reject the pod.
validationFailureAction: {{ .Values.imageHardening.validationFailureAction }}
# Kyverno's default (Fail) would stop hub-pipeline-notification-test pods starting while Kyverno is down.
failurePolicy: Ignore
# Verifying against GHCR and Rekor can exceed the 10s default.
webhookTimeoutSeconds: 30
rules:
- name: verify-hub-pipeline-notification-test-hardened
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/uug-ai/hub-pipeline-notification-test:*"
- "ghcr.io/uug-ai/hub-pipeline-notification-test@*"
# Kyverno's defaults (true) would rewrite pod images to digests and
# report every tag-based image; enable together with Enforce.
mutateDigest: false
verifyDigest: false
attestations:
- type: https://github.com/uug-ai/workflows/attestations/image-hardening/v1
attestors:
- entries:
- keyless:
issuer: https://token.actions.githubusercontent.com
# The UUG.AI release workflow, at a branch/tag ref or a commit.
subjectRegExp: '^https://github\.com/uug-ai/workflows/\.github/workflows/release-create\.yml@(refs/.+|[0-9a-f]{40})$'
additionalExtensions:
githubWorkflowRepository: uug-ai/hub-pipeline-notification-test
rekor:
url: https://rekor.sigstore.dev
# Fields of the attestation's predicate. {{`{{ ... }}`}} is Kyverno
# syntax, escaped so Helm leaves it alone.
conditions:
- all:
- key: "{{`{{ result }}`}}"
operator: Equals
value: passed
- key: "{{`{{ repository }}`}}"
operator: Equals
value: uug-ai/hub-pipeline-notification-test
{{- end }}
@@ -0,0 +1,53 @@
{{- /*
hub-pipeline-notification-test images must be signed by UUG.AI: a keyless cosign signature issued by
GitHub Actions, made by the UUG.AI release workflow, for uug-ai/hub-pipeline-notification-test.
A namespaced Policy, so it applies only to the release namespace.
Rendered only when imageVerification.enabled is true and Kyverno is installed.
*/ -}}
{{- if and .Values.imageVerification.enabled (.Capabilities.APIVersions.Has "kyverno.io/v1") }}
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: hub-pipeline-notification-test-signed-images
annotations:
policies.kyverno.io/title: hub-pipeline-notification-test images must be signed by UUG.AI
policies.kyverno.io/category: Image signing
policies.kyverno.io/severity: high
policies.kyverno.io/subject: Pod
# Kyverno defaults apply: background scans of running pods, signature required.
# Only settings whose Kyverno default would change behaviour are set below.
spec:
# Audit: report unsigned images, don't block. Enforce: reject the pod.
validationFailureAction: {{ .Values.imageVerification.validationFailureAction }}
# Kyverno's default (Fail) would stop hub-pipeline-notification-test pods starting while Kyverno is down.
failurePolicy: Ignore
# Verifying against GHCR and Rekor can exceed the 10s default.
webhookTimeoutSeconds: 30
rules:
- name: verify-hub-pipeline-notification-test
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/uug-ai/hub-pipeline-notification-test:*"
- "ghcr.io/uug-ai/hub-pipeline-notification-test@*"
# Kyverno's defaults (true) would rewrite pod images to digests and
# report every tag-based image; enable together with Enforce.
mutateDigest: false
verifyDigest: false
attestors:
- entries:
- keyless:
issuer: https://token.actions.githubusercontent.com
# The UUG.AI release workflow, at a branch/tag ref or a commit.
subjectRegExp: '^https://github\.com/uug-ai/workflows/\.github/workflows/release-create\.yml@(refs/.+|[0-9a-f]{40})$'
additionalExtensions:
# That workflow signs for every UUG.AI repository; this
# binds the signature to hub-pipeline-notification-test's own repository.
githubWorkflowRepository: uug-ai/hub-pipeline-notification-test
rekor:
url: https://rekor.sigstore.dev
{{- end }}
@@ -0,0 +1,65 @@
{{- /*
hub-pipeline-sequence images must come from a hardened release: they carry the signed
image-hardening attestation (result "passed") that the UUG.AI release workflow
adds after all hardening gates pass, for uug-ai/hub-pipeline-sequence.
The signature itself is checked by hub-pipeline-sequence-signed-images.yaml.
A namespaced Policy, so it applies only to the release namespace.
Rendered only when imageHardening.enabled is true and Kyverno is installed.
*/ -}}
{{- if and .Values.imageHardening.enabled (.Capabilities.APIVersions.Has "kyverno.io/v1") }}
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: hub-pipeline-sequence-hardened-images
annotations:
policies.kyverno.io/title: hub-pipeline-sequence images must come from a hardened UUG.AI release
policies.kyverno.io/category: Image hardening
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Pod
# Kyverno defaults apply: background scans of running pods, attestation required.
# Only settings whose Kyverno default would change behaviour are set below.
spec:
# Audit: report images without a hardened release, don't block. Enforce: reject the pod.
validationFailureAction: {{ .Values.imageHardening.validationFailureAction }}
# Kyverno's default (Fail) would stop hub-pipeline-sequence pods starting while Kyverno is down.
failurePolicy: Ignore
# Verifying against GHCR and Rekor can exceed the 10s default.
webhookTimeoutSeconds: 30
rules:
- name: verify-hub-pipeline-sequence-hardened
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/uug-ai/hub-pipeline-sequence:*"
- "ghcr.io/uug-ai/hub-pipeline-sequence@*"
# Kyverno's defaults (true) would rewrite pod images to digests and
# report every tag-based image; enable together with Enforce.
mutateDigest: false
verifyDigest: false
attestations:
- type: https://github.com/uug-ai/workflows/attestations/image-hardening/v1
attestors:
- entries:
- keyless:
issuer: https://token.actions.githubusercontent.com
# The UUG.AI release workflow, at a branch/tag ref or a commit.
subjectRegExp: '^https://github\.com/uug-ai/workflows/\.github/workflows/release-create\.yml@(refs/.+|[0-9a-f]{40})$'
additionalExtensions:
githubWorkflowRepository: uug-ai/hub-pipeline-sequence
rekor:
url: https://rekor.sigstore.dev
# Fields of the attestation's predicate. {{`{{ ... }}`}} is Kyverno
# syntax, escaped so Helm leaves it alone.
conditions:
- all:
- key: "{{`{{ result }}`}}"
operator: Equals
value: passed
- key: "{{`{{ repository }}`}}"
operator: Equals
value: uug-ai/hub-pipeline-sequence
{{- end }}
@@ -0,0 +1,53 @@
{{- /*
hub-pipeline-sequence images must be signed by UUG.AI: a keyless cosign signature issued by
GitHub Actions, made by the UUG.AI release workflow, for uug-ai/hub-pipeline-sequence.
A namespaced Policy, so it applies only to the release namespace.
Rendered only when imageVerification.enabled is true and Kyverno is installed.
*/ -}}
{{- if and .Values.imageVerification.enabled (.Capabilities.APIVersions.Has "kyverno.io/v1") }}
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: hub-pipeline-sequence-signed-images
annotations:
policies.kyverno.io/title: hub-pipeline-sequence images must be signed by UUG.AI
policies.kyverno.io/category: Image signing
policies.kyverno.io/severity: high
policies.kyverno.io/subject: Pod
# Kyverno defaults apply: background scans of running pods, signature required.
# Only settings whose Kyverno default would change behaviour are set below.
spec:
# Audit: report unsigned images, don't block. Enforce: reject the pod.
validationFailureAction: {{ .Values.imageVerification.validationFailureAction }}
# Kyverno's default (Fail) would stop hub-pipeline-sequence pods starting while Kyverno is down.
failurePolicy: Ignore
# Verifying against GHCR and Rekor can exceed the 10s default.
webhookTimeoutSeconds: 30
rules:
- name: verify-hub-pipeline-sequence
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/uug-ai/hub-pipeline-sequence:*"
- "ghcr.io/uug-ai/hub-pipeline-sequence@*"
# Kyverno's defaults (true) would rewrite pod images to digests and
# report every tag-based image; enable together with Enforce.
mutateDigest: false
verifyDigest: false
attestors:
- entries:
- keyless:
issuer: https://token.actions.githubusercontent.com
# The UUG.AI release workflow, at a branch/tag ref or a commit.
subjectRegExp: '^https://github\.com/uug-ai/workflows/\.github/workflows/release-create\.yml@(refs/.+|[0-9a-f]{40})$'
additionalExtensions:
# That workflow signs for every UUG.AI repository; this
# binds the signature to hub-pipeline-sequence's own repository.
githubWorkflowRepository: uug-ai/hub-pipeline-sequence
rekor:
url: https://rekor.sigstore.dev
{{- end }}
@@ -0,0 +1,65 @@
{{- /*
hub-pipeline-sprite images must come from a hardened release: they carry the signed
image-hardening attestation (result "passed") that the UUG.AI release workflow
adds after all hardening gates pass, for uug-ai/hub-pipeline-sprite.
The signature itself is checked by hub-pipeline-sprite-signed-images.yaml.
A namespaced Policy, so it applies only to the release namespace.
Rendered only when imageHardening.enabled is true and Kyverno is installed.
*/ -}}
{{- if and .Values.imageHardening.enabled (.Capabilities.APIVersions.Has "kyverno.io/v1") }}
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: hub-pipeline-sprite-hardened-images
annotations:
policies.kyverno.io/title: hub-pipeline-sprite images must come from a hardened UUG.AI release
policies.kyverno.io/category: Image hardening
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Pod
# Kyverno defaults apply: background scans of running pods, attestation required.
# Only settings whose Kyverno default would change behaviour are set below.
spec:
# Audit: report images without a hardened release, don't block. Enforce: reject the pod.
validationFailureAction: {{ .Values.imageHardening.validationFailureAction }}
# Kyverno's default (Fail) would stop hub-pipeline-sprite pods starting while Kyverno is down.
failurePolicy: Ignore
# Verifying against GHCR and Rekor can exceed the 10s default.
webhookTimeoutSeconds: 30
rules:
- name: verify-hub-pipeline-sprite-hardened
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/uug-ai/hub-pipeline-sprite:*"
- "ghcr.io/uug-ai/hub-pipeline-sprite@*"
# Kyverno's defaults (true) would rewrite pod images to digests and
# report every tag-based image; enable together with Enforce.
mutateDigest: false
verifyDigest: false
attestations:
- type: https://github.com/uug-ai/workflows/attestations/image-hardening/v1
attestors:
- entries:
- keyless:
issuer: https://token.actions.githubusercontent.com
# The UUG.AI release workflow, at a branch/tag ref or a commit.
subjectRegExp: '^https://github\.com/uug-ai/workflows/\.github/workflows/release-create\.yml@(refs/.+|[0-9a-f]{40})$'
additionalExtensions:
githubWorkflowRepository: uug-ai/hub-pipeline-sprite
rekor:
url: https://rekor.sigstore.dev
# Fields of the attestation's predicate. {{`{{ ... }}`}} is Kyverno
# syntax, escaped so Helm leaves it alone.
conditions:
- all:
- key: "{{`{{ result }}`}}"
operator: Equals
value: passed
- key: "{{`{{ repository }}`}}"
operator: Equals
value: uug-ai/hub-pipeline-sprite
{{- end }}
@@ -0,0 +1,53 @@
{{- /*
hub-pipeline-sprite images must be signed by UUG.AI: a keyless cosign signature issued by
GitHub Actions, made by the UUG.AI release workflow, for uug-ai/hub-pipeline-sprite.
A namespaced Policy, so it applies only to the release namespace.
Rendered only when imageVerification.enabled is true and Kyverno is installed.
*/ -}}
{{- if and .Values.imageVerification.enabled (.Capabilities.APIVersions.Has "kyverno.io/v1") }}
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: hub-pipeline-sprite-signed-images
annotations:
policies.kyverno.io/title: hub-pipeline-sprite images must be signed by UUG.AI
policies.kyverno.io/category: Image signing
policies.kyverno.io/severity: high
policies.kyverno.io/subject: Pod
# Kyverno defaults apply: background scans of running pods, signature required.
# Only settings whose Kyverno default would change behaviour are set below.
spec:
# Audit: report unsigned images, don't block. Enforce: reject the pod.
validationFailureAction: {{ .Values.imageVerification.validationFailureAction }}
# Kyverno's default (Fail) would stop hub-pipeline-sprite pods starting while Kyverno is down.
failurePolicy: Ignore
# Verifying against GHCR and Rekor can exceed the 10s default.
webhookTimeoutSeconds: 30
rules:
- name: verify-hub-pipeline-sprite
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/uug-ai/hub-pipeline-sprite:*"
- "ghcr.io/uug-ai/hub-pipeline-sprite@*"
# Kyverno's defaults (true) would rewrite pod images to digests and
# report every tag-based image; enable together with Enforce.
mutateDigest: false
verifyDigest: false
attestors:
- entries:
- keyless:
issuer: https://token.actions.githubusercontent.com
# The UUG.AI release workflow, at a branch/tag ref or a commit.
subjectRegExp: '^https://github\.com/uug-ai/workflows/\.github/workflows/release-create\.yml@(refs/.+|[0-9a-f]{40})$'
additionalExtensions:
# That workflow signs for every UUG.AI repository; this
# binds the signature to hub-pipeline-sprite's own repository.
githubWorkflowRepository: uug-ai/hub-pipeline-sprite
rekor:
url: https://rekor.sigstore.dev
{{- end }}
@@ -0,0 +1,65 @@
{{- /*
hub-pipeline-throttler images must come from a hardened release: they carry the signed
image-hardening attestation (result "passed") that the UUG.AI release workflow
adds after all hardening gates pass, for uug-ai/hub-pipeline-throttler.
The signature itself is checked by hub-pipeline-throttler-signed-images.yaml.
A namespaced Policy, so it applies only to the release namespace.
Rendered only when imageHardening.enabled is true and Kyverno is installed.
*/ -}}
{{- if and .Values.imageHardening.enabled (.Capabilities.APIVersions.Has "kyverno.io/v1") }}
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: hub-pipeline-throttler-hardened-images
annotations:
policies.kyverno.io/title: hub-pipeline-throttler images must come from a hardened UUG.AI release
policies.kyverno.io/category: Image hardening
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Pod
# Kyverno defaults apply: background scans of running pods, attestation required.
# Only settings whose Kyverno default would change behaviour are set below.
spec:
# Audit: report images without a hardened release, don't block. Enforce: reject the pod.
validationFailureAction: {{ .Values.imageHardening.validationFailureAction }}
# Kyverno's default (Fail) would stop hub-pipeline-throttler pods starting while Kyverno is down.
failurePolicy: Ignore
# Verifying against GHCR and Rekor can exceed the 10s default.
webhookTimeoutSeconds: 30
rules:
- name: verify-hub-pipeline-throttler-hardened
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/uug-ai/hub-pipeline-throttler:*"
- "ghcr.io/uug-ai/hub-pipeline-throttler@*"
# Kyverno's defaults (true) would rewrite pod images to digests and
# report every tag-based image; enable together with Enforce.
mutateDigest: false
verifyDigest: false
attestations:
- type: https://github.com/uug-ai/workflows/attestations/image-hardening/v1
attestors:
- entries:
- keyless:
issuer: https://token.actions.githubusercontent.com
# The UUG.AI release workflow, at a branch/tag ref or a commit.
subjectRegExp: '^https://github\.com/uug-ai/workflows/\.github/workflows/release-create\.yml@(refs/.+|[0-9a-f]{40})$'
additionalExtensions:
githubWorkflowRepository: uug-ai/hub-pipeline-throttler
rekor:
url: https://rekor.sigstore.dev
# Fields of the attestation's predicate. {{`{{ ... }}`}} is Kyverno
# syntax, escaped so Helm leaves it alone.
conditions:
- all:
- key: "{{`{{ result }}`}}"
operator: Equals
value: passed
- key: "{{`{{ repository }}`}}"
operator: Equals
value: uug-ai/hub-pipeline-throttler
{{- end }}
@@ -0,0 +1,53 @@
{{- /*
hub-pipeline-throttler images must be signed by UUG.AI: a keyless cosign signature issued by
GitHub Actions, made by the UUG.AI release workflow, for uug-ai/hub-pipeline-throttler.
A namespaced Policy, so it applies only to the release namespace.
Rendered only when imageVerification.enabled is true and Kyverno is installed.
*/ -}}
{{- if and .Values.imageVerification.enabled (.Capabilities.APIVersions.Has "kyverno.io/v1") }}
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: hub-pipeline-throttler-signed-images
annotations:
policies.kyverno.io/title: hub-pipeline-throttler images must be signed by UUG.AI
policies.kyverno.io/category: Image signing
policies.kyverno.io/severity: high
policies.kyverno.io/subject: Pod
# Kyverno defaults apply: background scans of running pods, signature required.
# Only settings whose Kyverno default would change behaviour are set below.
spec:
# Audit: report unsigned images, don't block. Enforce: reject the pod.
validationFailureAction: {{ .Values.imageVerification.validationFailureAction }}
# Kyverno's default (Fail) would stop hub-pipeline-throttler pods starting while Kyverno is down.
failurePolicy: Ignore
# Verifying against GHCR and Rekor can exceed the 10s default.
webhookTimeoutSeconds: 30
rules:
- name: verify-hub-pipeline-throttler
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/uug-ai/hub-pipeline-throttler:*"
- "ghcr.io/uug-ai/hub-pipeline-throttler@*"
# Kyverno's defaults (true) would rewrite pod images to digests and
# report every tag-based image; enable together with Enforce.
mutateDigest: false
verifyDigest: false
attestors:
- entries:
- keyless:
issuer: https://token.actions.githubusercontent.com
# The UUG.AI release workflow, at a branch/tag ref or a commit.
subjectRegExp: '^https://github\.com/uug-ai/workflows/\.github/workflows/release-create\.yml@(refs/.+|[0-9a-f]{40})$'
additionalExtensions:
# That workflow signs for every UUG.AI repository; this
# binds the signature to hub-pipeline-throttler's own repository.
githubWorkflowRepository: uug-ai/hub-pipeline-throttler
rekor:
url: https://rekor.sigstore.dev
{{- end }}
@@ -0,0 +1,65 @@
{{- /*
hub-pipeline-thumbnail images must come from a hardened release: they carry the signed
image-hardening attestation (result "passed") that the UUG.AI release workflow
adds after all hardening gates pass, for uug-ai/hub-pipeline-thumbnail.
The signature itself is checked by hub-pipeline-thumbnail-signed-images.yaml.
A namespaced Policy, so it applies only to the release namespace.
Rendered only when imageHardening.enabled is true and Kyverno is installed.
*/ -}}
{{- if and .Values.imageHardening.enabled (.Capabilities.APIVersions.Has "kyverno.io/v1") }}
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: hub-pipeline-thumbnail-hardened-images
annotations:
policies.kyverno.io/title: hub-pipeline-thumbnail images must come from a hardened UUG.AI release
policies.kyverno.io/category: Image hardening
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Pod
# Kyverno defaults apply: background scans of running pods, attestation required.
# Only settings whose Kyverno default would change behaviour are set below.
spec:
# Audit: report images without a hardened release, don't block. Enforce: reject the pod.
validationFailureAction: {{ .Values.imageHardening.validationFailureAction }}
# Kyverno's default (Fail) would stop hub-pipeline-thumbnail pods starting while Kyverno is down.
failurePolicy: Ignore
# Verifying against GHCR and Rekor can exceed the 10s default.
webhookTimeoutSeconds: 30
rules:
- name: verify-hub-pipeline-thumbnail-hardened
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/uug-ai/hub-pipeline-thumbnail:*"
- "ghcr.io/uug-ai/hub-pipeline-thumbnail@*"
# Kyverno's defaults (true) would rewrite pod images to digests and
# report every tag-based image; enable together with Enforce.
mutateDigest: false
verifyDigest: false
attestations:
- type: https://github.com/uug-ai/workflows/attestations/image-hardening/v1
attestors:
- entries:
- keyless:
issuer: https://token.actions.githubusercontent.com
# The UUG.AI release workflow, at a branch/tag ref or a commit.
subjectRegExp: '^https://github\.com/uug-ai/workflows/\.github/workflows/release-create\.yml@(refs/.+|[0-9a-f]{40})$'
additionalExtensions:
githubWorkflowRepository: uug-ai/hub-pipeline-thumbnail
rekor:
url: https://rekor.sigstore.dev
# Fields of the attestation's predicate. {{`{{ ... }}`}} is Kyverno
# syntax, escaped so Helm leaves it alone.
conditions:
- all:
- key: "{{`{{ result }}`}}"
operator: Equals
value: passed
- key: "{{`{{ repository }}`}}"
operator: Equals
value: uug-ai/hub-pipeline-thumbnail
{{- end }}
@@ -0,0 +1,53 @@
{{- /*
hub-pipeline-thumbnail images must be signed by UUG.AI: a keyless cosign signature issued by
GitHub Actions, made by the UUG.AI release workflow, for uug-ai/hub-pipeline-thumbnail.
A namespaced Policy, so it applies only to the release namespace.
Rendered only when imageVerification.enabled is true and Kyverno is installed.
*/ -}}
{{- if and .Values.imageVerification.enabled (.Capabilities.APIVersions.Has "kyverno.io/v1") }}
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: hub-pipeline-thumbnail-signed-images
annotations:
policies.kyverno.io/title: hub-pipeline-thumbnail images must be signed by UUG.AI
policies.kyverno.io/category: Image signing
policies.kyverno.io/severity: high
policies.kyverno.io/subject: Pod
# Kyverno defaults apply: background scans of running pods, signature required.
# Only settings whose Kyverno default would change behaviour are set below.
spec:
# Audit: report unsigned images, don't block. Enforce: reject the pod.
validationFailureAction: {{ .Values.imageVerification.validationFailureAction }}
# Kyverno's default (Fail) would stop hub-pipeline-thumbnail pods starting while Kyverno is down.
failurePolicy: Ignore
# Verifying against GHCR and Rekor can exceed the 10s default.
webhookTimeoutSeconds: 30
rules:
- name: verify-hub-pipeline-thumbnail
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/uug-ai/hub-pipeline-thumbnail:*"
- "ghcr.io/uug-ai/hub-pipeline-thumbnail@*"
# Kyverno's defaults (true) would rewrite pod images to digests and
# report every tag-based image; enable together with Enforce.
mutateDigest: false
verifyDigest: false
attestors:
- entries:
- keyless:
issuer: https://token.actions.githubusercontent.com
# The UUG.AI release workflow, at a branch/tag ref or a commit.
subjectRegExp: '^https://github\.com/uug-ai/workflows/\.github/workflows/release-create\.yml@(refs/.+|[0-9a-f]{40})$'
additionalExtensions:
# That workflow signs for every UUG.AI repository; this
# binds the signature to hub-pipeline-thumbnail's own repository.
githubWorkflowRepository: uug-ai/hub-pipeline-thumbnail
rekor:
url: https://rekor.sigstore.dev
{{- end }}
@@ -0,0 +1,65 @@
{{- /*
hub-reactivatesubscriptions images must come from a hardened release: they carry the signed
image-hardening attestation (result "passed") that the UUG.AI release workflow
adds after all hardening gates pass, for uug-ai/hub-reactivatesubscriptions.
The signature itself is checked by hub-reactivatesubscriptions-signed-images.yaml.
A namespaced Policy, so it applies only to the release namespace.
Rendered only when imageHardening.enabled is true and Kyverno is installed.
*/ -}}
{{- if and .Values.imageHardening.enabled (.Capabilities.APIVersions.Has "kyverno.io/v1") }}
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: hub-reactivatesubscriptions-hardened-images
annotations:
policies.kyverno.io/title: hub-reactivatesubscriptions images must come from a hardened UUG.AI release
policies.kyverno.io/category: Image hardening
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Pod
# Kyverno defaults apply: background scans of running pods, attestation required.
# Only settings whose Kyverno default would change behaviour are set below.
spec:
# Audit: report images without a hardened release, don't block. Enforce: reject the pod.
validationFailureAction: {{ .Values.imageHardening.validationFailureAction }}
# Kyverno's default (Fail) would stop hub-reactivatesubscriptions pods starting while Kyverno is down.
failurePolicy: Ignore
# Verifying against GHCR and Rekor can exceed the 10s default.
webhookTimeoutSeconds: 30
rules:
- name: verify-hub-reactivatesubscriptions-hardened
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/uug-ai/hub-reactivatesubscriptions:*"
- "ghcr.io/uug-ai/hub-reactivatesubscriptions@*"
# Kyverno's defaults (true) would rewrite pod images to digests and
# report every tag-based image; enable together with Enforce.
mutateDigest: false
verifyDigest: false
attestations:
- type: https://github.com/uug-ai/workflows/attestations/image-hardening/v1
attestors:
- entries:
- keyless:
issuer: https://token.actions.githubusercontent.com
# The UUG.AI release workflow, at a branch/tag ref or a commit.
subjectRegExp: '^https://github\.com/uug-ai/workflows/\.github/workflows/release-create\.yml@(refs/.+|[0-9a-f]{40})$'
additionalExtensions:
githubWorkflowRepository: uug-ai/hub-reactivatesubscriptions
rekor:
url: https://rekor.sigstore.dev
# Fields of the attestation's predicate. {{`{{ ... }}`}} is Kyverno
# syntax, escaped so Helm leaves it alone.
conditions:
- all:
- key: "{{`{{ result }}`}}"
operator: Equals
value: passed
- key: "{{`{{ repository }}`}}"
operator: Equals
value: uug-ai/hub-reactivatesubscriptions
{{- end }}
@@ -0,0 +1,53 @@
{{- /*
hub-reactivatesubscriptions images must be signed by UUG.AI: a keyless cosign signature issued by
GitHub Actions, made by the UUG.AI release workflow, for uug-ai/hub-reactivatesubscriptions.
A namespaced Policy, so it applies only to the release namespace.
Rendered only when imageVerification.enabled is true and Kyverno is installed.
*/ -}}
{{- if and .Values.imageVerification.enabled (.Capabilities.APIVersions.Has "kyverno.io/v1") }}
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: hub-reactivatesubscriptions-signed-images
annotations:
policies.kyverno.io/title: hub-reactivatesubscriptions images must be signed by UUG.AI
policies.kyverno.io/category: Image signing
policies.kyverno.io/severity: high
policies.kyverno.io/subject: Pod
# Kyverno defaults apply: background scans of running pods, signature required.
# Only settings whose Kyverno default would change behaviour are set below.
spec:
# Audit: report unsigned images, don't block. Enforce: reject the pod.
validationFailureAction: {{ .Values.imageVerification.validationFailureAction }}
# Kyverno's default (Fail) would stop hub-reactivatesubscriptions pods starting while Kyverno is down.
failurePolicy: Ignore
# Verifying against GHCR and Rekor can exceed the 10s default.
webhookTimeoutSeconds: 30
rules:
- name: verify-hub-reactivatesubscriptions
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/uug-ai/hub-reactivatesubscriptions:*"
- "ghcr.io/uug-ai/hub-reactivatesubscriptions@*"
# Kyverno's defaults (true) would rewrite pod images to digests and
# report every tag-based image; enable together with Enforce.
mutateDigest: false
verifyDigest: false
attestors:
- entries:
- keyless:
issuer: https://token.actions.githubusercontent.com
# The UUG.AI release workflow, at a branch/tag ref or a commit.
subjectRegExp: '^https://github\.com/uug-ai/workflows/\.github/workflows/release-create\.yml@(refs/.+|[0-9a-f]{40})$'
additionalExtensions:
# That workflow signs for every UUG.AI repository; this
# binds the signature to hub-reactivatesubscriptions's own repository.
githubWorkflowRepository: uug-ai/hub-reactivatesubscriptions
rekor:
url: https://rekor.sigstore.dev
{{- end }}
@@ -0,0 +1,65 @@
{{- /*
hub-vlm images must come from a hardened release: they carry the signed
image-hardening attestation (result "passed") that the UUG.AI release workflow
adds after all hardening gates pass, for uug-ai/hub-vlm.
The signature itself is checked by hub-vlm-signed-images.yaml.
A namespaced Policy, so it applies only to the release namespace.
Rendered only when imageHardening.enabled is true and Kyverno is installed.
*/ -}}
{{- if and .Values.imageHardening.enabled (.Capabilities.APIVersions.Has "kyverno.io/v1") }}
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: hub-vlm-hardened-images
annotations:
policies.kyverno.io/title: hub-vlm images must come from a hardened UUG.AI release
policies.kyverno.io/category: Image hardening
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Pod
# Kyverno defaults apply: background scans of running pods, attestation required.
# Only settings whose Kyverno default would change behaviour are set below.
spec:
# Audit: report images without a hardened release, don't block. Enforce: reject the pod.
validationFailureAction: {{ .Values.imageHardening.validationFailureAction }}
# Kyverno's default (Fail) would stop hub-vlm pods starting while Kyverno is down.
failurePolicy: Ignore
# Verifying against GHCR and Rekor can exceed the 10s default.
webhookTimeoutSeconds: 30
rules:
- name: verify-hub-vlm-hardened
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/uug-ai/hub-vlm:*"
- "ghcr.io/uug-ai/hub-vlm@*"
# Kyverno's defaults (true) would rewrite pod images to digests and
# report every tag-based image; enable together with Enforce.
mutateDigest: false
verifyDigest: false
attestations:
- type: https://github.com/uug-ai/workflows/attestations/image-hardening/v1
attestors:
- entries:
- keyless:
issuer: https://token.actions.githubusercontent.com
# The UUG.AI release workflow, at a branch/tag ref or a commit.
subjectRegExp: '^https://github\.com/uug-ai/workflows/\.github/workflows/release-create\.yml@(refs/.+|[0-9a-f]{40})$'
additionalExtensions:
githubWorkflowRepository: uug-ai/hub-vlm
rekor:
url: https://rekor.sigstore.dev
# Fields of the attestation's predicate. {{`{{ ... }}`}} is Kyverno
# syntax, escaped so Helm leaves it alone.
conditions:
- all:
- key: "{{`{{ result }}`}}"
operator: Equals
value: passed
- key: "{{`{{ repository }}`}}"
operator: Equals
value: uug-ai/hub-vlm
{{- end }}
@@ -0,0 +1,53 @@
{{- /*
hub-vlm images must be signed by UUG.AI: a keyless cosign signature issued by
GitHub Actions, made by the UUG.AI release workflow, for uug-ai/hub-vlm.
A namespaced Policy, so it applies only to the release namespace.
Rendered only when imageVerification.enabled is true and Kyverno is installed.
*/ -}}
{{- if and .Values.imageVerification.enabled (.Capabilities.APIVersions.Has "kyverno.io/v1") }}
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: hub-vlm-signed-images
annotations:
policies.kyverno.io/title: hub-vlm images must be signed by UUG.AI
policies.kyverno.io/category: Image signing
policies.kyverno.io/severity: high
policies.kyverno.io/subject: Pod
# Kyverno defaults apply: background scans of running pods, signature required.
# Only settings whose Kyverno default would change behaviour are set below.
spec:
# Audit: report unsigned images, don't block. Enforce: reject the pod.
validationFailureAction: {{ .Values.imageVerification.validationFailureAction }}
# Kyverno's default (Fail) would stop hub-vlm pods starting while Kyverno is down.
failurePolicy: Ignore
# Verifying against GHCR and Rekor can exceed the 10s default.
webhookTimeoutSeconds: 30
rules:
- name: verify-hub-vlm
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/uug-ai/hub-vlm:*"
- "ghcr.io/uug-ai/hub-vlm@*"
# Kyverno's defaults (true) would rewrite pod images to digests and
# report every tag-based image; enable together with Enforce.
mutateDigest: false
verifyDigest: false
attestors:
- entries:
- keyless:
issuer: https://token.actions.githubusercontent.com
# The UUG.AI release workflow, at a branch/tag ref or a commit.
subjectRegExp: '^https://github\.com/uug-ai/workflows/\.github/workflows/release-create\.yml@(refs/.+|[0-9a-f]{40})$'
additionalExtensions:
# That workflow signs for every UUG.AI repository; this
# binds the signature to hub-vlm's own repository.
githubWorkflowRepository: uug-ai/hub-vlm
rekor:
url: https://rekor.sigstore.dev
{{- end }}
@@ -0,0 +1,65 @@
{{- /*
hub-workflows images must come from a hardened release: they carry the signed
image-hardening attestation (result "passed") that the UUG.AI release workflow
adds after all hardening gates pass, for uug-ai/hub-workflows.
The signature itself is checked by hub-workflows-signed-images.yaml.
A namespaced Policy, so it applies only to the release namespace.
Rendered only when imageHardening.enabled is true and Kyverno is installed.
*/ -}}
{{- if and .Values.imageHardening.enabled (.Capabilities.APIVersions.Has "kyverno.io/v1") }}
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: hub-workflows-hardened-images
annotations:
policies.kyverno.io/title: hub-workflows images must come from a hardened UUG.AI release
policies.kyverno.io/category: Image hardening
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Pod
# Kyverno defaults apply: background scans of running pods, attestation required.
# Only settings whose Kyverno default would change behaviour are set below.
spec:
# Audit: report images without a hardened release, don't block. Enforce: reject the pod.
validationFailureAction: {{ .Values.imageHardening.validationFailureAction }}
# Kyverno's default (Fail) would stop hub-workflows pods starting while Kyverno is down.
failurePolicy: Ignore
# Verifying against GHCR and Rekor can exceed the 10s default.
webhookTimeoutSeconds: 30
rules:
- name: verify-hub-workflows-hardened
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/uug-ai/hub-workflows:*"
- "ghcr.io/uug-ai/hub-workflows@*"
# Kyverno's defaults (true) would rewrite pod images to digests and
# report every tag-based image; enable together with Enforce.
mutateDigest: false
verifyDigest: false
attestations:
- type: https://github.com/uug-ai/workflows/attestations/image-hardening/v1
attestors:
- entries:
- keyless:
issuer: https://token.actions.githubusercontent.com
# The UUG.AI release workflow, at a branch/tag ref or a commit.
subjectRegExp: '^https://github\.com/uug-ai/workflows/\.github/workflows/release-create\.yml@(refs/.+|[0-9a-f]{40})$'
additionalExtensions:
githubWorkflowRepository: uug-ai/hub-workflows
rekor:
url: https://rekor.sigstore.dev
# Fields of the attestation's predicate. {{`{{ ... }}`}} is Kyverno
# syntax, escaped so Helm leaves it alone.
conditions:
- all:
- key: "{{`{{ result }}`}}"
operator: Equals
value: passed
- key: "{{`{{ repository }}`}}"
operator: Equals
value: uug-ai/hub-workflows
{{- end }}
@@ -0,0 +1,53 @@
{{- /*
hub-workflows images must be signed by UUG.AI: a keyless cosign signature issued by
GitHub Actions, made by the UUG.AI release workflow, for uug-ai/hub-workflows.
A namespaced Policy, so it applies only to the release namespace.
Rendered only when imageVerification.enabled is true and Kyverno is installed.
*/ -}}
{{- if and .Values.imageVerification.enabled (.Capabilities.APIVersions.Has "kyverno.io/v1") }}
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: hub-workflows-signed-images
annotations:
policies.kyverno.io/title: hub-workflows images must be signed by UUG.AI
policies.kyverno.io/category: Image signing
policies.kyverno.io/severity: high
policies.kyverno.io/subject: Pod
# Kyverno defaults apply: background scans of running pods, signature required.
# Only settings whose Kyverno default would change behaviour are set below.
spec:
# Audit: report unsigned images, don't block. Enforce: reject the pod.
validationFailureAction: {{ .Values.imageVerification.validationFailureAction }}
# Kyverno's default (Fail) would stop hub-workflows pods starting while Kyverno is down.
failurePolicy: Ignore
# Verifying against GHCR and Rekor can exceed the 10s default.
webhookTimeoutSeconds: 30
rules:
- name: verify-hub-workflows
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/uug-ai/hub-workflows:*"
- "ghcr.io/uug-ai/hub-workflows@*"
# Kyverno's defaults (true) would rewrite pod images to digests and
# report every tag-based image; enable together with Enforce.
mutateDigest: false
verifyDigest: false
attestors:
- entries:
- keyless:
issuer: https://token.actions.githubusercontent.com
# The UUG.AI release workflow, at a branch/tag ref or a commit.
subjectRegExp: '^https://github\.com/uug-ai/workflows/\.github/workflows/release-create\.yml@(refs/.+|[0-9a-f]{40})$'
additionalExtensions:
# That workflow signs for every UUG.AI repository; this
# binds the signature to hub-workflows's own repository.
githubWorkflowRepository: uug-ai/hub-workflows
rekor:
url: https://rekor.sigstore.dev
{{- end }}
@@ -0,0 +1,54 @@
{{- /*
UUG.AI images are trusted only from ghcr.io/uug-ai: report a container, init
container or ephemeral container image that is a UUG.AI image (uugai/... on
Docker Hub, or any registry's uugai/ or uug-ai/ organisation) pulled from
anywhere else. Other images in the namespace (agents, cert-manager solvers,
third-party tools) are not the chart's to vouch for and are ignored. Signature
and hardening are checked per image by the *-signed-images / *-hardened-images
policies, which match ghcr.io/uug-ai images only.
A namespaced Policy, so it applies only to the release namespace.
Rendered only when imageVerification.enabled is true and Kyverno is installed.
*/ -}}
{{- if and .Values.imageVerification.enabled (.Capabilities.APIVersions.Has "kyverno.io/v1") }}
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: trusted-registry
annotations:
policies.kyverno.io/title: UUG.AI images must come from ghcr.io/uug-ai
policies.kyverno.io/category: Image registry
policies.kyverno.io/severity: high
policies.kyverno.io/subject: Pod
spec:
# Reports UUG.AI images from other registries without blocking them, even
# when imageVerification.validationFailureAction is Enforce.
validationFailureAction: Audit
# Kyverno's default (Fail) would stop pods starting while Kyverno is down.
failurePolicy: Ignore
rules:
- name: images-from-ghcr-uug-ai
match:
any:
- resources:
kinds:
- Pod
validate:
message: "UUG.AI images must come from the trusted registry ghcr.io/uug-ai/."
foreach:
- list: "request.object.spec.[containers, initContainers, ephemeralContainers][]"
deny:
conditions:
all:
# A UUG.AI image: Docker Hub uugai/..., or a uugai/ or uug-ai/
# organisation on any registry ...
- key: "{{`{{ element.image }}`}}"
operator: AnyIn
value:
- "uugai/*"
- "*/uugai/*"
- "*/uug-ai/*"
# ... that is not pulled from ghcr.io/uug-ai.
- key: "{{`{{ element.image }}`}}"
operator: NotEquals
value: "ghcr.io/uug-ai/*"
{{- end }}
+140 -2
View File
@@ -8,8 +8,91 @@
"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}
}
}
},
"imageVerification": {
"type": "object",
"additionalProperties": false,
"description": "Kyverno policies (templates/kyverno/) requiring UUG.AI images to be signed by UUG.AI. Created only when the cluster serves kyverno.io/v1.",
"properties": {
"enabled": {
"type": "boolean",
"description": "Create the policies when Kyverno is installed."
},
"validationFailureAction": {
"type": "string",
"enum": [
"Audit",
"Enforce"
],
"description": "Audit reports unsigned images but lets them run; Enforce rejects them."
}
}
},
"imageHardening": {
"type": "object",
"additionalProperties": false,
"description": "Kyverno policies (templates/kyverno/*-hardened-images.yaml) requiring images to come from a hardened UUG.AI release (signed image-hardening attestation). Created only when the cluster serves kyverno.io/v1.",
"properties": {
"enabled": {
"type": "boolean",
"description": "Create the hardening policies when Kyverno is installed."
},
"validationFailureAction": {
"type": "string",
"enum": [
"Audit",
"Enforce"
],
"description": "Audit reports images without a hardened release but lets them run; Enforce rejects them."
}
}
},
"deviceTags": {
"type": "object",
"additionalProperties": false,
"properties": {
"system": {
"type": "object",
"additionalProperties": false,
"properties": {
"enabled": {
"type": "boolean",
"description": "Owners may assign system tags and global workflows may select devices by them. Only for deployments whose owners are trusted (self-hosted)."
},
"catalog": {
"type": "array",
"description": "The system tags owners can assign, in the Hub shared choice catalog format (key, label) plus an optional description. Keys must be unique (checked when rendering).",
"items": {
"type": "object",
"additionalProperties": false,
"required": ["key", "label"],
"properties": {
"key": {
"type": "string",
"maxLength": 64,
"pattern": "^[a-z0-9]([a-z0-9._:-]*[a-z0-9])?$"
},
"label": {"type": "string", "minLength": 1},
"description": {"type": "string"}
}
}
}
}
}
}
},
"kerberoshub": {
"type": "object",
@@ -17,12 +100,67 @@
"api": {
"type": "object",
"properties": {
"mcp": {
"type": "object",
"additionalProperties": false,
"properties": {
"enabled": {"type": "boolean"},
"allowedOrigins": {
"type": "array",
"uniqueItems": true,
"items": {
"type": "string",
"pattern": "^https?://[^/?#*@,\\s]+$"
},
"description": "Exact HTTP(S) origins for browser MCP clients, without wildcards, credentials, paths or trailing slashes. Independent of REST CORS."
}
}
},
"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."
}
}
}
+323 -110
View File
@@ -26,6 +26,28 @@ readOnly: false # Set to 'true' to halt all write operations to the database (e.
# - ui: Hub services only
mode: "all"
###########################################################################
# Image signature verification (Kyverno)
###########################################################################
# Kyverno policies in templates/kyverno/ (one file per ghcr.io/uug-ai image)
# requiring UUG.AI images to be signed by UUG.AI. Opt-in: when enabled, they are
# created only if Kyverno is installed.
imageVerification:
enabled: false
# Audit: report unsigned images (PolicyReports) but let them run.
# Enforce: reject pods whose image is unsigned or signed for another repository.
# Images whose releases are not signed yet always stay in Audit (see README).
validationFailureAction: Audit
# Kyverno policies in templates/kyverno/ (*-hardened-images.yaml, one per image
# whose repository makes hardened releases) requiring the image to come from a
# hardened UUG.AI release: a signed image-hardening attestation with result
# "passed" for the image's own repository. Opt-in and separate from
# imageVerification, since only releases made with hardening enabled carry it.
imageHardening:
enabled: false
# Audit: report images without a hardened release but let them run.
# Enforce: reject pods whose image is not from a hardened release.
validationFailureAction: Audit
###########################################################################
# Global configuration
###########################################################################
global:
@@ -50,7 +72,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 +94,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,9 +102,39 @@ 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
# across organisations by system tag (`systemTags` in a trigger). Owners set
# them, so any owner could enroll devices into such a workflow: enable only
# where owners are the deployment's administrators (self-hosted), never on
# multi-tenant SaaS. While disabled, hub-api gets an empty catalog (system tags
# cannot be set) and rendering fails on workflows that select devices by
# system tag.
deviceTags:
system:
enabled: false # a boolean (true/false), not a string
# The only system tags owners can assign, in the Hub shared choice catalog
# format (as catalogs/classifications.json) plus an optional description.
# Rendered into the device-system-tags ConfigMap for hub-api.
# key stored tag: 1-64 characters of a-z, 0-9, ".", "_", ":" or
# "-", starting and ending with a letter or digit; unique.
# label display name.
# description optional help text shown with the tag.
# Workflow trigger systemTags must exist here; otherwise rendering fails.
catalog: []
# - 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:
@@ -97,7 +147,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.
@@ -178,17 +228,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)
#
@@ -226,9 +286,34 @@ kerberosvault:
# For now it's used to create, edit users for Hub.
###########################################################################
admin:
repository: uugai/admin
# Signed release from the UUG.AI release workflow (only ghcr.io/uug-ai is trusted).
# It runs shell-free as 65532:65532 on 8080 with a read-only root filesystem:
# keep the runtime settings below together with the image (see README).
repository: ghcr.io/uug-ai/admin
containerPort: 8080
securityContext:
runAsNonRoot: true
runAsUser: 65532
runAsGroup: 65532
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
seccompProfile:
type: RuntimeDefault
readinessProbe:
httpGet:
path: /healthz
port: http
periodSeconds: 10
livenessProbe:
httpGet:
path: /healthz
port: http
initialDelaySeconds: 10
periodSeconds: 10
pullPolicy: IfNotPresent
tag: "v1.3.0"
tag: "v1.4.6"
replicas: 2
# Optional pod topology spread constraints for this deployment. Empty by
# default. Example:
@@ -240,13 +325,16 @@ admin:
# matchLabels:
# app: admin
topologySpreadConstraints: []
# Optional extra volumes / volumeMounts for this deployment (empty = none).
#volumes:
# - name: extra
# emptyDir: {}
#volumeMounts:
# - name: extra
# mountPath: /data
# The only writable path: memory-backed /tmp (counts against the pod memory
# limit). Append extra volumes / volumeMounts to these lists.
volumes:
- name: admin-tmp
emptyDir:
medium: Memory
sizeLimit: 16Mi
volumeMounts:
- name: admin-tmp
mountPath: /tmp
logLevel: "info" # possible values: trace, debug, info, warn, error
resources:
requests:
@@ -293,8 +381,27 @@ kerberoshub:
api:
repository: ghcr.io/uug-ai/hub-api
pullPolicy: IfNotPresent
tag: "v1.9.51"
tag: "v1.11.42"
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).
@@ -303,11 +410,22 @@ kerberoshub:
serviceEnabled: true
logLevel: "info" # possible values: trace, debug, info, warn, error
jwtSecret: "this-is-a-secret-please-change-to-random-string" # change to a random value, this is for generating JWT tokens.
# How long a user stays signed in (Go durations, e.g. "15m", "12h").
session:
accessTokenTimeout: "15m" # Lifetime of one access token; the frontend renews it silently.
idleTimeout: "12h" # A session that is not used for this long ends.
maxLifetime: "24h" # Hard limit counted from login; the user must sign in again after it.
schema: "https"
url: "api.yourdomain.com"
# Additional complete HTTP(S) origins allowed to call the Hub API.
# The configured frontend origins remain allowed automatically.
corsAllowedOrigins: []
# Read-only Streamable HTTP MCP endpoint at /mcp; requires a compatible API image.
mcp:
enabled: false
# Exact HTTP(S) origins for browser MCP clients; no wildcards or trailing slash.
# Clients without an Origin header (such as VS Code) need no entries.
allowedOrigins: []
# Temporarily stop serving the polled GET /devices list: hub-api answers 204
# without querying MongoDB. The frontend still loads devices once through
# /session/bootstrap, but device online/offline status stops refreshing.
@@ -329,6 +447,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
@@ -351,6 +470,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"
@@ -366,6 +492,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:
@@ -424,8 +555,28 @@ kerberoshub:
frontend:
repository: ghcr.io/uug-ai/hub-frontend
pullPolicy: IfNotPresent
tag: "v1.13.9"
tag: "v1.19.35"
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).
@@ -560,6 +711,7 @@ kerberoshub:
switcherEnabled: "false" # Enable or disable organisation switching 'true' or 'false'
creationEnabled: "false" # Enable or disable organisation creation; requires switcherEnabled 'true' or 'false'
settingsEnabled: "false" # Enable or disable the organisation settings link 'true' or 'false'
membersAccessPreviewEnabled: "false" # Show the mocked "Users (preview)" tab in organisation settings to owners 'true' or 'false'
projects:
enabled: "" # Fallback project group switch used only when organisations.enabled is empty
switcherEnabled: "false" # Enable or disable the read-only project dropdown 'true' or 'false'
@@ -599,6 +751,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'
@@ -678,6 +837,7 @@ kerberoshub:
publicKey: ""
googlemaps: # Google maps is used inside the application to visualise cameras and sites.
apikey: "xxxx"
placesApi: "legacy" # Places API for address autocomplete: "legacy" (default) or "new" for keys with Places API (New) enabled. Uses the same apikey.
zendesk: # We can use different support tools, for now we use Zendesk but others can be integrated
url: "yourdomain.zendesk.com"
posthog: # Posthog is used for auditing and user interaction logging
@@ -702,9 +862,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.27"
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).
@@ -752,13 +915,19 @@ kerberoshub:
requests:
memory: 10Mi
cpu: 10m
# Dispatches canonical audit_events to one or more external webhook sinks.
# Credentials are always read from existing Secrets and never stored here.
# Dispatches canonical audit_events to one or more destinations: webhook
# sinks, or S3 Object Lock buckets for immutable storage.
# Destination credentials are set inline on each destination.
audit:
repository: ghcr.io/uug-ai/hub-audit
pullPolicy: IfNotPresent
tag: "v1.0.1"
tag: "v1.0.12"
replicas: 1
resources: {}
# resources:
# requests:
# memory: 32Mi
# cpu: 10m
dispatchInterval: "5s"
leaseDuration: "2m"
terminationGracePeriodSeconds: 150 # Keep above leaseDuration so an in-flight cycle can finish.
@@ -766,37 +935,70 @@ 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
# Each destination is optional; see the README for every field. Webhook:
destinations: []
# - id: primary-siem
# type: webhook
# enabled: true
# url: https://siem.example.com/api/audit
# timeoutSeconds: 10
# batchSize: 100
# maxAttempts: 10
# startAt: latest # latest | beginning, applied only on first creation
# headers:
# - id: primary-siem # required; ^[a-z][a-z0-9-]{0,62}$, keeps the checkpoint
# type: webhook # optional: webhook (default) | s3
# enabled: true # optional, default true; false pauses delivery
# url: https://siem.example.com/api/audit # required, http or https
# timeoutSeconds: 10 # optional, default 10, at most 60
# batchSize: 100 # optional, default 100, at most 1000
# maxAttempts: 10 # optional, default 10; then the batch is dead-lettered
# startAt: latest # optional: latest (default) | beginning, for a new id only
# headers: # optional; not Authorization or Idempotency-Key
# X-Source: kerberos-hub
# bearerTokenSecret:
# name: audit-primary-siem
# key: token
# # Optional inline credentials. The chart stores them in Secret
# # hub-audit-<id>-credentials and mounts them.
# bearerToken: "xxx" # sent as Authorization: Bearer <token>
# secretHeaders:
# X-Api-Key:
# secretName: audit-primary-siem
# secretKey: api-key
# filters:
# value: "xxx"
# filters: # optional, default all events; an event must match every list set
# organisationIds: []
# projectIds: []
# actions: []
# categories: []
# outcomes: [] # success | failure
# resources:
# requests:
# memory: 32Mi
# cpu: 10m
# S3 Object Lock destination. 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 # required
# type: s3 # required for S3
# enabled: true # optional, default true
# timeoutSeconds: 30
# batchSize: 500
# maxAttempts: 100 # keep high: exhausted batches are dead-lettered, not archived
# startAt: beginning
# s3:
# bucket: hub-audit-archive # required
# region: eu-west-1 # required, also for S3-compatible stores
# prefix: hub # optional, default none
# endpoint: https://s3.example.com # optional; S3-compatible stores only, omit for AWS S3
# forcePathStyle: true # optional, default false; usually true for Ceph RGW, MinIO and Garage
# # Optional, default false. Only for stores without Object Lock (e.g. Garage):
# # skips the lock check, so archived batches are deletable.
# allowUnlockedBucket: false
# # Optional inline credentials, both or neither. The chart stores them
# # in Secret hub-audit-<id>-credentials and mounts them.
# accessKey: "xxx"
# secretKey: "xxx"
# # Without them, hub-audit uses the pod's AWS identity (IRSA via
# # kerberoshub.audit.serviceAccount, or an instance profile): AWS S3 only.
# hub-workflows is the standalone, queue-driven workflow engine. It consumes
# pipeline events and dispatches the stages declared in its workflow
# definitions, tracking each run in its own `workflow_runs` collection. It shares events
@@ -839,7 +1041,9 @@ kerberoshub:
# triggers how a run OPENS. Omit for a single bare automatic trigger
# (opens for every recording); the per-stage `needs` then decide
# which stages FIRE. Narrow with device/schedule triggers, e.g.
# `- {type: automatic, devices: [{key: <device-key>}]}`.
# `- {type: automatic, devices: [{key: <device-key>}]}`, or by
# device label across organisations, e.g.
# `- {type: automatic, systemTags: [anpr-pilot]}` (see deviceTags).
# stages the executable stages, each {operation, dispatch?, needs?,
# needsMode?}. dispatch is "always" (default) or "conditional";
# a conditional stage's `needs` are upstream dependencies, each
@@ -913,8 +1117,10 @@ kerberoshub:
# `enabled` here.
workflows:
repository: ghcr.io/uug-ai/hub-workflows
securityContext: {}
podSecurityContext: {}
pullPolicy: IfNotPresent
tag: "v1.0.0"
tag: "v1.0.49"
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).
@@ -944,7 +1150,7 @@ kerberoshub:
# enabled: true
# repository: ghcr.io/uug-ai/hub-loitering
# pullPolicy: IfNotPresent
# tag: "v1.0.0"
# tag: "v1.0.11"
# replicas: 1 # Number of pods for the worker.
# topologySpreadConstraints: [] # Optional pod topology spread constraints (empty = none).
# #volumes:
@@ -973,7 +1179,7 @@ kerberoshub:
# enabled: true
# repository: ghcr.io/uug-ai/hub-objecttracking
# pullPolicy: IfNotPresent
# tag: "v1.0.0"
# tag: "v1.0.11"
# replicas: 1 # Number of pods for the worker.
# topologySpreadConstraints: [] # Optional pod topology spread constraints (empty = none).
# logLevel: "info" # possible values: trace, debug, info, warn, error
@@ -998,7 +1204,7 @@ kerberoshub:
# enabled: true
# repository: ghcr.io/uug-ai/hub-vlm
# pullPolicy: IfNotPresent
# tag: "v1.0.0"
# tag: "v1.0.9"
# replicas: 1 # Number of pods for the worker.
# topologySpreadConstraints: [] # Optional pod topology spread constraints (empty = none).
# logLevel: "info" # possible values: trace, debug, info, warn, error
@@ -1021,12 +1227,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.15"
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
@@ -1040,9 +1253,9 @@ kerberoshub:
# persistentVolumeClaim:
# claimName: custom-layout-claim
reactivate:
repository: uugai/hub-reactivatesubscriptions
repository: ghcr.io/uug-ai/hub-reactivatesubscriptions
pullPolicy: IfNotPresent
tag: "v1.0.2"
tag: "v1.0.3"
replicas: 0 # Number of pods for the service. Set to 0 to disable.
topologySpreadConstraints: [] # Optional pod topology spread constraints (empty = none).
# Optional extra volumes / volumeMounts for this deployment (empty = none).
@@ -1057,43 +1270,6 @@ kerberoshub:
requests:
memory: 10Mi
cpu: 10m
forwarder:
enabled: false
topologySpreadConstraints: [] # Optional pod topology spread constraints (empty = none).
# Optional extra volumes / volumeMounts for this deployment (empty = none).
#volumes:
# - name: extra
# emptyDir: {}
#volumeMounts:
# - name: extra
# mountPath: /data
#repository: kerberos/vault-forwarder
#pullPolicy: IfNotPresent
#tag: "1.0.2732389692"
#resources:
# requests:
# memory: 10Mi
# cpu: 10m
# This proxy is legacy for the old agent, will be migrated in the new Hub API.
proxy:
enabled: false
repository: uugai/hub-proxy
pullPolicy: IfNotPresent
tag: "v1.0.0"
replicas: 0 # Number of pods for the service when enabled.
topologySpreadConstraints: [] # Optional pod topology spread constraints (empty = none).
# Optional extra volumes / volumeMounts for this deployment (empty = none).
#volumes:
# - name: extra
# emptyDir: {}
#volumeMounts:
# - name: extra
# mountPath: /data
logLevel: "info" # possible values: trace, debug, info, warn, error
resources:
requests:
memory: 10Mi
cpu: 10m
# The Kerberos Hub pipeline is a series of microservices that process the recordings.
# Each microservice is responsible for a specific task, like counting objects, generating thumbnails, etc.
# The pipeline is orchestrated through Kafka topics or RabbitMQ queues.
@@ -1106,7 +1282,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.11"
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).
@@ -1127,7 +1303,7 @@ kerberospipeline:
# deploymentAnnotations:
# argocd.argoproj.io/sync-wave: "70"
pullPolicy: IfNotPresent
tag: "v1.3.13"
tag: "v1.3.30"
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).
@@ -1148,7 +1324,7 @@ kerberospipeline:
# deploymentAnnotations:
# argocd.argoproj.io/sync-wave: "60"
pullPolicy: IfNotPresent
tag: "v1.6.26"
tag: "v1.7.15"
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).
@@ -1158,17 +1334,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.11"
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).
@@ -1184,12 +1363,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.49"
replicas: 1 # Number of pods for the service.
audit:
enabled: true
@@ -1209,9 +1390,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.8"
replicas: 1 # Number of pods for the service.
topologySpreadConstraints: [] # Optional pod topology spread constraints (empty = none).
resources:
@@ -1232,7 +1413,7 @@ kerberospipeline:
# deploymentAnnotations:
# argocd.argoproj.io/sync-wave: "40"
pullPolicy: IfNotPresent
tag: "v1.8.5"
tag: "v1.8.31"
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).
@@ -1243,6 +1424,16 @@ 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 up to one thumbnail job per
# classified recording; size pipe-thumbnail accordingly before enabling.
# minSeconds (THUMBNAIL_REGENERATION_MIN_SECONDS): only regenerate when the
# object first appears at least this many seconds into the recording; earlier
# objects are already in the original thumbnail. 0 regenerates for every object.
thumbnailRegeneration:
enabled: false
minSeconds: 3
# 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:
@@ -1253,12 +1444,18 @@ 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.
enabled: false # Enable or disable dominant color extraction 'true' or 'false'
repository: ghcr.io/uug-ai/hub-pipeline-dominantcolors
deploymentAnnotations: {}
# deploymentAnnotations:
# argocd.argoproj.io/sync-wave: "50"
pullPolicy: IfNotPresent
tag: "v2.0.3"
tag: "v2.0.11"
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).
@@ -1277,12 +1474,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.25"
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).
@@ -1297,6 +1496,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:
@@ -1307,12 +1507,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.7"
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).
@@ -1334,7 +1536,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.29"
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).
@@ -1361,7 +1563,7 @@ kerberospipeline:
export:
repository: ghcr.io/uug-ai/hub-pipeline-export
pullPolicy: IfNotPresent
tag: "v1.2.10"
tag: "v1.2.23"
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
@@ -1414,5 +1616,16 @@ 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"
# Recording check alerts (Alerts → Devices → Recording check). Empty uses
# the "recording" template with SMTP. For SendGrid or Mailgun, set the
# template id; empty uses the device template there.
recording: ""
# Empty uses "[Device] A camera stopped recording", "[Device] A camera is
# recording again" and their forms for several cameras.
recordingTitle: ""
+87
View File
@@ -0,0 +1,87 @@
#!/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
}
# Since chart 0.184.0 the hardened ghcr.io/uug-ai/admin runtime is the default;
# LEGACY_FLAGS opts back into a legacy image on port 80.
LEGACY_FLAGS=(
--set admin.repository=uugai/admin
--set admin.tag=v1.3.0
--set admin.containerPort=80
--set admin.securityContext=null
--set admin.readinessProbe=null
--set admin.livenessProbe=null
--set admin.volumes=null
--set admin.volumeMounts=null
)
for mode in all ui; do
hardened_render="$(helm template hub "$CHART_DIR" --set "mode=$mode" --set admin.oauth2Proxy.enabled=true)"
hardened_deployment="$(resource deployment <<< "$hardened_render")"
hardened_service="$(resource service <<< "$hardened_render")"
hardened_ingress="$(resource ingress <<< "$hardened_render")"
hardened_oauth="$(resource oauth2-proxy <<< "$hardened_render")"
assert_contains "$hardened_deployment" 'image: "ghcr.io/uug-ai/admin:'
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
assert_contains "$hardened_service" 'port: 80$'
assert_contains "$hardened_service" 'targetPort: http$'
legacy_render="$(helm template hub "$CHART_DIR" --set "mode=$mode" --set admin.oauth2Proxy.enabled=true "${LEGACY_FLAGS[@]}")"
legacy_deployment="$(resource deployment <<< "$legacy_render")"
assert_contains "$legacy_deployment" 'containerPort: 80$'
assert_absent "$legacy_deployment" 'securityContext:|readinessProbe:|livenessProbe:|admin-tmp'
[[ "$(resource service <<< "$legacy_render")" == "$hardened_service" ]]
[[ "$(resource ingress <<< "$legacy_render")" == "$hardened_ingress" ]]
[[ "$(resource oauth2-proxy <<< "$legacy_render")" == "$hardened_oauth" ]]
assert_contains "$hardened_ingress" 'nginx.ingress.kubernetes.io/auth-url:'
assert_contains "$hardened_ingress" 'number: 80$'
echo "OK ($mode): hardened ghcr.io defaults; legacy opt-out; stable Service, ingress and OAuth2"
done
pipeline_render="$(helm template hub "$CHART_DIR" --set mode=pipeline)"
assert_absent "$pipeline_render" '^# Source: hub/templates/admin/'
echo "All Admin runtime checks passed."
+102
View File
@@ -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.35"$'
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: 8080$'
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."
+202
View File
@@ -0,0 +1,202 @@
#!/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_admin() {
awk '
/^---$/ { skip=0 }
/^# Source: hub\/templates\/admin\/deployment\.yaml$/ { skip=1 }
!skip { print }
'
}
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")"
# Frontends, hub-api and Admin (since 0.184.0) are hardened by default.
worker_defaults="$(without_frontends <<< "$defaults" | without_admin)"
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" | without_admin | grep -c 'runAsUser: 65532')" == "$expected" ]]
[[ "$(without_frontends <<< "$hardened" | without_admin | 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."
# Since chart 0.184.0 the hardened Admin runtime is the default; these flags
# opt back into a legacy image on port 80.
legacy_admin_flags=(
--set admin.repository=uugai/admin
--set admin.tag=v1.3.0
--set admin.containerPort=80
--set admin.securityContext=null
--set admin.readinessProbe=null
--set admin.livenessProbe=null
--set admin.volumes=null
--set admin.volumeMounts=null
--set admin.oauth2Proxy.enabled=true
)
for mode in all ui pipeline; do
legacy="$(helm template hub "$CHART_DIR" --set "mode=$mode" "${legacy_admin_flags[@]}")"
configured="$(helm template hub "$CHART_DIR" --set "mode=$mode" --set admin.oauth2Proxy.enabled=true)"
helm lint "$CHART_DIR" --quiet --set "mode=$mode" "${legacy_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")" ]]
[[ "$(without_admin <<< "$legacy" | grep 'image:')" == "$(without_admin <<< "$configured" | grep 'image:')" ]]
assert_line "$old" 'image: "uugai/admin:v1.3.0"$'
assert_line "$block" 'image: "ghcr.io/uug-ai/admin:v1.4.6"$'
# No other workload inherits Admin's container port or security settings.
[[ "$(without_frontends <<< "$configured" | grep -c 'runAsUser: 65532')" == 1 ]]
echo "OK ($mode): Admin hardened defaults; legacy opt-out; Service port, ingress and OAuth2-proxy preserved"
done
echo "All hardened Admin checks passed."
+100
View File
@@ -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."
+86
View File
@@ -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
+86
View File
@@ -0,0 +1,86 @@
#!/usr/bin/env bash
set -euo pipefail
CHART_DIR="${1:-charts/hub}"
extract_mcp() {
awk '
/^---/ { deployment=0; name=""; variable="" }
/^kind: Deployment$/ { deployment=1 }
deployment && /^ name:/ { name=$2 }
/- name: MCP_/ { variable=$3; next }
variable != "" && /value:/ {
value=$0
sub(/^[[:space:]]*value: /, "", value)
print (deployment ? name : "non-deployment"), variable, value
variable=""
}
' | sort
}
assert_mcp() {
local rendered="$1" expected="$2" actual
actual="$(printf '%s\n' "$rendered" | extract_mcp)"
if [[ "$actual" != "$expected" ]]; then
printf 'Unexpected MCP environment:\n%s\nExpected:\n%s\n' "$actual" "$expected" >&2
return 1
fi
}
defaults="$(printf '%s\n' 'hub-api MCP_ALLOWED_ORIGINS ""' 'hub-api MCP_ENABLED "false"')"
enabled="$(printf '%s\n' \
'hub-api MCP_ALLOWED_ORIGINS "https://agent.example.com,http://localhost:4200"' \
'hub-api MCP_ENABLED "true"')"
flags=(
--set kerberoshub.api.mcp.enabled=true
--set-json 'kerberoshub.api.mcp.allowedOrigins=["https://agent.example.com","http://localhost:4200"]'
)
for mode in all ui pipeline; do
default_render="$(helm template hub "$CHART_DIR" --set "mode=$mode")"
configured_render="$(helm template hub "$CHART_DIR" --set "mode=$mode" "${flags[@]}")"
helm lint "$CHART_DIR" --quiet --set "mode=$mode" "${flags[@]}"
if [[ "$mode" == pipeline ]]; then
assert_mcp "$default_render" ""
assert_mcp "$configured_render" ""
[[ "$default_render" == "$configured_render" ]]
else
assert_mcp "$default_render" "$defaults"
assert_mcp "$configured_render" "$enabled"
rest_default="$(grep -A1 -- '- name: CORS_ALLOWED_ORIGINS' <<< "$default_render")"
rest_configured="$(grep -A1 -- '- name: CORS_ALLOWED_ORIGINS' <<< "$configured_render")"
[[ "$rest_default" == "$rest_configured" ]]
fi
echo "OK ($mode): MCP defaults, overrides, API-only environment and REST CORS isolation"
done
assert_mcp "$(helm template hub "$CHART_DIR" --set kerberoshub.api.mcp.enabled=true)" \
"$(printf '%s\n' 'hub-api MCP_ALLOWED_ORIGINS ""' 'hub-api MCP_ENABLED "true"')"
assert_mcp "$(helm template hub "$CHART_DIR" --set kerberoshub.api.mcp=null)" "$defaults"
assert_mcp "$(helm template hub "$CHART_DIR" "${flags[@]}" --set kerberoshub.api.mcp.enabled=false)" \
"$(printf '%s\n' 'hub-api MCP_ALLOWED_ORIGINS "https://agent.example.com,http://localhost:4200"' 'hub-api MCP_ENABLED "false"')"
expect_invalid() {
local output
if output="$(helm template hub "$CHART_DIR" "$@" 2>&1)"; then
echo "Invalid MCP settings unexpectedly rendered: $*" >&2
return 1
fi
if [[ "$output" != *"mcp"* ]]; then
printf 'Rendering failed for an unexpected reason:\n%s\n' "$output" >&2
return 1
fi
}
expect_invalid --set-string kerberoshub.api.mcp.enabled=true
expect_invalid --set-string kerberoshub.api.mcp.allowedOrigins=https://agent.example.com
expect_invalid --set-string kerberoshub.api.mcp.origin=https://agent.example.com
for origin in 'https://*.example.com' 'https://agent.example.com/' \
'https://agent.example.com/path' 'https://agent.example.com?query' \
'https://agent.example.com#fragment' 'https://user@agent.example.com' \
'https://agent.example.com,https://other.example.com' 'https://bad host' 'ftp://agent.example.com'; do
expect_invalid --set-json "kerberoshub.api.mcp.allowedOrigins=[\"$origin\"]"
done
expect_invalid --set-json 'kerberoshub.api.mcp.allowedOrigins=["https://agent.example.com","https://agent.example.com"]'
echo "All Hub MCP configuration checks passed."
+130
View File
@@ -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."
@@ -51,21 +51,21 @@ expected="$(printf '%s\n' \
'pipe-counting 50' 'pipe-sequence 60' 'pipe-monitor 70' | sort)"
for mode in all pipeline; do
default_render="$(helm template hub "$CHART_DIR" --set "mode=$mode" --set kerberospipeline.sprite.enabled=true)"
default_render="$(helm template hub "$CHART_DIR" --set "mode=$mode" --set kerberospipeline.sprite.enabled=true --set kerberospipeline.dominantColor.enabled=true)"
assert_waves "$default_render" ""
if printf '%s\n' "$default_render" | grep -q '^ annotations: null'; then
echo "Empty annotations must be omitted" >&2
exit 1
fi
configured_render="$(helm template hub "$CHART_DIR" "${WAVE_FLAGS[@]}" --set "mode=$mode" --set kerberospipeline.sprite.enabled=true)"
configured_render="$(helm template hub "$CHART_DIR" "${WAVE_FLAGS[@]}" --set "mode=$mode" --set kerberospipeline.sprite.enabled=true --set kerberospipeline.dominantColor.enabled=true)"
assert_waves "$configured_render" "$expected"
echo "OK ($mode): defaults opt out; per-service values assign all ten Deployment waves"
done
disabled_sprite="$(helm template hub "$CHART_DIR" "${WAVE_FLAGS[@]}" --set mode=pipeline)"
assert_waves "$disabled_sprite" "$(printf '%s\n' "$expected" | grep -v '^pipe-sprite ')"
disabled_optional="$(helm template hub "$CHART_DIR" "${WAVE_FLAGS[@]}" --set mode=pipeline)"
assert_waves "$disabled_optional" "$(printf '%s\n' "$expected" | grep -v -e '^pipe-sprite ' -e '^pipe-dominantcolor ')"
ui_render="$(helm template hub "$CHART_DIR" "${WAVE_FLAGS[@]}" --set mode=ui --set kerberospipeline.sprite.enabled=true)"
ui_render="$(helm template hub "$CHART_DIR" "${WAVE_FLAGS[@]}" --set mode=ui --set kerberospipeline.sprite.enabled=true --set kerberospipeline.dominantColor.enabled=true)"
assert_waves "$ui_render" ""
# Exercise zero/negative strings and an unrelated annotation on the same map.
+54
View File
@@ -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