Configuration

Main Configuration File

The file OPAL_HOME/conf/opal-config.properties is to be edited to match your server needs.

HTTP Server Configuration

Opal web services and web application user interface can be accessed through HTTP or secured HTTP requests. The HTTP(S) connection ports can be configured.

Property

Description

org.obiba.opal.http.port

The port to use for listening for HTTP connections. Default value is 8080, -1 to disable.

org.obiba.opal.https.port

The port to use for listening for HTTPS connections. Default value is 8443, -1 to disable.

org.obiba.opal.maxIdleTime

The maximum time a single read/write HTTP operation can take in millis (default is 30000). See idleTimeout Jetty configuration.

org.obiba.opal.ssl.excludedProtocols

Specify the SSL/TLS protocols to be excluded. Usually SSLv3 will be excluded. Use commas for separating multiple protocol names. Default is no protocol is excluded (for legacy reason). See JSSE Provider documentation.

org.obiba.opal.ssl.includedCipherSuites

Specify which Cipher Suites to be included. Use commas for separating multiple cipher suites names. Default is all that is available. See JSSE Provider documentation.

org.obiba.opal.server.context-path

The context path when server is accessed at a subdirectory (for instance in http://example.org/opal the context path is /opal). Default is empty.

The HTTPS server requires a certificate. If none can be found Opal creates a default one to ensure that HTTPS is always available. It should be configured afterward, following the procedure described in HTTPS Configuration.

SSH Server Configuration

Opal is accessible using SSH clients: SFTP is available through SSH connections. The SSH connection port can be configured.

Property

Description

org.obiba.opal.ssh.port

The port to use for listening for SSH connections. Default value is 8022.

SMTP Server Configuration

Opal is able to send emails to notify that a rapport has been produced. To allow this, it is required to configuration to a SMTP server.

Property

Description

org.obiba.opal.smtp.host

The SMTP server host name.

org.obiba.opal.smtp.port

The SMTP server port number.

org.obiba.opal.smtp.from

The “From” email address when sending emails.

org.obiba.opal.smtp.auth

A flag to indicated if authentication against SMTP server is required. Allowed values are: true/false. Default is false (usually not required when server is in the same intranet).

org.obiba.opal.smtp.username

The SMTP user name to be authenticate (if authentication is activated).

org.obiba.opal.smtp.password

The SMTP user password (if authentication is activated).

Apps Configuration

External applications can be discovered or can self-register. The following settings the apps management defaults.

Property

Description

apps.registration.token

Apps self-registration default token. Default is empty (self-registration not allowed). When configured from Apps administration page, this value is overridden.

apps.registration.include

White list rule to accept an app self-registration: (java) regular expression applied to app’s server address. If not defined (default) all apps are filtered-in.

apps.registration.exclude

Black list rule to accept an app self-registration: (java) regular expression applied to app’s server address. If not defined (default) no app is filtered-out.

apps.discovery.interval

Apps discovery scheduling in milliseconds. Default is 10000.

apps.discovery.rock.hosts

Comma separated list of Rock R server URLs to discover on start up. Default is localhost:8085. See also R Server Configuration.

R Server Configuration

Opal is able to perform R queries by talking with a running R server. See the R Server documentation. The properties for connecting to the default Rock R server(s) are the following:

Property

Description

rock.default.administrator.username

Rock administrator user name. Default is administrator.

rock.default.administrator.password

Rock administrator user password. Default is password.

rock.default.manager.username

Rock manager user name.

rock.default.manager.password

Rock manager user password.

rock.default.user.username

Rock regular user name.

rock.default.user.password

Rock regular user password.

org.obiba.opal.r.endpoint

Enable/disable the plain R web service. When disabled, even the system administrator cannot interact directly with a plain R session. Use of the DataSHIELD web service is recommended instead. Default is true (enabled).

org.obiba.opal.r.sessionTimeout

Time in minutes after which an inactive R session will be automatically terminated (default is 4 hours).

org.obiba.opal.r.sessionTimeout.R

Time in minutes after which an inactive R session with R context will be automatically terminated (default is to fallback to org.obiba.opal.r.sessionTimeout).

org.obiba.opal.r.sessionTimeout.DataSHIELD

Time in minutes after which an inactive R session with DataSHIELD context will be automatically terminated (default is to fallback to org.obiba.opal.r.sessionTimeout).

org.obiba.opal.r.sessionTimeout.Import

Time in minutes after which an inactive R session with Import context will be automatically terminated (default is to fallback to org.obiba.opal.r.sessionTimeout).

org.obiba.opal.r.sessionTimeout.Export

Time in minutes after which an inactive R session with Export context will be automatically terminated (default is to fallback to org.obiba.opal.r.sessionTimeout).

org.obiba.opal.r.sessionTimeout.SQL

Time in minutes after which an inactive R session with SQL context will be automatically terminated (default is to fallback to org.obiba.opal.r.sessionTimeout).

org.obiba.opal.r.sessionTimeout.Analyse

Time in minutes after which an inactive R session with Analyse context will be automatically terminated (default is to fallback to org.obiba.opal.r.sessionTimeout).

org.obiba.opal.r.sessionTimeout.View

Time in minutes after which an inactive R session with View context will be automatically terminated (default is to fallback to org.obiba.opal.r.sessionTimeout).

org.obiba.opal.r.repos

The list of CRAN repositories from which R packages can be downloaded, comma separated. Default value is https://cloud.r-project.org,https://cran.obiba.org.

DataSHIELD Configuration

Some minimal default DataSHIELD infrastructure settings can be defined.

Property

Description

datashield.r.parser

DataSHIELD R parser version: v1 or v2 See DataSHIELD4J library documentation. Default is the latest.

Login Policy Configuration

To prevent brute force password guessing, a user can be temporarily banned after too many login failures.

Property

Description

org.obiba.opal.security.login.maxRetry

Number of failed login attempts before being banned (default is 3).

org.obiba.opal.security.login.retryTime

Time span in which the maximum of retry count should happen before starting a ban period, in seconds (default is 300). No time limit if not positive.

org.obiba.opal.security.login.banTime

Ban time after max retry, within the retry time span, was reached, in seconds (default is 300). No ban if not positive.

org.obiba.opal.security.login.pat.expiresIn

Time in days after which a personal access token is automatically removed. Default is -1 (i.e. tokens never expire).

org.obiba.opal.security.login.pat.activityTimeout

Time in days since last access after which a personal access token is marked as being inactive. This state can be reverted by user. Default is 60 (2 months).

Agate Server Configuration

Opal user lookup can include the Agate’s user realm. Default configuration enables connection to a Agate server.

Property

Description

org.obiba.realm.url

Address to connect to Agate server. Default is https://localhost:8444. To disable Agate connection, specify an empty value for this property.

org.obiba.realm.publicUrl

Public address to create a link from Opal’s user profile page to the Agate’s one where personal information, password and two-factor authentication can be managed. Default is empty.

org.obiba.realm.service.name

Application name of this Opal instance in Agate. Default is opal.

org.obiba.realm.service.key

Application key of this Opal instance in Agate. Default is changeit.

System Identifiers Generation Configuration

When importing data and selecting a identifiers mapping, if an imported identifier does not exist for the selected mapping and the strategy that was chosen is to generate a system identifier, then the following default settings apply for system identifiers generation:

Property

Description

org.obiba.opal.identifiers.length

Length of the numerical part of the identifier (i.e. not including the prefix length). Default is 10.

org.obiba.opal.identifiers.zeros

Allow leading zeros in the numerical part of the identifiers. Default is false.

org.obiba.opal.identifiers.prefix

Character prefix to be applied. Default is none.

org.obiba.opal.identifiers.checksum

Add a checksum digit so that the generated identifier can be validated regarding the Luhn algorithm. Default is false.

Cross Site Resource Forgery (CSRF)

CSRF attacks can be mitigated by a built-in interceptor. Default behavior allows connections (http or https) from localhost and 127.0.0.1. Requests from pages served by Opal should be allowed as well (https only), unless network settings or proxies modify or do not report the referer URL.

Property

Description

csrf.allowed

Comma separated list of client host:port explicitly allowed to connect to Opal server. Use * as a wildcard. Default is empty.

csrf.allowed-agents

Comma separated list of User-Agent fragments, matched case insensitively, allowed to connect when the request carries no Referer header - the API clients. Default is curl,python,java,HTTP.jl.

Configuration Database

Opal stores its own configuration (projects, users, permissions, registered databases, resources, DataSHIELD profiles, R activity) in a relational database. By default this is an embedded H2 database in OPAL_HOME/data/config, opened as user opal with the <databasePassword> that Opal generates in OPAL_HOME/data/opal-config.xml: nothing has to be configured for that.

The following properties point Opal at an external database server instead, for instance PostgreSQL. The database must exist and be empty, Opal creates the schema itself on first start.

Property

Description

config.datasource.url

The JDBC URL of the configuration database, for instance jdbc:postgresql://localhost:5432/opal_config. Default is empty, meaning the embedded H2 database in OPAL_HOME/data/config.

config.datasource.driverClass

The JDBC driver class name, for instance org.postgresql.Driver.

config.datasource.username

The user name to connect with.

config.datasource.password

The user password. An external database requires one: the generated password applies to the embedded database only.

config.hibernate.dialect

The Hibernate dialect, for instance org.hibernate.dialect.PostgreSQLDialect. Default is empty, letting Hibernate detect it from the connection.

For example:

config.datasource.url=jdbc:postgresql://localhost:5432/opal_config
config.datasource.driverClass=org.postgresql.Driver
config.datasource.username=opal
config.datasource.password=secret
config.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect

Note

These settings are to be applied before the first start of Opal, so that the configuration is written there from the beginning. Pointing an Opal that already has a configuration at an empty external database gives an empty configuration.

Note

With the embedded database, OPAL_HOME/data/config and OPAL_HOME/data/opal-config.xml must be backed up and restored together: the database password is generated by Opal and kept, encrypted with the <secretKey>, in that XML file. Restore them from different installations, or replace the secret key, and Opal cannot open its own configuration.

Storage Safeguards

Two background tasks watch what Opal writes to disk.

The first one forces the open H2 databases (the configuration database and the H2 data databases) to physical disk. While Opal runs, H2 writes pages in the background but never syncs the file, so a power loss costs everything written since the last checkpoint. A clean shutdown syncs by itself and does not depend on this task.

The second one samples the free space of the volumes Opal writes to: OPAL_HOME/data, OPAL_HOME/data/config, OPAL_HOME/data/h2, the Opal file system root, OPAL_HOME/logs and the JVM temporary directory. Folders that share a mount point, which is the usual case, are reported once. The readings are in Administration > Java Virtual Machine and in the /system/status web service.

Property

Description

storage.checkpoint.interval

Milliseconds between two checkpoints of the open H2 databases. Default is 300000 (5 minutes). Set to 0 or less to disable.

storage.disk.interval

Milliseconds between two samples of the free disk space. Default is 60000 (1 minute). Set to 0 or less to disable the monitoring.

storage.disk.enforce

Whether the levels below are acted upon. When false the readings are still taken and reported, and nothing is refused, which allows the thresholds to be observed against a real deployment before they start failing jobs. Default is false.

storage.disk.warn.bytes

Number of free bytes under which the WARN level is reached. Default is 5368709120 (5 GB).

storage.disk.degraded.bytes

Number of free bytes under which the DEGRADED level is reached. Default is 2147483648 (2 GB).

storage.disk.critical.bytes

Number of free bytes under which the CRITICAL level is reached. Default is 536870912 (512 MB).

storage.disk.upload.safetyFactor

How much more room than its announced size an upload is asked to leave, as a factor: an upload is written through a temporary file and a multipart envelope is larger than its payload. Default is 1.2.

The level of a volume is the worst level any of its thresholds triggers, and the level of the server is the worst level of its volumes. When enforcement is on:

Level

Effect

OK

Nothing.

WARN

Reported and logged, nothing is refused.

DEGRADED

Imports, copies, backups, restores and uploads are refused. Reads, login and configuration writes keep working, which is what leaves a way in to make room.

CRITICAL

Running tasks are cancelled as well. A cancelled import can be run again; a database that hits a full disk closes without persisting and costs a restart.

Note

The thresholds are absolute numbers of bytes and not a share of the volume: what has to fit is an import, a compaction or a clean close, and none of those needs more room on a larger disk. A deployment whose imports are much bigger than usual raises storage.disk.warn.bytes. A threshold larger than the volume it applies to can never be satisfied, and Opal says so in the log.

Miscelaneous Configuration

Advanced settings.

Property

Description

org.obiba.opal.keys.entityType

Type of entities to store in the identifiers table.

org.obiba.opal.keys.tableReference

Fully-qualified name of the identifiers table

org.obiba.opal.taxonomies

Comma separated list of URIs to taxonomy files in YAML format. Note that file URI schema is supported (allows to read locally defined taxonomy).

org.obiba.opal.plugins.site

The URL to the plugins repository (default is https://plugins.obiba.org). A plugin repository is not just a list of files, meta-data information about plugins are expected to be provided by a plugins.json file.

org.obiba.opal.maxFormContentSize

Maximum body size of a HTTP(S) form post request. Default value is 200000 bytes.

org.obiba.opal.ws.messageSizeLimit

Limit of the Protobuf message size. Default value is 524288000 bytes (500MB).

org.obiba.magma.entityIdNames

Specify the column name per entity type to be used for the entity identifier when exporting data to a file (CSV, SAS, SPSS, Stata). If empty for the considered entity type, the default column name will apply. The format to be used is a comma-separated key-value list, for instance: org.obiba.magma.entityIdNames=Participant=Idepic,Biomarker=Biom_Id

org.obiba.magma.entityIdName

Specify the default column name to be used for the entity identifier when exporting data to a file (CSV, SAS, SPSS, Stata). If empty, this name depends on the file format.

org.obiba.magma.readDataPointsCount

Maximum number of data points (number of rows per number of variables) when batches of values are read from a table. Default value is 100000.

org.obiba.opal.security.multiProfile

Allow user to login from different realms with the same username. Note that the user is always logged in one realm at a time (no addition of the privileges). Default value is true.

org.obiba.opal.security.ssl.allowInvalidCertificates

When connecting to MongoDB using SSL and when remote certificate is self-signed, the certificate check can be deactivated (not recommended, default is false).

org.obiba.opal.jdbc.maxPoolSize

Maximum size of the pool of JDBC connections, for each SQL database. Default value is 100.

productionMode

When set to false the CSRF check is disabled and plugin jars conflict checks are skipped. Default value is true.

Advanced Configuration File

The file OPAL_HOME/data/opal-config.xml can be edited to match some of your server needs.

File System Root

Opal offers a “file system” in which users may manipulate files without having a user defined in the OS running Opal. That is, all interactions with the underlying file-system go through a unique system-user: the one that runs the Opal server.

The Opal file system root is set by default to be OPAL_HOME/fs. To change it, modify the following statement:

<!-- Windows example -->
<fileSystemRoot>C:/opal-filesystem</fileSystemRoot>

Several types of file root names are recognized:

  • Absolute URI. These must start with a scheme, such as ‘file:’, followed by a scheme dependent file name. For example:

  • Absolute local file name. For example, /home/someuser/somedir or c:dirsomedir. Elements in the name can be separated using any of the following characters: /, , or the native file separator character. For example, the following file names are the same:

    c:dirsomedir c:/dir/somedir

User Directories

The security framework that is used by Opal for authentication, authorization etc. is Shiro. Configuring Shiro for Opal is done via the file OPAL_HOME/conf/shiro.ini. See also Shiro ini file documentation.

Note

Default configuration is a static user ‘administrator’ with password ‘password’ (or the one provided while installing Opal Debian/RPM package).

By default Opal server has several built-in user directories (in the world of Shiro, a user directory is called a realm):

  • a file-based user directory (shiro.ini file),

  • the internal Opal user directory,

  • the user directory provided by Agate.

In the world of Shiro, a user directory is called a realm.

File Based User Directory

The file-based user directory configuration file OPAL_HOME/conf/shiro.ini.

Note

It is not recommended to use this file-based user directory. It is mainly dedicated to define a default system super-user.

For a better security, user passwords are encrypted with a one way hash such as sha256.

The example shiro.ini file below demonstrates how encryption is configured.

# =======================
# Shiro INI configuration
# =======================

[main]
# Objects and their properties are defined here,
# Such as the securityManager, Realms and anything else needed to build the SecurityManager


[users]
# The 'users' section is for simple deployments
# when you only need a small number of statically-defined set of User accounts.
#
# Password here must be encrypted!
# Use shiro-hasher tools to encrypt your passwords:
#   DEBIAN:
#     cd /usr/share/opal/tools && ./shiro-hasher -p
#   UNIX:
#     cd <OPAL_DIST_HOME>/tools && ./shiro-hasher -p
#   WINDOWS:
#     cd <OPAL_DIST_HOME>/tools && shiro-hasher.bat -p
#
# Format is:
# username="password-hash"[,role]*
administrator = "$shiro2$argon2id$v=19$t=1,m=65536,p=4$q4uNaPW6sY1mDaLvlRcXPw$/zeLc6+GYP9//fahi+8qDj/a1KlJLvtQjRRTvbOePb0",admin

[roles]
# The 'roles' section is for simple deployments
# when you only need a small number of statically-defined roles.
# Format is:
# role=permission[,permission]*
admin = *

Passwords must be encrypted using shiro-hasher tools (included in Opal tools directory):

cd /usr/share/opal/tools
./shiro-hasher -p

LDAP and Active Directory Authentication

Opal can authenticate users by using an existing LDAP or Active Directory server. This is done by adding the proper configuration section in the shiro.ini file:

[main]
ldapRealm = org.apache.shiro.realm.ldap.JndiLdapRealm
ldapRealm.contextFactory.url = ldap://ldap.hostname.or.ip:389
ldapRealm.userDnTemplate = uid={0},ou=users,dc=mycompany,dc=com

The userDnTemplate should be modified to match your LDAP schema. The {0} will be replaced by the username provided at login. Authentication will use the user’s credentials to try to bind to LDAP; if binding succeeds, the credentials are considered valid and authentication will succeed.

There is currently no support to extract a user’s groups from LDAP. This will be added in a future release.

With Active Directory you can specify a mapping between AD groups and roles in Shiro. Example configuration for Active Directory authentication:

[main]
adRealm = org.apache.shiro.realm.activedirectory.ActiveDirectoryRealm
adRealm.url = ldap://ad.hostname.or.ip:389
adRealm.systemUsername = usernameToConnectToAD
adRealm.systemPassword = passwordToConnectToAD
adRealm.searchBase = "CN=Users,DC=myorg"
adRealm.groupRolesMap = "CN=shiroGroup,CN=Users,DC=myorg":"myrole"
#adRealm.principalSuffix =

Logging

The runtime messages can be configured in the OPAL_HOME/conf/logback.xml file. See Logback documentation.

By default, Logback is configured to output files in the OPAL_HOME/logs folder. The log files are:

  • opal.log, contains the Opal application main log messages,

  • rest.log, contains the web services specific log messages,

  • datashield.log, contains the DataSHIELD activity log messages,

  • sql.log, contains the SQL API specific log messages.

These log files can be downloaded from the web interface (Administration > Java Virtual Machine > Logs or Administration > DataSHIELD > Logs) or using the opalr R package.

These messages can also be sent to an OpenTelemetry collector, in addition to being written to file: see OpenTelemetry.

OpenTelemetry

Opal can export its logs, its DataSHIELD traces and its DataSHIELD metrics to an OpenTelemetry collector, using the OTLP protocol. This is in addition to the log files described above: the entries written to OPAL_HOME/logs/datashield.log do not change, and the files remain the local record when the collector is unreachable.

Enabling

Nothing is exported until an OTLP endpoint is set. Setting one turns the three signals on together.

Opal embeds the OTLP/HTTP sender only, so the endpoint is the http/protobuf one: port 4318 on a standard collector, not the 4317 gRPC port.

Where the variables are set depends on how Opal was installed. Each of these files ships with the settings below already listed, commented out — with one caveat for the zip distribution: nothing ever writes into an existing OPAL_HOME/conf, so if yours predates this feature, copy conf/opal-env.sh across from the new distribution yourself.

Installation

Environment file

Zip distribution

OPAL_HOME/conf/opal-env.sh, sourced by bin/opal. Use export VAR=value.

Debian/RPM package

/etc/default/opal, read by systemd. Use VAR=value, without export.

Docker image

The environment section of the compose file, or -e on docker run.

For example, with a collector running on the same host:

OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
OTEL_RESOURCE_ATTRIBUTES=deployment.environment=production,service.namespace=my-node

Settings

Environment Variable

Description

OTEL_EXPORTER_OTLP_ENDPOINT

Base URL of the OTLP/HTTP collector, for instance https://collector.example.org:4318. Setting it is what enables the export.

OTEL_EXPORTER_OTLP_LOGS_ENDPOINT

Full URL of the logs endpoint, when the signals do not go to the same collector. Also enables the export on its own.

OTEL_EXPORTER_OTLP_TRACES_ENDPOINT

Full URL of the traces endpoint, when the signals do not go to the same collector. Also enables the export on its own.

OTEL_EXPORTER_OTLP_METRICS_ENDPOINT

Full URL of the metrics endpoint, when the signals do not go to the same collector. Also enables the export on its own.

OTEL_SERVICE_NAME

Name reported to the backend. Default is opal.

OTEL_RESOURCE_ATTRIBUTES

Comma separated key=value pairs added to every record, span and measurement. Useful to tell nodes apart in a federated study.

OTEL_METRIC_EXPORT_INTERVAL

Milliseconds between metric exports. Default is 60000.

The other OpenTelemetry SDK environment variables are honoured as well, with two things to know:

  • any one of the four endpoint variables switches the export on: the global OTEL_EXPORTER_OTLP_ENDPOINT, or a signal specific one on its own. A deployment that only wants the traces, or only the metrics, sets that endpoint and nothing else.

  • the exporter protocol defaults to http/protobuf and has to stay there. Opal bundles the HTTP sender only, so setting OTEL_EXPORTER_OTLP_PROTOCOL to grpc makes the SDK fail to build. Opal logs Failed to initialize OpenTelemetry, continuing without it, starts normally, and exports nothing.

Securing the endpoint

The DataSHIELD stream carries submitted R expressions, user names and client addresses. Anywhere other than a collector on localhost, it needs TLS, and usually a credential.

Environment Variable

Description

OTEL_EXPORTER_OTLP_CERTIFICATE

Path to the PEM certificate of the collector’s certificate authority.

OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE

Path to the client certificate, when the collector requires mutual TLS.

OTEL_EXPORTER_OTLP_CLIENT_KEY

Path to the client private key.

OTEL_EXPORTER_OTLP_HEADERS

Comma separated headers, for instance Authorization=Bearer%20<token>. Values are URL encoded.

Warning

On the Debian and RPM packages, /etc/default/opal is world readable, so a collector token does not belong in it. The service also reads /etc/default/opal-secrets, which systemd opens as root before dropping to the opal user, so it can be kept private:

echo 'OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer%20<token>' > /etc/default/opal-secrets
chmod 600 /etc/default/opal-secrets

What is exported

Logs. The Opal application messages, the web services messages and the DataSHIELD activity, under the scope names org.obiba.opal.*, org.obiba.opal.web.security.AuditInterceptor and datashield.user respectively. The SQL log is not exported.

The DataSHIELD records carry their context as attributes. The ds_* names of the log file are historical, so they are renamed on the way out — in an appender placed after the file appender, so the file itself keeps them:

datashield.log field

Exported attribute

Content

ds_id

datashield.session.id

The DataSHIELD session the operation belongs to.

ds_profile

datashield.profile

The DataSHIELD profile in use.

ds_action

datashield.action

OPEN, ASSIGN, PARSE, AGGREGATE, CLOSE, …

ds_symbol

datashield.symbol

The R symbol being assigned.

ds_eval

datashield.script

The R expression evaluated, in full.

ds_script_in

datashield.script.submitted

The expression as the user submitted it (PARSE only).

ds_script_out

datashield.script.generated

The expression the parser generated (PARSE only).

ds_map

datashield.script.mapping

The DataSHIELD to R function mapping applied (PARSE only).

ds_table

datashield.table

The table a symbol was assigned from.

r_duration

datashield.r.duration

Milliseconds spent in the R server.

r_size

datashield.r.size

Bytes returned by the R server.

username

enduser.id

The authenticated user.

ip

client.address

The client address.

Traces. One trace per DataSHIELD session, on the instrumentation scope org.obiba.opal.datashield. A datashield.session span covers the life of the session and carries the session id, the profile, the user and the client address; under it sits one span per operation — datashield.open, .assign, .parse, .aggregate, .close, .ws_save, .ws_restore — in the order the audit log records them. A failure sets the span status to ERROR: a script the DataSHIELD parser refuses shows up as a datashield.parse span in error, carrying the submitted expression.

Each exported log record also carries the trace id of its session, so a backend such as Grafana can go from a span to the audit lines it produced, and back. That holds whether or not anything else is instrumenting the HTTP layer: an audit record is anchored to its session, not to the request that happened to trigger it.

Metrics. Four instruments on the same scope, covering the DataSHIELD workload rather than the server as a whole. They are described in full below.

Metrics

Four instruments are exported, on the org.obiba.opal.datashield instrumentation scope, once every OTEL_METRIC_EXPORT_INTERVAL milliseconds — a minute by default. They describe the DataSHIELD workload; the JVM and the HTTP layer are not instrumented by Opal itself, and come from the OpenTelemetry Java agent when it is installed, see Tracing the rest of the server.

Instrument

Kind

Unit

Measures

datashield.session.active

Observable gauge

sessions

Open DataSHIELD sessions, counted at each export.

datashield.operation.count

Counter

operations

DataSHIELD operations, completed or failed.

datashield.operation.duration

Histogram

seconds

Time spent on a DataSHIELD operation.

datashield.quota.rejection

Counter

rejections

Sessions refused because a usage quota was spent.

datashield.session.active

The number of R sessions running in the DataSHIELD execution context, by datashield.profile. It is asked of the session manager when the exporter collects, rather than maintained as sessions come and go: a session also ends by timing out and by losing its R server, so a counter incremented around the REST endpoints would drift upwards forever. A profile with no session reports nothing at all — the series disappears rather than reading zero.

datashield.operation.count

Incremented once per operation, when the operation ends, whether it succeeded or failed. Attributes are datashield.action, datashield.profile and datashield.outcome.

datashield.operation.duration

The duration of that same operation, in seconds, under the same three attributes — recorded from the same measurement as the operation’s span, so a graph and a trace never disagree. The explicit bucket boundaries are 0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10, 30, 60 and 300 seconds: the OpenTelemetry defaults are laid out for milliseconds, and would put nearly every R operation in one bucket.

datashield.quota.rejection

Incremented when a user is refused a new session because their allowance is spent, by datashield.quota.metric. A quota is held against a user and a metric, not against a profile, so there is no profile dimension. A user who has spent both allowances increments both series, and the same refusal is also counted as an OPEN operation in error — the quota check runs inside the operation.

Attributes

Attribute

Values

On

datashield.action

OPEN, CLOSE, PARSE, ASSIGN, AGGREGATE, WS_SAVE, WS_RESTORE

operation.count, operation.duration

datashield.profile

The DataSHIELD profile the session runs on, absent when unknown

session.active, operation.count, operation.duration

datashield.outcome

ok, or error when the operation threw

operation.count, operation.duration

datashield.quota.metric

EXECUTION_TIME, SESSION_TIME

quota.rejection

The actions are those of the audit log, and mean the same thing there — PARSE is the DataSHIELD parser vetting a submitted expression on the request thread, AGGREGATE and ASSIGN are the R server evaluating what came out of it, OPEN covers starting the R session and seeding it. The actions the audit log records without an operation to time, RM, LS and QUOTA, are not counted here.

Note

Session identifiers, user names, R expressions and symbol names are never metric attributes. Each attribute multiplies the number of time series a backend has to keep, so anything that identifies one operation rather than a class of them stays on the logs and the spans, where it can be looked up on demand. To go from a suspicious curve to the operations behind it, filter the DataSHIELD logs or traces on the same action, profile and time range.

Names in the backend

The names above are the OTLP ones. A Prometheus flavoured backend — Prometheus itself, Mimir, Grafana Cloud — renames them on ingestion: dots become underscores, a real unit is appended, and a monotonic counter gets _total. What Opal exports as datashield.operation.count is therefore queried as datashield_operation_count_total.

Instrument

Prometheus time series

datashield.session.active

datashield_session_active

datashield.operation.count

datashield_operation_count_total

datashield.operation.duration

datashield_operation_duration_seconds_bucket, ..._sum, ..._count

datashield.quota.rejection

datashield_quota_rejection_total

Attributes become labels under the same rule: datashield_action, datashield_profile, datashield_outcome, datashield_quota_metric. The OTEL_SERVICE_NAME and OTEL_RESOURCE_ATTRIBUTES values land on every series as well, which is what tells the nodes of a federated study apart.

# operations per second, by action
sum by (datashield_action) (rate(datashield_operation_count_total[5m]))

# share of operations that fail, by profile
sum by (datashield_profile) (rate(datashield_operation_count_total{datashield_outcome="error"}[5m]))
  / sum by (datashield_profile) (rate(datashield_operation_count_total[5m]))

# 95th percentile of the time an aggregation takes
histogram_quantile(0.95, sum by (le) (
  rate(datashield_operation_duration_seconds_bucket{datashield_action="AGGREGATE"}[5m])))

# sessions open right now, and refusals over the day
sum by (datashield_profile) (datashield_session_active)
sum by (datashield_quota_metric) (increase(datashield_quota_rejection_total[1d]))

Upgrading an existing installation

The OpenTelemetry appenders live in OPAL_HOME/conf/logback.xml, which belongs to the installation: an upgrade never overwrites it, so an Opal upgraded from a version that predates them keeps a file that has none. Traces and metrics are then exported and log records are not.

Opal says so at startup when it happens:

OpenTelemetry export enabled.
WARNING: conf/logback.xml declares no OpenTelemetry appender, so no log record will be exported ...

To fix it, copy the otel, otelrest, otelraw and otelds appenders — and the appender-ref entries that use them — from the logback.xml of the distribution into your own. Where that copy is depends on the packaging:

Installation

Distribution copy of logback.xml

Zip distribution

OPAL_DIST/conf/logback.xml

Debian package

/usr/share/opal-server-<version>/conf/logback.xml

RPM package

no conf directory is installed under /usr/share; /etc/opal is installed noreplace, so the upgrade leaves the new file beside yours as /etc/opal/logback.xml.rpmnew

Docker image

/usr/share/opal/conf/logback.xml

Tracing the rest of the server

The spans above cover the DataSHIELD operations themselves. Adding the OpenTelemetry Java agent to the JVM arguments fills in what happens underneath them — the JDBC and Hibernate calls, the MongoDB lookups, the HTTP calls to the R server — nested inside the DataSHIELD spans. A session trace then shows each audited operation together with the work it caused. Opal needs no change for this:

# zip distribution, in OPAL_HOME/conf/opal-env.sh
export JAVA_OPTS="$JAVA_OPTS -javaagent:/path/opentelemetry-javaagent.jar"

# Debian/RPM package, in /etc/default/opal
JAVA_ARGS="... -javaagent:/opt/opentelemetry-javaagent.jar"

Two things to expect with the agent running:

  • it logs a warning that GlobalOpenTelemetry.set calls are ignored. That is normal, and nothing is lost: Opal keeps its own SDK for the log appenders and uses the agent’s for spans and metrics.

  • the HTTP server spans stay in traces of their own. A DataSHIELD trace is rooted on the session rather than on a request, so it does not nest under the request that started it, and the part of a request that runs outside a DataSHIELD operation stays in that request’s trace. The two are tied together by span links: the session span links to the request that opened the session, and each operation span to the request that asked for it, so a backend such as Grafana offers one trace from the other.

Other Settings

Shiro’s default session timeout is 1800s (half an hour). The session timeout can be set explicitly in the shiro.ini file, in the [main] section:

# =======================
# Shiro INI configuration
# =======================

[main]
# Objects and their properties are defined here,
# Such as the securityManager, Realms and anything else needed to build the SecurityManager
# 3,600,000 milliseconds = 1 hour
securityManager.sessionManager.globalSessionTimeout = 3600000

# ...

The session timeout is in milliseconds and allowed values are:

  • a negative value means sessions never expire.

  • a non-negative value (0 or greater) means session timeout will occur as expected.

Reverse Proxy Configuration

Opal server can be accessed through a reverse proxy server.

Apache

Example of Apache directives that:

  • redirects HTTP connection on port 80 to HTTPS connection on port 443,

  • specifies acceptable protocols and cipher suites,

  • refines organization’s specific certificate and private key.

<VirtualHost *:80>
    ServerName opal.your-organization.org
    ProxyRequests Off
    ProxyPreserveHost On
    <Proxy *>
        Order deny,allow
        Allow from all
    </Proxy>
    RewriteEngine on
    ReWriteCond %{SERVER_PORT} !^443$
    RewriteRule ^/(.*) https://opal.your-organization.org:443/$1 [NC,R,L]
</VirtualHost>
<VirtualHost *:443>
    ServerName opal.your-organization.org
    SSLProxyEngine on
    SSLEngine on
    SSLProtocol All -SSLv2 -SSLv3
    SSLHonorCipherOrder on
    # Prefer PFS, allow TLS, avoid SSL, for IE8 on XP still allow 3DES
    SSLCipherSuite "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+AESG CM EECDH EDH+AESGCM EDH+aRSA HIGH !MEDIUM !LOW !aNULL !eNULL !LOW !RC4 !MD5 !EXP !PSK !SRP !DSS"
    # Prevent CRIME/BREACH compression attacks
    SSLCompression Off
    SSLCertificateFile /etc/apache2/ssl/cert/your-organization.org.crt
    SSLCertificateKeyFile /etc/apache2/ssl/private/your-organization.org.key
    ProxyRequests Off
    ProxyPreserveHost On
    ProxyPass / https://localhost:8443/
    ProxyPassReverse / https://localhost:8443/
</VirtualHost>

For performance, you can also activate Apache’s compression module (requires deflate module) with the following settings (note the json content type setting) in file /etc/apache2/mods-available/deflate.conf:

<IfModule mod_deflate.c>
  <IfModule mod_filter.c>
      # these are known to be safe with MSIE 6
      AddOutputFilterByType DEFLATE text/html text/plain text/xml
      # everything else may cause problems with MSIE 6
      AddOutputFilterByType DEFLATE text/css
      AddOutputFilterByType DEFLATE application/x-javascript application/javascript application/ecmascript
      AddOutputFilterByType DEFLATE application/rss+xml
      AddOutputFilterByType DEFLATE application/xml
      AddOutputFilterByType DEFLATE application/json
  </IfModule>
</IfModule>

Recommended security headers are (to be added to the apache2.conf file, requires headers module):

# Security Headers, see https://securityheaders.com/
Header set Strict-Transport-Security "max-age=63072000"
Header set X-Frame-Options DENY
Header set X-XSS-Protection 1;mode=block
Header set X-Content-Type-Options nosniff
Header set Content-Security-Policy "frame-ancestors 'none'"
Header set Referrer-Policy "same-origin"
Header set Permissions-Policy "fullscreen=(self)"
Header onsuccess edit Set-Cookie ^(.+)$ "$1;HttpOnly;Secure;SameSite=Strict"

Proxy Configuration

Outbound connections may go through a proxy, depending on the host institution’s network setup. It is possible to declare the proxy settings by modifying the JAVA_OPTS environment variable. As an example:

# without authentication
JAVA_OPTS=-Dhttp.proxyHost=127.0.0.1 -Dhttp.proxyPort=3020 -Xms1G -Xmx8G

# or with authentication
JAVA_OPTS=-Dhttp.proxyHost=127.0.0.1 -Dhttp.proxyPort=3020 -Dhttp.proxyUser=opal -Dhttp.proxyPassword=xxxxxx -Xms1G -Xmx8G