Skip to content

Ship the DLS OPA access policy in the image instead of a ConfigMap #5

Open

Description

@gilesknap

b01-1's tiled loads DiamondOpenPolicyAgentAuthorizationPolicy (the class that asks OPA whether a token may read or write a node, using the proposal/visit/beamline in its access blob) from a ConfigMap at run time:

  • services/b01-1-tiled/templates/configmap.yaml in b01-1-services holds dls.py and an empty __init__.py
  • the chart mounts it at /deploy/access_policy and sets PYTHONPATH=/deploy/ so tiled can import access_policy.dls

Runtime-loaded code like this escapes the image's tests and versioning, and editing the ConfigMap silently changes running code. It belongs in this package like the visr router.

To do:

  • add the class as dls_tiled.access_policy with tests (it subclasses tiled.access_control.access_policies.ExternalPolicyDecisionPoint, so check it against the tiled version in TILED_VERSION)
  • release
  • in b01-1-services, remove the ConfigMap template, its volume and mount, and PYTHONPATH, and reference dls_tiled.access_policy:DiamondOpenPolicyAgentAuthorizationPolicy when access_control is switched on

Nothing calls the policy today: access_control is commented out in b01-1's tiled config, and the scopes_map there grants every valid identity token write access as a stop-gap. Switching on OPA with this policy is what replaces that.

Activity

  1. gilesknap commented on Oct 6, 2026

    @gilesknap
    ContributorAuthor

    Findings

    b01-1's ConfigMap holds an old version of the class that does not work on tiled 0.2.18

    The class started in blueapi's system tests (tests/system_tests/services/tiled_config/dls.py), and beamline repos have copied it from there at different times:

    Copy Version PrincipalType used tiled it runs on
    b01-1-services before blueapi's "update diamond access policy" (2026-03): overrides only build_input, endpoint session/write_to_beamline_visit external 0.2.18 (OPA off)
    p46/p47/p48/p49-services current: adds _check_principal, overrides init_node/modify_node/filters, ALL_ACCESS for ["*"], integer session tags external 0.2.7 (OPA on in p46)
    t11-services current; same as blueapi user 0.2.18
    blueapi system tests current, updated for tiled 0.2.12 user 0.2.12

    In tiled 0.2.18 PrincipalType has only user and service (external was renamed to user somewhere between 0.2.7 and 0.2.12). With b01-1's copy, every request that reaches build_input would fail with AttributeError. Nobody has hit this because access_control is commented out. The p46–p49 copies work today only because those beamlines run tiled 0.2.7, and they will break the same way when they upgrade.

    The p4x and t11 copies differ only in external vs user (and t11's header comment and ConfigMap name). p46, p48 and p49 are byte-identical; p47 differs by a trailing blank line.

    b01-1 has no OPA service

    The commented config points at http://b01-1-opa:8181/v1/data/diamond/policy/, but b01-1-services has no OPA service. p46 has p46-opa (reached via p46-tiled-authz), and t11 has t11-opa. The OPA endpoint names the current class calls (tiled/user_session, tiled/user_sessions, tiled/scopes, tiled/modify_session) must match the policy that OPA serves.

    Behaviour to be aware of

    _check_principal raises 401 for None (anonymous) principals and for service principals (API keys). With OPA on, allow_anonymous_access no longer gives access, and blueapi has to write with a user token.

    Proposed fix

    In this repo:

    1. Port the current class (blueapi's / t11's, using PrincipalType.user), not b01-1's copy, as src/dls_tiled/access_policy.py. Config reference: dls_tiled.access_policy:DiamondOpenPolicyAgentAuthorizationPolicy.
    2. Unit tests with _get_external_decision stubbed (no network, no new HTTP-mocking dependency):
      • build_input: JSON tag (proposal, visit and beamline), integer session tag, no access blob, token included only for user principals
      • _check_principal rejects None and service with 401
      • init_node turns the OPA result into {"tags": [result]} and raises when OPA returns nothing
      • filters: ["*"] gives ALL_ACCESS, a list gives AccessBlobFilter, no result gives NO_ACCESS
      • constructing the class succeeds, so it still satisfies tiled's abstract base class
    3. Add dls_tiled.access_policy to the Dockerfile's import check. Don't enable the policy in the container smoke test: it needs OPA, and it returns 401 for anonymous requests, which would break the visr check.
    4. README section on the policy and its access_control: config.

    Follow-ups (separate issues)

    • b01-1-services: switching on access control also needs an OPA service (based on p46-opa) and a rethink of the loose scopes_map. Removing the ConfigMap, its volume and mount, and PYTHONPATH can happen as soon as this is released.
    • t11-services: already on tiled 0.2.18, so it can drop its ConfigMap and use the class from this package.
    • p46–p49-services: need external changed to user when they upgrade tiled; moving to this package at that point fixes it.
    • blueapi: its system tests keep their own copy of the class. In the long run they could run against the dls-tiled image, so that there is only one copy.
  2. gilesknap commented on Oct 6, 2026

    @gilesknap
    ContributorAuthor

    Where the code comes from

    DiamondOpenPolicyAgentAuthorizationPolicy started as a test fixture in blueapi's system tests (tests/system_tests/services/tiled_config/dls.py), written by @ZohebShaikh. blueapi doesn't ship it as a package, so each deployment has copied it.

    Changes in blueapi:

    Date PR Change
    2026-01-06 DiamondLightSource/blueapi#1287 first version, overriding only build_input, in the new OPA + Keycloak + tiled system test
    2026-02-24 DiamondLightSource/blueapi#1398 use a service account for tiled insertion
    2026-03-11 DiamondLightSource/blueapi#1431 current form: _check_principal, overridden init_node/modify_node/filters, new OPA endpoint names
    2026-06-24 DiamondLightSource/blueapi#1488 tiled 0.2.12: PrincipalType.external becomes PrincipalType.user

    When each beamline copied it:

    • b01-1-services (GitLab), 2026-01-12, "Add configmap": the January version, not updated since
    • p46-services, 2026-03-11, blueapi and tiled update epics-containers/p46-services#148: the March version, the same day as blueapi#1431. p47–p49 were copied from p46 and still use external, because they predate the tiled 0.2.12 rename.
    • t11-services, copied from p46, then updated for tiled 0.2.18 on 2026-09-18 (external → user), which makes it match blueapi's current version

    So the code has no single owner: it's a test fixture copied into six deployment repos at different points in its history. Moving it into dls_tiled gives it a single owner, with versions and tests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions