Lolly
Отвори приложението

Admission policy with Kubewarden

Бележка: Lolly Work

This guide covers the optional organisation service. Configuration, console and API paths refer to your Work instance. The packaged console keeps its release-matched copy of this guide.

This page is for a platform team that runs Kubewarden and wants admission policy for Lolly Work's pods. Lolly Work publishes what its pods need as data and a ready policy set built from published Kubewarden policies. Nothing is installed with the Helm chart: a cluster without Kubewarden runs Work unchanged, and the set applies only to namespaces you label. For running Kubewarden itself, and for rehearsing in audit mode first, see WebAssembly and Kubewarden choices.

Three files in the lolly-work repository make up the set:

FileWhat it holds
deploy/kubewarden/lolly-work-requirements.jsonWhat Work's pods need, generated from the Helm chart's templates, the render worker's egress rule and the release workflow
deploy/kubewarden/policies.yaml13 ClusterAdmissionPolicy objects that admit those pods and refuse everything else
deploy/kubewarden/options/render-worker-user-namespace.yamlApplied on top, only when the render worker runs in its own user namespace

What Work's pods need

Work has up to five kinds of container: the control plane, the migration Job, the optional render worker, and the optional copy steps that fill the pack and web app volumes from your images. With the chart's defaults, each of them:

NeedValue
User, group and fsGroup1000, with no supplemental groups
Root usernever (runAsNonRoot: true)
Capabilitiesdrop ALL, add none
Privilege escalation and privileged modeoff
Root filesystemread-only; scratch is an emptyDir at /tmp
SeccompRuntimeDefault
AppArmorunset, so the runtime's default applies
Volume typesconfigMap, emptyDir, secret (the worker's certificates and trust bundle), persistentVolumeClaim (pack or web app volumes)
Host network, PID, IPC, ports or user namespacenone
Kubernetes API accessnone; no ServiceAccount token is mounted

Some chart values change that list:

Chart valueWhat changes
renderWorker.seccompProfile: { type: Localhost, localhostProfile: lolly/chromium.json }The worker uses the reviewed Chromium seccomp profile installed on the node
renderWorker.appArmorProfile: { type: Localhost, localhostProfile: lolly-chromium }On AppArmor nodes, the worker uses the reviewed AppArmor profile
renderWorker.userNamespace.enabled: trueThe worker runs with hostUsers: false and adds SYS_ADMIN and SYS_CHROOT inside its own user namespace
pack.type or shell.type set to hostPathA hostPath volume, meant for a single node. The policy set refuses it
serviceAccount.automountServiceAccountToken: trueA projected token volume. The policy set refuses it
pack.image, shell.imageAn init container running your image

Images and their signatures. The two images are ghcr.io/lolly-tools/lolly-work-server and ghcr.io/lolly-tools/lolly-work-render-worker. Both are signed keyless with Sigstore by lolly-work's release workflow at a release tag:

FieldValue
Issuerhttps://token.actions.githubusercontent.com
Subjecthttps://github.com/lolly-tools/lolly-work/.github/workflows/release.yml@refs/tags/v<version>

The signatures are Sigstore bundles stored as OCI referrers, as cosign 3 writes them; there is no .sig tag. To check one by hand:

cosign verify ghcr.io/lolly-tools/lolly-work-server@sha256:<digest> \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  --certificate-identity-regexp '^https://github\.com/lolly-tools/lolly-work/\.github/workflows/release\.yml@refs/tags/v'

Network. The control plane accepts traffic on port 8787 and, by default, may reach anything: PostgreSQL, your identity provider, catalog sources, mail relays and the SIEM receiver. The render worker accepts connections from the control plane only, on port 8791. Its NetworkPolicy lets it reach cluster DNS and public addresses on ports 80 and 443, and its own code refuses the same private, shared, loopback, link-local (which includes cloud metadata at 169.254.169.254), documentation and multicast ranges, so a rendered page cannot reach the cluster or the metadata service. The requirements file lists every range.

The policy set

PolicyPublished policyWhat it enforces
lolly-work-privilegedpod-privileged v1.0.14No privileged container
lolly-work-privilege-escalationallow-privilege-escalation-psp v1.0.15allowPrivilegeEscalation: false in every container
lolly-work-capabilitiescapabilities-psp v1.0.15Drop ALL and add nothing, in every pod except the render worker
lolly-work-capabilities-render-workercapabilities-psp v1.0.15The same for the render worker
lolly-work-user-groupuser-group-psp v1.1.5UID and GID 1000, supplemental groups within 1000
lolly-work-fsgroupallowed-fsgroups-psp v1.0.14fsGroup 1000
lolly-work-host-namespaceshost-namespaces-psp v1.1.13No host network, PID, IPC or ports
lolly-work-volumesvolumes-psp v1.1.12configMap, emptyDir, persistentVolumeClaim and secret only
lolly-work-readonly-rootreadonly-root-filesystem-psp v1.0.15A read-only root filesystem in every container
lolly-work-seccompseccomp-psp v1.0.16RuntimeDefault, or Localhost with lolly/chromium.json
lolly-work-apparmorcel-policy v1.6.5Unset, RuntimeDefault, or Localhost with lolly-chromium, in the field and in the older annotation
lolly-work-image-sourcestrusted-repos v2.1.6Only the two Work images
lolly-work-image-signaturesverify-image-signatures v1.0.13Both images carry a signature from the release workflow; each pod runs the digest that was verified

Every module is pinned by tag and digest in policies.yaml. Two policies mutate pods: lolly-work-user-group fills an unset container UID or GID with 1000, and lolly-work-image-signatures replaces an image tag with the digest it verified. The others only admit or refuse.

Apply the set

Label the namespace Work runs in, apply the policies and wait for them:

kubectl label namespace lolly-work lolly.tools/policy-set=lolly-work
kubectl apply -f deploy/kubewarden/policies.yaml
kubectl wait --for=condition=PolicyActive clusteradmissionpolicy \
  -l app.kubernetes.io/part-of=lolly-work-policy-set --timeout=5m

The policies apply to every Pod in a labelled namespace, so keep PostgreSQL and other workloads in a namespace of their own, and let only cluster administrators change namespace labels. The Policy Server restarts whenever a policy changes; give it a minute before you test.

Check your values before installing

From a lolly-work checkout, the preflight script sends every pod the chart would create to the cluster as a server-side dry run, so each admission webhook answers and nothing is created:

helm template lolly-work deploy/helm -n lolly-work -f my-values.yaml \
  | node scripts/kubewarden-preflight.ts --namespace lolly-work --probes

With --probes it also sends nine copies of the control-plane pod, each breaking one rule: a privileged container, an added capability, the root user, a writable root filesystem, the host network, a hostPath volume, an unconfined seccomp profile, an unsigned image and an image outside the list. It prints ADMITTED or REFUSED with the policy's message for each, and exits with status 1 when a Work pod is refused or a probe is admitted.

Options

Behaviour of the published policies

Qualifying the set turned up four things about the published policies that shaped it:

Qualification

On 2026-10-27 the set ran on Kubewarden 1.38.2 (admission-controller chart 6.1.0) with K3s 1.35.5 in k3d, on arm64. Work 1.1.2 and its render worker, pinned by digest, were installed with PostgreSQL 17 in a separate namespace and the worker on the Localhost seccomp profile.

CheckResult
Migration Job, control plane and render workerAdmitted; both Deployments became ready, and the worker's readiness requires a launched, sandboxed Chromium
A privileged pod, created for realRefused by lolly-work-privileged
Work's own pod with the unsigned lolly-work-server:0.2.0 image, created for realRefused by lolly-work-image-signatures
A signature from the release workflow, against a policy expecting another workflowRefused
The nine preflight probesEach refused, by the policy for its rule
An unreviewed AppArmor profile on the workerRefused by lolly-work-apparmor
The user-namespace worker, without and with the option fileRefused, then admitted
A pod with the worker's label adding SYS_ADMIN without its own user namespaceRefused by the CEL rule

When the chart changes

node scripts/kubewarden-requirements.ts --write regenerates the requirements file from the chart. tests/kubewarden-policies.test.ts fails until the requirements match the chart and the policies agree with the requirements, so a chart change that needs a new volume type, capability or profile cannot pass review with the set out of date.