Authorization Plugins

Authorization plugins are classes that can be used to customize access permissions to the Intake catalog server. The Intake server and client communicate over HTTP, so when security is a concern, the most important step to take is to put a TLS-enabled reverse proxy (like nginx) in front of the Intake server to encrypt all communication.

Whether or not the connection is encrypted, the Intake server by default allows all clients to list the full catalog, and open any of the entries. For many use cases, this is sufficient, but if the visibility of catalog entries needs to be limited based on some criteria, a server- (and/or client-) side authorization plugin can be used.

Server Side

An Intake server can have exactly one server side plugin enabled at startup. The plugin is activated using the Intake configuration file, which lists the class name and the keyword arguments it takes. For example, the “shared secret” plugin would be configured this way:

auth:
  class: intake.auth.secret.Secret
  kwargs:
    secret: A_SECRET_HASH

This plugin is very simplistic, and exists as a demonstration of how an auth plugin might function for more realistic scenarios.

For more information about configuring the Intake server, see Configuration.

The server auth plugin has two methods. The allow_connect() method decides whether to allow a client to make any request to the server at all, and the allow_access() method decides whether the client is allowed to see a particular catalog entry in the listing and whether they are allowed to open that data source. Note that for catalog entries which allow direct access to the data (via network or shared filesystem), the Intake authorization plugins have no impact on the visibility of the underlying data, only the entries in the catalog.

The actual implementation of a plugin is very short. Here is a simplified version of the shared secret auth plugin:

class SecretAuth(BaseAuth):
    def __init__(self, secret, key='intake-secret'):
        self.secret = secret
        self.key = key

    def allow_connect(self, header):
        try:
            return self.get_case_insensitive(header, self.key, '') \
                        == self.secret
        except:
            return False

    def allow_access(self, header, source, catalog):
        try:
            return self.get_case_insensitive(header, self.key, '') \
                        == self.secret
        except:
            return False

The header argument is a dictionary of HTTP headers that were present in the client request. In this case, the plugin is looking for a special intake-secret header which contains the shared secret token. Because HTTP header names are not case sensitive, the BaseAuth class provides a helper method get_case_insensitive(), which will match dictionary keys in a case-insensitive way.

The allow_access method also takes two additional arguments. The source argument is the instance of LocalCatalogEntry for the data source being checked. Most commonly auth plugins will want to inspect the _metadata dictionary for information used to make the authorization decision. Note that it is entirely up to the plugin author to decide what sections they want to require in the metadata section. The catalog argument is the instance of Catalog that contains the catalog entry. Typically, plugins will want to use information from the catalog.metadata dictionary to control global defaults, although this is also up to the plugin.

Client Side

Although server side auth plugins can function entirely independently, some authorization schemes will require the client to add special HTTP headers for the server to look for. To facilitate this, the Catalog constructor accepts an optional auth parameter with an instance of a client auth plugin class.

The corresponding client plugin for the shared secret use case describe above looks like:

class SecretClientAuth(BaseClientAuth):
    def __init__(self, secret, key='intake-secret'):
        self.secret = secret
        self.key = key

    def get_headers(self):
        return {self.key: self.secret}

It defines a single method, get_headers(), which is called to get a dictionary of additional headers to add to the HTTP request to the catalog server. To use this plugin, we would do the following:

import intake
from intake.auth.secret import SecretClientAuth

auth = SecretClientAuth('A_SECRET_HASH')
cat = intake.Catalog('http://example.com:5000', auth=auth)

Now all requests made to the remote catalog will contain the intake-secret header.