Identity and access control
Enduro optionally supports external OpenID Connect (OIDC) compatible providers for authentication and access control. Users can authenticate against the external provider from the dashboard to receive an access token that will be sent to the API on each request.
Enduro uses Attribute Based Access Control (ABAC) to determine the actions and resources to which an authenticated user has access. It looks for a configurable claim in the access token to know the attributes assigned to the user in their external provider.
This section explains how to configure the OIDC provider in Enduro's API and dashboard.
API configuration
Below is a self-documented API section from an Enduro configuration file in TOML format:
[api]
# TCP address for the server to listen on, in the form "host:port".
listen = "0.0.0.0:9000"
# Enable debug mode.
debug = false
# Allowed CORS origin URL.
corsOrigin = "http://localhost"
[api.auth]
# Enable API authentication. OIDC is the only protocol supported at the
# moment. When enabled the API verifies the access token submitted with
# each request. Clients are responsible for obtaining an access token
# from a configured OIDC provider below.
enabled = true
# Multiple OIDC providers are supported. Add one `[[api.auth.oidc]]` table
# per provider/client pair. For each provider/client pair add an
# `[api.auth.oidc.abac]` section if ABAC is needed. Example:
#
# [[api.auth.oidc]]
# providerURL = "http://keycloak:7470/realms/artefactual"
# clientID = "enduro"
# [api.auth.oidc.abac]
# enabled = true
# claimPath = "attributes.enduro"
# claimPathSeparator = "."
#
# [[api.auth.oidc]]
# providerURL = "http://keycloak:7470/realms/artefactual-internal"
# clientID = "enduro-s2s"
[[api.auth.oidc]]
# OIDC provider URL. Required when auth. is enabled.
providerURL = "http://keycloak:7470/realms/artefactual"
# OIDC client ID. The client ID must be included in the `aud` claim of
# the access token. Required when auth. is enabled.
clientID = "enduro"
# Do not check if the `email_verified` claim is present and set to `true`.
skipEmailVerifiedCheck = false
[api.auth.oidc.abac]
# Enable Attribute Based Access Control (ABAC). If enabled, the API will
# check a configurable multivalue claim against required attributes based
# on each endpoint configuration.
enabled = true
# Claim path of the Enduro attributes within the access token. If the claim
# path is nested then include all fields separated by `claimPathSeparator`
# (see below). E.g. "attributes.enduro" with `claimPathSeparator = "."`.
# Required when ABAC is enabled.
claimPath = "enduro"
# Separator used to split the claim path fields. The default value of "" will
# try to match the claim path as-is to a top-level field from the access token.
claimPathSeparator = ""
# Add a prefix to filter the values of the configured claim. If the claim
# contains values unrelated to Enduro's ABAC, the values relevant to Enduro
# should be prefixed so they are the only values used for access control.
# For example, a claim with values ["enduro:*", "unrelated"] will be filtered
# to a value of ["*"] when `claimValuePrefix = "enduro:"`. The default "" will
# not filter any value.
claimValuePrefix = ""
# Consider the values obtained from the claim as roles and use the `rolesMapping`
# config below to map them to Enduro attributes.
useRoles = false
# A JSON formatted string specifying a mapping from expected roles to Enduro
# attributes. JSON format:
# {
# "role1": ["attribute1", "atrribute2"],
# "role2": ["attribute1", "atrribute2", "attribute3", "atrribute4"]
# }
# Example:
# rolesMapping = '{"admin": ["*"], "operator": ["ingest:sips:list", "ingest:sips:read", "ingest:sips:upload", "ingest:sips:workflows:list"], "readonly": ["ingest:sips:list", "ingest:sips:read", "ingest:sips:workflows:list"]}'
rolesMapping = ""
[api.auth.ticket.redis]
# Redis URI to store a ticket used to set a websocket connection.
address = "redis://redis.enduro-sdps:6379"
# Prefix used as part of the ticket keys in Redis.
prefix = "enduro"
Ingest storage client configuration
Use the following section to configure ingest as an authenticated client of the storage API using the OIDC client credentials flow. Tokens generated by this OIDC provider must be verified by at least one of the providers from the full API OIDC configuration.
[ingest.storage]
# Storage API host:port for ingest client requests.
address = "enduro.enduro-sdps:9002"
# Default destination location for permanent storage workflows.
defaultPermanentLocationId = "f2cc963f-c14d-4eaa-b950-bd207189a1f1"
[ingest.storage.oidc]
# Enable service to service OIDC authentication for storage API requests.
enabled = true
# OIDC provider URL used for token endpoint discovery.
providerURL = "http://keycloak:7470/realms/artefactual"
# Optional token endpoint URL. If set, discovery is skipped.
tokenURL = ""
# OIDC client credentials used for client_credentials token requests.
clientID = "enduro-s2s"
clientSecret = "replace-me"
# Optional scopes requested during token retrieval.
scopes = ""
# Optional audience to include in token endpoint params.
audience = ""
# Refresh token before expiry to avoid edge races.
tokenExpiryLeeway = "30s"
# Retry settings for transient token endpoint failures.
retryMaxAttempts = 3
retryInitialInterval = "500ms"
retryMaxInterval = "2s"
retryBackoffCoefficient = 2.0
Dashboard configuration
The following environment variables can be used to configure the dashboard:
VITE_OIDC_ENABLED
VITE_OIDC_BASE_URL
VITE_OIDC_AUTHORITY
VITE_OIDC_CLIENT_ID
VITE_OIDC_SCOPES
VITE_OIDC_ABAC_ENABLED
VITE_OIDC_ABAC_CLAIM_PATH
VITE_OIDC_ABAC_CLAIM_PATH_SEPARATOR
VITE_OIDC_ABAC_CLAIM_VALUE_PREFIX
VITE_OIDC_ABAC_USE_ROLES
VITE_OIDC_ABAC_ROLES_MAPPING
Important
The VITE_OIDC_AUTHORITY environment variable is included in the Content
Security Policy's connect-src directive. If this URL contains a path,
ensure it ends with a slash (/) to allow connections to the necessary
OIDC endpoints for authentication.
They must match the ones configured in the API. VITE_OIDC_AUTHORITY has to be
the same OIDC provider URL and VITE_OIDC_CLIENT_ID needs to be the same or a
trusted client. This client (or the one used in the API configuration, if they
are not the same) must be included in the aud claim from the access token.
VITE_OIDC_BASE_URL will be used to generate the signin and signout callback
URLs, to set them in the OIDC provider for this client, they will be:
- Signin:
VITE_OIDC_BASE_URL+/user/signin-callback - Signout:
VITE_OIDC_BASE_URL+/user/signout-callback
The authorization flow will request the openid email profile scopes by
default. If needed, VITE_OIDC_SCOPES can be used to replace those scopes.
VITE_OIDC_EXTRA_QUERY_PARAMS can be set to specify further query string
parameters to be including in the authorization request. E.g, when using Azure
AD a resource parameter is required, or using Auth0 you may need to send an
audience client ID. The expected format is key-value pairs separated by =
(audience=client-id), if more than one parameter is needed they can be added
separated by comma (audience=client-id,key=value).
The ABAC variables will work in the same way as they do in the API, they are explained in detail in the API configuration comments above.
These environment variables can be set at build time, or they can be replaced in
the final assets. For example, the following script uses envsubst to do the
replacement:
#!/usr/bin/env bash
ENDURO_DASHBOARD_ROOT=/usr/lib/enduro-dashboard
TMP_DIR=/tmp/inject_vite_envs
mkdir $TMP_DIR
# Get a comma delimited list of env var names starting with "VITE"
VITE_ENVS=$(printenv | awk -F= '$1 ~ /^VITE/ {print $1}' | sed 's/^/\$/g' | paste -sd,);
echo "Vite envs: ${VITE_ENVS}"
# Inject environment variables into distribution files
for file in $ENDURO_DASHBOARD_ROOT/assets/*.js;
do
echo "Inject VITE environment variables into $(basename $file)"
envsubst $VITE_ENVS < $file > $TMP_DIR/$(basename $file)
cp $TMP_DIR/$(basename $file) $file
done
rm -rf $TMP_DIR
Required attributes
The following table shows the attributes required for each API endpoint. The
attributes allow a wildcard hierarchical declaration. For example,
ingest:sips:* will give access to endpoints requiring ingest:sips:list,
ingest:sips:read, etc. The * attribute will provide full access to the API.
In order to stablish a Websocket connection from the browser, the
GET /ingest/monitor and GET /storage/monitor endpoints require a cookie
obtained from the POST /ingest/monitor and POST /storage/monitor endpoints.
User claims are stored internally and the attributes are checked before sending
events to the connection.
Similarly, to be able to stream a SIP, AIP or AIP deletion report download from
the browser, the GET endpoints require a cookie obtained from the POST
endpoints.
| Method | Endpoint | Attributes |
|---|---|---|
| GET | /about | - |
| POST | /ingest/batches | ingest:batches:create |
| GET | /ingest/batches | ingest:batches:list |
| GET | /ingest/batches/{uuid} | ingest:batches:read |
| POST | /ingest/batches/{uuid}/review | ingest:batches:review |
| GET | /ingest/monitor | - |
| POST | /ingest/monitor | - |
| GET | /ingest/sip-sources/{uuid}/objects | ingest:sipsources:objects:list |
| GET | /ingest/sips | ingest:sips:list |
| POST | /ingest/sips | ingest:sips:create |
| POST | /ingest/sips/upload | ingest:sips:upload |
| GET | /ingest/sips/{uuid} | ingest:sips:read |
| POST | /ingest/sips/{uuid}/confirm | ingest:sips:review |
| GET | /ingest/sips/{uuid}/decision | ingest:sips:decision |
| POST | /ingest/sips/{uuid}/decision | ingest:sips:decision |
| GET | /ingest/sips/{uuid}/download | - |
| POST | /ingest/sips/{uuid}/download | ingest:sips:download |
| POST | /ingest/sips/{uuid}/reject | ingest:sips:review |
| GET | /ingest/sips/{uuid}/workflows | ingest:sips:workflows:list |
| GET | /ingest/users | ingest:users:list |
| GET | /storage/aips | storage:aips:list |
| POST | /storage/aips | storage:aips:create |
| GET | /storage/aips/{uuid} | storage:aips:read |
| POST | /storage/aips/{uuid}/deletion-auto | storage:aips:deletion:auto |
| POST | /storage/aips/{uuid}/deletion-cancel | storage:aips:deletion:request |
| GET | /storage/aips/{uuid}/deletion-report | - |
| POST | /storage/aips/{uuid}/deletion-report | storage:aips:deletion:report |
| POST | /storage/aips/{uuid}/deletion-request | storage:aips:deletion:request |
| POST | /storage/aips/{uuid}/deletion-review | storage:aips:deletion:review |
| GET | /storage/aips/{uuid}/download | - |
| POST | /storage/aips/{uuid}/download | storage:aips:download |
| POST | /storage/aips/{uuid}/reject | storage:aips:review |
| GET | /storage/aips/{uuid}/store | storage:aips:move |
| POST | /storage/aips/{uuid}/store | storage:aips:move |
| GET | /storage/aips/{uuid}/workflows | storage:aips:workflows:list |
| GET | /storage/locations | storage:locations:list |
| POST | /storage/locations | storage:locations:create |
| GET | /storage/locations/{uuid} | storage:locations:read |
| GET | /storage/locations/{uuid}/aips | storage:locations:aips:list |
| GET | /storage/monitor | - |
| POST | /storage/monitor | - |