The Guardian is the Nubus authorization engine. It runs Cerbos as the policy decision point (PDP) and evaluates access requests against a set of YAML policies.
The release deliverable on UCS is the univention-guardian-server Debian package.
It runs Cerbos as a systemd-managed container on a UCS server and includes
example policies; products deliver their own policies as policy bundles
registered in LDAP (see Policies). The package is released through the regular UCS errata
process and is also published as the univention-guardian App Center component,
so other apps can depend on it via RequiredAppsInDomain = univention-guardian.
Cerbos exposes an HTTP API on 3592 and a gRPC API on 3593, published on the
host loopback interface and reachable from containers on the shared guardian
Docker network.
On Nubus for Kubernetes,
the same engine is deployed by the guardian Helm chart in
helm/guardian/,
a subchart of the Nubus umbrella chart.
It needs no deployment of its own;
operators enable it in their Nubus values.
See Nubus for Kubernetes.
Migration from OPA. The Guardian previously used Open Policy Agent (OPA) together with a Management API, an Authorization API and a web UI. Those components have been retired in favour of the Cerbos package. The last commit containing the full OPA source is tagged
v3.0.9-opa-final.No upgrade path from the OPA-based Guardian is supported. The Cerbos package is a fresh install; there is no automated migration of policies, roles or data from an existing OPA deployment.
- This file - install, operate and configure the package, manage its policies, and call it from your own service.
docs/architecture.md: design decisions, component layout and design concepts not yet implemented.docs/policy-bundles.md: how to author and register your own policies from another app or package.docs/kubernetes-policies.md: every way a policy reaches Cerbos in Nubus for Kubernetes.helm/guardian/README.md: the chart's values, generated fromvalues.yaml.README.dev.md: building, testing and releasing the package (internal developer workflows).
Install the univention-guardian App Center component through the UCS App
Center, or from the command line:
univention-app install univention-guardianThe package is intended for Primary and Backup Directory Nodes; the App Center
component restricts the app to these roles. The systemd unit
univention-guardian-server.service runs the docker-compose stack as a
long-lived process and auto-starts at install; Restart=on-failure recovers
from container exits.
The following requests to Cerbos's HTTP API confirm that it loads policies and returns decisions — one expected allow and one expected deny:
# Same-app: alice (guardian:myapp-admin) on a myapp resource -> EFFECT_ALLOW
curl -s http://127.0.0.1:3592/api/check/resources \
-H 'Content-Type: application/json' \
-d '{
"requestId": "r1",
"principal": {"id": "alice", "roles": ["guardian:myapp-admin"]},
"resources": [{
"resource": {"id": "x", "kind": "guardian.management_api",
"attr": {"app_name": "myapp"}},
"actions": ["read_resource"]
}]
}'
# Cross-app: same alice on an otherapp resource -> EFFECT_DENY
curl -s http://127.0.0.1:3592/api/check/resources \
-H 'Content-Type: application/json' \
-d '{
"requestId": "r2",
"principal": {"id": "alice", "roles": ["guardian:myapp-admin"]},
"resources": [{
"resource": {"id": "y", "kind": "guardian.management_api",
"attr": {"app_name": "otherapp"}},
"actions": ["read_resource"]
}]
}'A native host process uses the published loopback port, http://127.0.0.1:3592.
A container cannot reach that interface, so it joins the shared guardian
network instead and addresses Cerbos by its DNS name:
# your service's docker-compose.yaml
services:
my-service:
environment:
CERBOS_URL: http://cerbos:3592
networks:
- guardian
networks:
guardian:
external: trueFor a single-container App Center app, set this UCR variable from the app's
preinst, which runs before the container is started. --network guardian is
then added to the container's docker parameters:
ucr set appcenter/apps/<app-id>/docker/params='--network guardian'The package owns two UCR variables. Setting these will restart Cerbos.
| Variable | Default | Notes |
|---|---|---|
guardian/cerbos/log-level |
WARN |
One of DEBUG, INFO, WARN, ERROR. |
guardian/cerbos/audit-logging/enabled |
false |
Writes Cerbos access and decision logs to stdout. |
All other settings (image pin, mount paths, listener addresses) are fixed in the
UCR-templated /usr/share/univention-guardian-server/docker-compose.yaml and
config/cerbos.yaml.
Policy delivery differs by platform, and neither mechanism exists on the other.
On UCS, policies are files below
/usr/share/univention-guardian-server/policies/,
distributed across the domain as LDAP policy bundles;
that is what this section describes.
In Nubus for Kubernetes, each source is a mounted subdirectory of /policies,
shipped as a packaged integration — see
docs/kubernetes-policies.md.
Cerbos loads every policy under
/usr/share/univention-guardian-server/policies/ recursively. Policies reach a
server two ways:
- Included with the package.
policies/default/andpolicies/examples/contain illustrative policies. These are example content, not a finalized policy set. - Domain-distributed bundles. Any app or package can register its own
policies as a
settings/dataobject in LDAP. The included listener (cerbos-policies.py) installs each bundle into a per-app subdirectory (policies/<app>/) on every server running Cerbos, validates it withcerbos compile, and restarts Cerbos to apply it. Seedocs/policy-bundles.md.
Write policies in the Cerbos policy language. Upstream documents the format: resource policies, derived roles and conditions.
Two constraints of the on-disk model:
- No hot-reload. Cerbos runs with
watchForChanges: false, so editing a YAML file has no effect until Cerbos restarts. Manual on-disk edits are also lost on the next package upgrade. - Do not name a policy file
*_test.yaml. Cerbos treats those as test files, not policies, and silently ignores them at runtime.
Cerbos has no shadow-override semantics. Two policies for the same resource and
version raise a duplicate policy definitions error and one of them is
discarded. Your policies can add resources and actions, but they cannot replace
a policy that another bundle already defines.
List what a server currently loads:
ls -R /usr/share/univention-guardian-server/policies/Each subdirectory is one source. default/ and examples/ come from the
package. Every other subdirectory is named after the app value of a
registered bundle.
To try a policy on a single server, copy it into a per-app subdirectory and restart Cerbos:
mkdir -p /usr/share/univention-guardian-server/policies/<app-name>/
cp my_policy.yaml /usr/share/univention-guardian-server/policies/<app-name>/
systemctl restart univention-guardian-server.serviceUse this for local experiments only. The next package upgrade removes the file, and the change reaches no other server in the domain. Register a policy bundle once the policy is ready.
Compile the policy directory and run its test suites. This needs no UCS server:
docker run --rm -v "$PWD/my-policies:/policies" \
ghcr.io/cerbos/cerbos:0.54.0 compile /policiescerbos compile reports syntax and reference errors, and it runs every
*_test.yaml file it finds. A successful compile proves that the policy is
valid, not that it grants what you intend. Write Cerbos tests for the decisions
that matter. See
validating and testing policies.
The same compile step runs on the server. The listener rejects a bundle that does not compile and keeps the previous policies in place.
Read the Cerbos log to see which policies loaded and why a policy was skipped:
journalctl -u univention-guardian-server.serviceRead the listener log to see whether a bundle was installed or rejected:
grep cerbos-policies /var/log/univention/listener.logSet guardian/cerbos/log-level to DEBUG for more detail, and
guardian/cerbos/audit-logging/enabled to true to log the requests and the
decisions. Reset both when you finish. Debug logs and audit logs contain
request payloads.
The guardian chart deploys one Cerbos Deployment
and one ClusterIP Service;
helm/guardian/README.md
documents every value.
| Address | |
|---|---|
| HTTP | http://<release>-guardian:3592 |
| gRPC | <release>-guardian:3593 |
The Service is named after the release and the chart,
so a nameOverride changes it;
kubectl -n <namespace> get svc shows the actual name.
From another namespace, append .<namespace>.svc.cluster.local.
Everything Integrating your service says applies
here too, with the client pointed at the Service name instead of 127.0.0.1.
Cerbos runs without TLS in the cluster and authenticates nobody,
so any pod that reaches either port can ask for any decision;
restrict access with a NetworkPolicy if the cluster does not already.
The chart ships no policies of its own, and Cerbos denies by default,
so a fresh deployment authorizes nothing.
The primary way to ship a policy set is a
packaged integration
carrying policy files under the guardian-policies plugin type,
the Kubernetes counterpart of the LDAP policy bundles used on UCS.
The chart values and another component's ConfigMap are the other two ways;
docs/kubernetes-policies.md covers all three.
Your service is the policy enforcement point (PEP). It collects the facts about a request and asks Cerbos, the policy decision point (PDP), for a decision. Your service then enforces that decision.
On UCS, Cerbos binds to localhost only.
Your service must run on the same UCS server as the Guardian package;
there is no remote access to the PDP.
In a cluster it is the Service that scopes access instead — see
Reach Cerbos from another pod.
Use an official Cerbos SDK rather than raw HTTP. The SDKs handle connection reuse, retries and the request format. Cerbos publishes SDKs for Go, Java, JavaScript, .NET, PHP, Python, Ruby and Rust. See the Cerbos ecosystem.
Prefer the gRPC API on port 3593. It is the faster interface and the one upstream recommends for new code. Use the HTTP API on port 3592 when your language has no SDK, or for shell-based checks.
The Python SDK (pip install cerbos) connects like this:
from cerbos.sdk.grpc.client import CerbosClient
from cerbos.engine.v1 import engine_pb2
from google.protobuf.struct_pb2 import Value
principal = engine_pb2.Principal(
id="alice",
roles={"guardian:myapp-admin"},
)
resource = engine_pb2.Resource(
id="x",
kind="guardian.management_api",
attr={"app_name": Value(string_value="myapp")},
)
with CerbosClient("127.0.0.1:3593", tls_verify=False) as client:
if not client.is_allowed("read_resource", principal, resource):
raise PermissionError("read_resource denied")Cerbos runs without TLS here, because it is reachable on the loopback interface
only. tls_verify=False selects a plaintext channel.
Cerbos never reads LDAP, a database or any other source. It decides on the content of the request alone. Your service must supply every attribute that your policies evaluate.
Cerbos also trusts what you send. It does not authenticate the end user and it does not verify the roles in the request. Your service must authenticate the user and resolve the roles before it asks for a decision.
A decision request carries:
principal: the actor. Anid, a list ofroles, and free-formattr.resources: the objects to check. Each has akindthat selects the resource policy, anid, free-formattr, and theactionsto check.
Read the request and response format in the Cerbos API reference.
The response returns one result per resource, in request order. Each result maps
every requested action to EFFECT_ALLOW or EFFECT_DENY.
Treat any value other than EFFECT_ALLOW as a deny. Do not infer an allow from
a missing action or an empty result.
Set includeMeta while you develop a policy. The response then names the
matched policy, the matched scope and the effective derived roles. Turn it off
in production.
The Guardian runs Cerbos with schema.enforcement: reject. If a policy declares
a schema and your request does not match it, Cerbos returns validationErrors
with the offending path and message, and denies the action. Log these errors.
They indicate a bug in the caller, not a policy decision.
One request can carry many resources and many actions. Ask once for a whole page of objects instead of once per object.
The Guardian raises the Cerbos request limits to 500 resources per request and 500 actions per resource. The upstream defaults are 50 and 50.
To filter a large collection, do not check every row. Call
PlanResources instead. Cerbos returns a query plan that says
KIND_ALWAYS_ALLOWED, KIND_ALWAYS_DENIED, or a condition that you translate
into a query filter. See
filtering resources.
Cerbos restarts when an administrator sets one of the UCR variables, and when the listener applies a new policy bundle. Requests fail during a restart.
Retry a failed call, then deny if it still fails. Never allow an action because the PDP is unreachable. The SDKs retry transient gRPC failures by default.
Your service needs its own resource policies. Do not rely on the policies that come with the package. Those are illustrative and will be replaced.
Package your policies as a policy bundle and register them from your join
script. The listener then installs them on every server in the domain that runs
Cerbos. See docs/policy-bundles.md.
In Nubus for Kubernetes, ship them as a packaged integration instead:
docs/kubernetes-policies.md.
- No transport authentication. Cerbos is bound to localhost only, but any caller on the server can reach it. In a cluster the Service is reachable from any pod, and the chart ships no NetworkPolicy to narrow that.
- No server-role check in the deb. Install it only on Primary or Backup
Directory Nodes (the App Center component enforces this; installing the
.debdirectly does not).
See docs/architecture.md for the reasoning behind
these and the planned work.