Skip to content
Connect > Gateway

Gateway Security

The Gateway secures each listener in 3 layers: mutual TLS admits only known certificates, an authentication plugin verifies the connecting user, and an optional entitlement filter narrows what each user may query or subscribe to. This page describes how those layers combine and which properties control them.

Overview

The 2 listeners are secured independently at the transport layer, each with its own keystore and authorized-clients list, but they share one authentication plugin and one entitlement filter. Transport security is not optional: a listener with a keystore always enforces mutual TLS.

Mutual TLS

Whenever a listener has an SSL keystore configured, it enforces mutual TLS. A client is admitted only if the public key of the certificate it presents matches an entry in that listener's authorized-clients file. The server authorizes its own key automatically; every external client must be listed. A listener with no keystore configured stays plain TCP.

There is no property to turn this off. Mutual TLS is mandatory once a keystore is set.

The relevant properties, documented in full in Configuration:

  • ami.gateway.ssl.keystore.file / ami.gateway.ssl.authorized.clients.file for the native listener.
  • ami.gateway.jdbc.ssl.keystore.file / ami.gateway.jdbc.ssl.authorized.clients.file for the JDBC listener.

Both authorized-clients files default to the shared keys/3forge.auth. Entries are ssh-rsa public keys.

If a listener's keystore file does not exist when the Gateway starts, the Gateway generates a self-signed keystore at that path, with a .pub public key file beside it. It also adds its own public key to the listener's authorized-clients file, creating the file if needed, which is how it authorizes its own key. The Gateway never adds external client keys. Editing the authorized-clients file does not take effect while the Gateway is running: reload the Gateway component for a change to the file to apply.

Authorizing a client on the Gateway

Obtaining a client certificate and converting its public key to the ssh-rsa form the allowlist takes is common to every 3forge listener and is not covered here. Two parts of the process are specific to the Gateway:

Applying a change to the allowlist. Append the client's ssh-rsa line to the file named by the listener's authorized-clients property, then reload the Gateway component:

ami.reloadComponent("<name>")

Reload, not restart. shutdownComponent, startupComponent, and restartComponent do not work on a Gateway, see Stopping and starting a Gateway. Until the reload, a newly added key is still rejected at the handshake, with nothing to indicate that the file has changed.

Pointing a client at the JDBC listener. The client's certificate, its key, and the server's public key go in the connection URL as sslKeystoreFile, sslKeystorePassword, and sslPubFile, with isSecure=true. See JDBC Access.

Testing the allowlist with the shipped keystore

The Gateway authorizes its own certificate automatically. Because the shipped keys/3forge.jks holds the server's own key, a client that connects with that keystore is admitted even when its key is not in keys/3forge.auth. To see the allowlist reject a client, test with a separate client certificate, not the shipped one.

Authentication

A single authentication plugin applies to both listeners, selected by ami.gateway.auth.plugin.class. By default it follows the instance-wide ami.auth.plugin.class (ACCESS_FILE). There is no separate JDBC authentication plugin. For the login plugins themselves (LDAP, OAuth, SAML), see Authentication; for the certificate exchange behind mutual TLS, see Mutual TLS.

When the authentication plugin is left blank, the Gateway installs a disabled authenticator and does not require a login.

A client authenticating over a listener presents both a client certificate authorized for the listener (for the mandatory mutual TLS) and valid credentials for the authentication plugin.

Entitlement filtering

An optional entitlement filter plugin, set with ami.gateway.filter.plugin.class, is applied to every query and subscription from a client that has logged in. The filter is attached to the session when the client logs in, so a client that has not logged in is not filtered. A client can skip the login only when the Gateway does not require one (see Authentication). When the plugin is not set, no filter is applied and all authenticated traffic passes.

A filter can, per session:

  • Reject, pass, or rewrite a query before it is routed.
  • Rewrite a subscription: narrow its columns or tighten its WHERE clause.
  • Filter individual rows, showing, hiding, or modifying each one.

The filter fails closed. If it errors while supplying its row filter, the whole snapshot request is rejected; if it errors on one subscription, that subscription is dropped. Neither case serves data unfiltered.

Writing a filter plugin is outside the scope of this page; the shipped SampleGatewayFilterPlugin demonstrates the available hooks and can be enabled with:

ami.gateway.filter.plugin.class=com.f1.ami.gateway.SampleGatewayFilterPlugin

The sample filter changes results

The sample is a demonstration, not a neutral filter. Enable it only on a test instance. It:

  • Appends WHERE symbol=='MSFT' to the text of every query.
  • For a table named Orders, hides new IBM rows, sets px to -1 on new F rows, and doubles px on every update.
  • Writes the logged-in username into a desc column on every new row, in any table, except Orders rows for MSFT and IBM.

What this does not support

  • Mutual TLS has no off switch while a keystore is configured.
  • The 2 listeners cannot use different authentication plugins. One plugin serves both.
  • The Gateway does not issue certificates for clients. It generates only its own keystore, when that file is missing, and lists only its own key in the authorized-clients file.