Securing Static Files

Description

Request for comments.

> *tl;dr:* This proposes a plug-in based authorization mechanism for
> delivering static files (e.g. videos) which, by default, requires no
> configuration and which adheres to Opencast's default access control
> mechanisms (users, roles, groups, and access control lists).

Opencast comes with a complex access management system with fine-grained
control over users, roles groups of roles and access rights. But all of
this is blatantly ignored when it comes to serving static file content
like the video files. These are served by Opencast's static module which
is nothing but a file server.

Right now, these file's protection is the form of the URLs which
generally contain at least two UUIDs and is thus hard to guess
(comparable e.g. to the “Share via link” options in Google docs, …). The
problem of this is obviously that if an authorized user publishes the
direct URL to a file, all protection is lost.

To address this problem, Opencast has an implementation for a
token-based authorization system which can be configured to only allow
access with valid tokens which in turn are generated by a trusted entity
and which are valid only for a certain amount of time. The advantage of
this is that it can be used to have external systems authorize
downloads. The disadvantage is that it's hard to configure correctly,
that yet another authorization mechanism ties in the already existing
one and that there are problems with either granting or preventing
access in some edge cases. Can't we do better?

An intuitive solution for this problem would be that the file server
simply adheres to the access control lists set for a given media package
and only delivers files to users which are authorized to access this
content as part of their user rights and roles. So, let's do that.

The Problem
-----------
Opencast's static files server delivers files for all publication
channels. By default these are:

  • /static/*api*/<media-package-id>/…

  • /static/*engage-player*/<media-package-id>/…

  • /static/*internal*/<media-package-id>/…

  • /static/*oaipmh-default*/<media-package-id>/…

In other words, several components of Opencast are involved in this and
the module “static” does not even know about them. It is not even
guaranteed that the form is always the same. The module could be used to
serve any path as long as the URL starts with `/static/`.

Proposed Solution
-----------------
To allow for flexibility, I propose to allow for arbitrary services to
optionally register themselves with “static”, providing an authorization
interface which contains:

A list of URL patterns the authorization plug-in feels responsible for
(e.g. the search service could offer responsibility for
`/static/engage-player/…`)

  • A method checking authorization based on a given URL (e.g. the search
    service would check access based on the media package in the URL)

  • Delivering files for URLs which are not allowed by anyone would be
    forbidden.

This allows for intuitive use of the authorization mechanisms already in
place for parts of the system without the need to do any complicated,
additional configuration. For example, I publish access control lists to
the engage components anyway and they are used for checking access to
content in the search service based on my user. Expecting the same
mechanism for content as well would be just natural.

Compatibility
-------------
Does this break the current usage? In most cases, it shouldn't since a
lot of people already rely on the access controls set by Opencast and
other users or systems shouldn't access any files right now.

Nevertheless, there might be the usual special case for which the system
should be easy to turn off, restoring the current behavior of Opencast.
The same can also be archived by serving the static content directly via
an HTTP server. By default, however, the protection should be turned on
since this is like the more intuitive understanding when it comes to
protecting content.

An additional way to deal with custom downloads, e.g. for custom
publication channels, would be to provide a simple authorization plug-in
with configurable patterns which can be used to allow additional content
to be downloaded without any specific checks.

Example Implementation
----------------------
The provided example code implements the suggestion for parts of
Opencast, outlining how such a mechanism can be integrated. The code
contains:

  • The authorization “plug-in” mechanism

  • An example implementation for the engage components

  • A configurable plug-in to simply allow access to additional patterns

The way this works is basically that OSGI components can register
themselves with the file server by implementing a simple Java interface.
When a file is requested, the file server will check if any plug-in
feels responsible for the requested path. If so, the authorization is
handed over to that plug-in. In the case of engage, for example, the
search service is checked for if the user accessing the file has access
to the media package the file belongs to. If he has, the file is served.
Otherwise, access is denied.

Performance
-----------
Permissions only need to be checked once per file, not constantly while
a file is being downloaded. Thus, the number of requests should be
limited.

Nevertheless, things can easily be optimized. A simple way is shown by
the engage implementation which is caching the requested access for a
user to a media package for a minute, preventing a number of repetitive
requests for a given media package which would otherwise be likely since
actions like opening a player would request a number of static files
belonging to one media package (video files, thumbnails, cover images,
…).

External HTTP Servers
---------------------
Instead of letting Opencast serve static files, web servers like Nginx
can be used to serve this content directly, improving performance and
stability. To still work with the authorization checks the example
implements the same check when using an HTTP HEAD request, allowing for
the usage of mechanisms for offloading authentication and authorization
build into some web servers:

Steps to reproduce

None

Status

Assignee

Lars Kiesow

Reporter

Lars Kiesow

Criticality

None

Tags (folksonomy)

None

Components

Affects versions

8.0

Priority

Major
Configure