Skip to main content

Data set permissions

AdministratorsIT security
In one minute

Read and write access is granted per data set, through organization groups in your identity provider.

Two properties are worth internalising: read and write are independent, a write grant does not imply read, and denied reads are silently filtered out rather than producing an error.

What permissions cover

Data set permissions gate the four data-bearing entity types: resources, time series, events and files.

ActionRequires
Listing, searching, readingRead on the entity's data set
Receiving subscription deliveriesRead, the same boundary: push delivery is filtered like any other read
Creating, updating, deletingWrite on the entity's data set
Adding datapoints, uploading filesWrite on the target data set
Moving an entity to another data setWrite on both the source and the destination

That last row is the one people trip over. Moving something between data sets is simultaneously a delete and a create as far as access is concerned, so it needs both sides.

How a grant is expressed

Access is administered entirely in Keycloak, or in a customer directory federated into it. There is nothing about access to manage inside DataHub.

Per-data-set access comes from organization groups, named after the data set's external id:

/datasets/<externalId>/read
/datasets/<externalId>/write

Putting somebody in the group is the grant. A member of /datasets/plant_a/read can read everything in the plant_a data set.

Grants inherit down the hierarchy

A grant on a data set covers every data set beneath it. Data sets form a hierarchy, and access follows it downward.

This is what keeps the model manageable. You grant on a handful of roots rather than on hundreds of individual data sets, and anything created later underneath a granted root is covered automatically.

Read and write are independent

A write grant does not confer read.

Somebody who needs both on plant_a must be a member of /datasets/plant_a/read and /datasets/plant_a/write.

This is deliberate, and it enables the pattern described in users and access: an ingestion service account that can write measurements without being able to read anything back. If those credentials leak, the exposure is limited.

Access to everything stays a role

Blanket grants name no data set, so they remain ordinary realm roles:

RoleGrants
DATAHUB_ADMINRead and write every data set, the operator escape hatch
DATAHUB_DATASET_ALLRead and write every data set
DATAHUB_DATASET_READ_ALLRead every data set
DATAHUB_DATASET_WRITE_ALLWrite every data set

Managing the data sets themselves

Creating, updating or deleting a data set itself requires one of the all-data-sets write grants above, DATAHUB_DATASET_ALL, DATAHUB_DATASET_WRITE_ALL or DATAHUB_ADMIN. Grants on individual data sets never confer it, however many of them somebody holds.

The reason is that a data set is the unit access is granted on: renaming one, or moving it in the hierarchy, changes what every existing grant covers.

Granting access in Keycloak

Create the group inside the organization

In the organization representing the tenant, create /datasets/<externalId>/read or /datasets/<externalId>/write.

Make the person an organization member

Group membership means nothing until they belong to the organization itself.

Add them to the group

That is the whole grant.

Groups that do not match the pattern are ignored, so an organization's group tree can hold whatever else it needs.

Changes take effect within about a minute, rather than waiting for the user's token to expire, so revoking access does not mean asking somebody to sign out and back in.

Why groups rather than roles

Realm roles are global to the realm, but data sets belong to one tenant. A role naming a data set would mean something different in every tenant, and somebody who belongs to two tenants would carry it into both. An organization group is scoped to one organization by construction, and it can be delegated, so a tenant's own administrator can manage their team's access without realm-wide rights.

How denial behaves

Reads and writes are denied differently, and the difference is intentional.

Reads are filtered. List and search results simply omit entities in data sets the caller cannot read. No error is raised. This matches the platform's general contract that missing items are left out, and it avoids leaking the existence of data through error messages.

The practical consequence for support: "I cannot see the series my colleague can see" is almost always a permission, not a bug. Check the group membership before investigating anything else.

Direct reads of a single item return a clear denial, except for files. Fetching a specific resource by id, or starting a graph traversal at one, returns an explicit forbidden response. Files deliberately return "not found" instead, so that a denial cannot be used to confirm that a particular file exists.

Writes fail loudly. Create, update, delete, datapoint insertion and file upload all return an explicit forbidden response naming the data set and the permission that was missing, so the fix is obvious.

If the identity provider cannot be reached, requests are refused with a server error rather than quietly returning nothing. "We could not verify your permissions" is not "you have no permissions", and the platform will not let the two look alike.

Graph traversal is gated on the starting node only

Worth knowing before you rely on data sets to separate sensitive equipment.

Access is checked on the resource a traversal starts from. The connected network returned from there is not itself filtered by data set, so a caller who can read one resource can see resources it connects to even where those sit in data sets the caller cannot read directly.

Do not treat a data set boundary as a barrier inside a connected graph. If two areas must be genuinely separate, separate them in the model, or put them in different tenants.

Entities without a data set

Entities can exist without a data set, typically from an early import, or a create call that did not specify one. What happens then differs by type, and the difference matters:

TypeWith no data set
Resources, time series, eventsConservative. Only a caller holding an all-data-sets grant can see them.
Files and foldersPublic to every authenticated user. No data set means no restriction.

That file exception is the one to plan around. If you are relying on data sets to control who can read documents, make sure nothing lands without one, and check for existing files that have no data set assigned before you assume the boundary holds.

If data appears to be missing after an import, checking whether it landed without a data set is a good early diagnostic for the other three types. Assigning one brings it under the normal permission model.

Designing a permission scheme

Design the data set boundaries first

Permissions follow data sets, so the boundary decision is the access decision. Split by who should see it, not by what it is. Designing data set boundaries →

Grant on roots, not on leaves

Because grants inherit downward, a group on a top-level data set covers everything under it. Aim for a few groups, not one per data set.

Grant read and write separately, deliberately

Most people need read on many data sets and write on very few. Reflect that.

Keep the escape hatches rare and named

DATAHUB_ADMIN and the _ALL roles should have a small, reviewable list of holders.

Review quarterly

The group names carry the data set they grant, so the group list in your identity provider reads as an access report.

Where policies appear in the console

Each data set has a Policies tab, where lifecycle, access and requirement rules are recorded. Those are documented intent, not enforcement: access comes entirely from the identity-provider groups and roles above, so "who can see this data set" is answered by the group list, and the tab records what should govern it. Policies →

Go deeper