From 9a087eb0ab2d093f147e11c4e9939acf20af7c4a Mon Sep 17 00:00:00 2001 From: Tom Bentley Date: Fri, 13 Jun 2025 16:26:11 +1200 Subject: [PATCH 1/4] 005-filter-api-to-expose-client-and-server-tls-info Signed-off-by: Tom Bentley --- ...pi-to-expose-client-and-server-tls-info.md | 647 ++++++++++++++++++ 1 file changed, 647 insertions(+) create mode 100644 proposals/005-filter-api-to-expose-client-and-server-tls-info.md diff --git a/proposals/005-filter-api-to-expose-client-and-server-tls-info.md b/proposals/005-filter-api-to-expose-client-and-server-tls-info.md new file mode 100644 index 0000000..5631eb7 --- /dev/null +++ b/proposals/005-filter-api-to-expose-client-and-server-tls-info.md @@ -0,0 +1,647 @@ + +# Authentication APIs + +This proposal describes a set of public APIs to expose client identity to plugin implementations. + +## Terminology + +Let's define some terminology to make discussion easier. +For the purposes of this proposal we consider TLS and SASL as the only authentication mechanisms we care about. + +Let's first define terms for TLS: + +* A **mutual authentication mechanism** is one which proves the identity of a server to a client. +* When the proxy is configured to accept TLS connections from clients it is performing **TLS Termination**, which does not imply mutual authentication. + (For the avoidance of doubt, Kroxylicious does not support TLS passthrough; the alternative to TLS Termination is simply using TCP as the application transport). +* A **TLS client certificate** is a TLS certificate for the client-side of a TLS Handshake. + For a given client and server pairing, a proxy might have _two_ of these: the Kafka client's TLS client certificate, and the proxy's own TLS client certificate for its connection to the server. +* When the proxy is configured to require TLS client certificates from clients and validates these against a set of trusted signing certificates (CA certificate) it is performing **client mutual TLS authentication** ("client mTLS"). +* A **TLS server certificate** is a TLS certificate for the server-side of a TLS Handshake. + As above, there could be two of these for a given connection through a proxy. +* When the proxy is configured to use a TLS client certificate when making a TLS connection to a server, we will use the term **server mutual TLS authentication_** ("server mTLS"). + +Now let's talk about SASL. In the following, the word "component" is generalising over filters, other plugins, and a proxy virtual cluster as a whole: + +* a component which forwards a client's `SaslAuthenticate` requests to the server, and conveys the responses back to the client, is performing **SASL Passthrough**. +* SASL Passthrough is one way to for a proxy to be **identity preserving**, which means that, for all client principals in the virtual cluster, each of those principals will have the same name as the corresponding client principal in the broker. +* a component performing SASL Passthrough and looking at the requests and responses to infer the client's principal is performing **SASL Passthrough Inspection**. + Note that this technique does not work with all SASL mechanisms. +* a component that responds to a client's `SaslAuthenticate` requests _itself_, without forwarding those requests to the server, is performing **SASL Termination**. +* a component that injects its own `SaslAuthenticate` requests into a SASL exchange with the server is performing **SASL Initiation**. + +When _all_ the filters/plugins on the path between client and server are performing "SASL passthrough" (or don't intercept SASL messages at all) then the virtual cluster as a whole is performing "SASL passthrough". +Alternatively, if any filters/plugins on the path between client and server is performing "SASL Termination", then we might say that the virtual cluster as a whole is performing "SASL Termination". + +It is possible for a proxy to be perform neither, one, or both, of SASL Termination and SASL Initiation. + +Finally, let's define some concepts from JAAS: + +* A **subject** represents a participant in the protcol (a client or server). +* A **principal** identifies a subject. + A subject may have zero or more principals. + Subjects that haven't authenticated will have no principals. + A subject gains a principal following a successful TLS handshake. + A subject also gains a principal following a successful `SaslAuthenticate` exchange. +* A **credential** is information used to prove the authenticity of a principal. +* A **public credential**, such as a TLS certificate, does not need not be kept secret. +* A **private credential**, such as a TLS private key or a password, must be kept secret, otherwise the authenticity of a principal is compromised. + +## Current situation + +The `Filter` and `FilterContext` APIs currently don't directly expose any authenticated client identity information. +Specifically: + +* If proxy uses client mTLS, then filters don't have access to a `Subject` or `Principal` corresponding to the client's TLS client certificate. +* If clients are authenticating using SASL, the only way a `Filter` can know about that is by intercepting those frames. + - identity-using filters in the chain must _each_ implement SASL passthrough inspection. + - but this is usually incompatible with use of a filter performing SASL Termination or SASL Initiation. + +## Motivation + +The lack of API support makes implementing client identity aware plugins difficult, or impossible. + +Goals: + +* Allow the possibility for new kinds of KRPC intercepting plugins in the future by not assuming that `Filters` are the only kind of KRPC intercepting plugin. We'll use the term **plugin**, unless saying something specifically about `Filters`. +* Enable plugins to access a client's identity using a single, consistent API, irrespective of which authentication mechanism(s) are being used, TLS or SASL, and whether they're implemented by the proxy runtime (in the TLS case), or a prior plugin in the chain (in the SASL termination case). +* Allow access to TLS- or SASL-specific details by plugins should they need them. +* Don't require a plugin to handle `SaslAuthenticate` unless it is performing SASL termination or initiation. +* Provide a flexible API to make serving niche use cases possible (though perhaps not simple). +* Drop support for the "raw" (i.e. not encapsulated within the Kafka protocol) support for SASL, as [Kafka itself has does from Kafka 4.0](https://cwiki.apache.org/confluence/display/KAFKA/KIP-896%3A+Remove+old+client+protocol+API+versions+in+Kafka+4.0) + +## Proposal + +### API for Filters to access client TLS information + +TLS (in contrast to SASL) is handled entirely by the proxy runtime. +By the time a `Filter` is instantiated the proxy already has an established TLS connection. +All that's required is an API for exposing appropriate details to `Filters`. + +The following method will be added to the existing `FilterContext` interface: + +```java + /** + * @return The TLS context for the connection between the Kafka client and the proxy, + * or empty if the client connection is not TLS. + */ + Optional clientTlsContext(); +``` + +Where `ClientTlsContext` is a new interface in the new package `io.kroxylicious.proxy.tls`: + +``` +package io.kroxylicious.proxy.tls; + +import java.security.cert.X509Certificate; +import java.util.Optional; + +public interface ClientTlsContext { + /** + * @return The TLS server certificate that the proxy presented to the client during TLS handshake. + */ + X509Certificate proxyServerCertificate(); + + /** + * @return the TLS client certificate was presented by the Kafka client to the proxy during TLS handshake, + * or empty if no TLS client certificate was presented. + */ + Optional clientCertificate(); + +} +``` + +Having a distinct type, `ClientTlsContext`, means we can easily expose the same information to future plugins that are not filters (and thus do not have access to a `FilterContext`). + + +### APIs for Filters to produce client SASL information + +SASL (in contrast to TLS) is embedded in the Kafka protocol (the `SaslHandshake` and `SaslAuthentication` messages), and therefore can be handled by `Filter` implementations. +The goals require decoupling the production and consumption of SASL information by plugins. +Let's consider the production side first: SASL terminators and inspectors require a way of announcing the outcome of a SASL authentication. +For this purpose we will add the following methods to the existing `FilterContext` interface: + +```java + /** + * Allows a filter (typically one which implements {@link SaslAuthenticateRequestFilter}) + * to announce a successful authentication outcome with the Kafka client to other plugins. + * After calling this method the result of {@link #clientSaslContext()} will + * be non-empty for this and other filters. + * + * In order to support reauthentication, calls to this method and + * {@link #clientSaslAuthenticationFailure(String, String, Exception)} + * may be arbitrarily interleaved during the lifetime of a given filter instance. + * + * This method can only be called by filters created from {@link FilterFactory FilterFactories} which + * have been annotated with {@link ClientSaslProducer @ClientSaslProducer}. + * Calls from filters where this is not the case will be logged but otherwise ignored. + * + * @param mechanism The SASL mechanism used. + * @param authorizationId The authorization ID. + */ + void clientSaslAuthenticationSuccess(String mechanism, String authorizationId); + + /** + * Allows a filter (typically one which implements {@link SaslAuthenticateRequestFilter}) + * to announce a failed authentication outcome with the Kafka client. + * It is the filter's responsilbity to return the right error response to a client, and/or disconnect. + * + * In order to support reauthentication, calls to this method and + * {@link #clientSaslAuthenticationSuccess(String, String)} + * may be arbitrarily interleaved during the lifetime of a given filter instance. + * + * This method can only be called by filters created from {@link FilterFactory FilterFactories} which + * have been annotated with {@link ClientSaslProducer @ClientSaslProducer}. + * Calls from filters where this is not the case will be logged but otherwise ignored. + * + * @param mechanism The SASL mechanism used, or null if this is not known. + * @param authorizationId The authorization ID, or null if this is not known. + * @param exception An exception describing the authentication failure. + */ + void clientSaslAuthenticationFailure(String mechanism, String authorizationId, Exception exception); +``` + +Note that [RFC 4422][RFC4422] defines the "authorization identity" as one of pieces of information transferred via the challenges and responses defined by a mechanism. +In SASL this need not be the same thing as a client's username or other identity, though it usually is in Apache Kafka's use of SASL. +We're sticking with the SASL terminology in this part of the API. + +### APIs for Filters to consume client SASL information + +Some filters, such as audit loggers, may need to use SASL authentication information specifically. +For such filters we will add the following methods to `FilterContext`: + +```java + /** + * Returns the SASL context for the client connection, or empty if the client + * has not successfully authenticated using SASL. + * Filters should use {@link #clientPrincipal()} in preference to this method, unless they require SASL-specific functionality. + * @return The SASL context for the client connection, or empty if the client + * has not successfully authenticated using SASL. + */ + Optional clientSaslContext(); +``` + +Where + +```java +package io.kroxylicious.proxy.authentication; + +import java.util.Optional; + +import io.kroxylicious.proxy.filter.FilterContext; + +/** + * Exposes SASL authentication information to plugins, for example using {@link FilterContext#clientSaslContext()}. + * This is implemented by the runtime for use by plugins. + */ +public interface ClientSaslContext { + + /** + * The name of the SASL mechanism used by the client. + * @return The name of the SASL mechanism used by the client. + */ + String mechanismName(); + + /** + * Returns the client's authorizationId that resulted from the SASL exchange. + * @return the client's authorizationId. + */ + String authorizationId(); +} +``` + +### APIs for using client principals in a generic way + +Most `Filters` don't need to be opinionated about how the client is identified. They only need to know: + +* that the client _is_ authenticated, somehow +* the name of the client's identity + +We want to avoid making `Filter` developers pick a source of authentication information (`clientTlsContext()` or `clientSaslContext()`) in order to maximise the reusabilty of `Filter` implementations. +For this purpose we will make use of `java.security.Principal` by providing the following method on `FilterContext`: + +```java + /** + *

Returns the authenticated principal for the client connection.

+ * + *

The concrete type of principal returned depends on the proxy configuration. + * For example, + * it may be a {@link javax.security.auth.x500.X500Principal} if client identity is TLS-based, + * or it may be a {@link SaslPrincipal} if client identity is SASL based.

+ * + *

Callers should not:

+ *
    + *
  • assume any particular type of principal. + *
  • assume the principal does not change during the lifetime of a filter (due to reauthentication) + *
+ * + * @return The authenticated principal for the client connection, or empty if the client + * has not successfully authenticated. + */ + Optional clientPrincipal(); +``` + +The choice about what concrete type of `Principal` this method returns will be left to the person configuring the proxy. +They will do this using a new `principalType` property in the `VirtualCluster` configuration YAML. +This will support the values `X500` or `SASL`. +When configured with `X500`, the `Principal` returned by `FilterContext.clientPrincipal()` will be the `javax.security.auth.x500.X500Principal` from the client's `java.security.cert.X509Certificate`. +When configured with `SASL`, the `principal` returned by `FilterContext.clientPrincipal()` will be an instance of `SaslPrincipal`, defined as follows: + +```java +package io.kroxylicious.proxy.authentication; + +import java.security.Principal; +import java.util.Objects; + +/** + * A principal established using SASL. + */ +public record SaslPrincipal(String name) implements Principal { + @Override + public String getName() { + return this.name; + } +} +``` + +The `name` will be the `authorizationId` argument from the SASL Terminator or Inspector's call to `FilterContext.clientSaslAuthenticationSuccess()`. + +### API for Filters to access server TLS information + +So far we've only covered authentication on the _client-to-proxy connection_. +To cater for "client-side" proxy deployment topologies we must also consider authentication on the _proxy-to-server connection_. +Both TLS and SASL can provide for mutual authentication, so there may be a server identity which, logically a filter could make use of. + +The API for exposing the proxy-to-broker TLS information to `Filters` is very similar to the client one. The following method will be added to `FilterContext`: + +```java + /** + * @return The TLS context for the connection between the proxy and the Kafka server, + * or empty if the server connection is not TLS. + */ + Optional serverTlsContext(); +``` + +Where + +```java +package io.kroxylicious.proxy.tls; + +import java.security.cert.X509Certificate; +import java.util.Optional; + +public interface ServerTlsContext { + /** + * @return The TLS server certificate that the proxy presented to the server during TLS handshake, + * or empty if no TLS client certificate was presented during TLS handshake. + */ + Optional proxyClientCertificate(); + + /** + * @return the TLS server certificate was presented by the Kafka server to the proxy during TLS handshake. + */ + X509Certificate serverCertificate(); +} +``` + +### APIs for Filters to produce server SASL information + +A SASL Initiator will be able to use the follow methods on `FilterContext` to announce a successful, or failed, authentication with a Kafka server: + +```java + /** + * Allows a filter + * to announce a successful authentication outcome with the Kafka server to other plugins. + * After calling this method the result of {@link #serverSaslContext()} will + * be non-empty for this and other filters. + * This method may be called multiple times over the lifetime of + * a session if reauthentication is required. + * TODO define the semantics around reauth + * @param saslPrincipal The authenticated principal. + */ + void serverSaslAuthenticationSuccess(String mechanism, String serverName); + + /** + * Allows a filter + * to announce a failed authentication outcome with the Kafka server. + * @param exception An exception describing the authentication failure. + */ + void serverSaslAuthenticationFailure(String mechanism, String serverName, Exception exception); +``` + +### APIs for Filters to consume server SASL information + +```java + /** + * @return The SASL context for the server connection, or empty if the server + * has not successfully authenticated using SASL. + */ + Optional serverSaslContext(); +``` + +Where: + +```java +package io.kroxylicious.proxy.authentication; + +import java.util.Optional; + +import io.kroxylicious.proxy.filter.FilterContext; + +/** + * Exposes SASL authentication information to plugins, for example using {@link FilterContext#serverSaslContext()} ()}. + * This is implemented by the runtime for use by plugins. + */ +public interface ServerSaslContext { + + /** + * The name of the SASL mechanism used. + * @return The name of the SASL mechanism used. + */ + String mechanismName(); +} +``` + +### APIs for using server principals in a generic way + +This works similarly to the client-facing equivalent: + +```java + /** + * Returns the authenticated principal for the server connection, or empty if the server + * has not successfully authenticated, or if the server authentication was not mutual. + * The concrete type of principal returned depends on the proxy configuration. + * For example, + * it may be a {@link javax.security.auth.x500.X500Principal} if server identity is TLS-based, + * or it may be a {@link SaslPrincipal} is client identity is SASL based. + * @return The authenticated principal for the server connection, or empty if the server + * has not successfully authenticated. + */ + Optional serverPrincipal(); +``` + + +### API for selecting target cluster TLS credentials + +The APIs presented so far are sufficient to write plugins which: + +* Propagate SASL, letting a client's SASL identity reach the server unchanged +* Initiate SASL, injecting mechanism-specific credentials prior to letting client-originated requests reach the server. + The selection of the server-facing SASL credentials to use could be based on the client's identity + (e.g. SASL termination and SASL initiation in the same virtual cluster) + +What's missing is an API where the server-facing TLS client certificate is chosen based on the client's identity. +For this purpose we will add a new plugin interface, `ServerTlsCredentialSupplierFactory`. +It will use the usual Kroxylicious plugin mechanism, leveraging `java.util.Service`-based discovery. +However, this plugin is not the same thing as a `FilterFactory`. +Rather, an implementation class will be defined on the TargetCluster configuration object, and instantiated once for each target cluster. +The TargetCluster's `tls` object will gain a `tlsCredentialSupplier` property, supporting `type` and `config` properties (similarly to how filters are configured). +The interface itself is declared like this: + +```java +package io.kroxylicious.proxy.tls; + +/** + *

A pluggable source of {@link ServerTlsCredentialSupplier} instances.

+ *

ServerTlsCredentialSupplierFactories are:

+ *
    + *
  • {@linkplain java.util.ServiceLoader service} implementations provided by plugin authors
  • + *
  • called by the proxy runtime to {@linkplain #create(Context, Object) create} instances
  • + *
+ * @param The type of configuration. + * @param The type of initialization data. + */ +public interface ServerTlsCredentialSupplierFactory { + I initialize(Context context, C config) throws PluginConfigurationException; + ServerTlsCredentialSupplier create(Context context, I initializationData); + default void close(I initializationData) { + } +} +``` + +`ServerTlsCredentialSupplierFactory` is following the convention established by `FilterFactory`, and the `Context` referenced above is similar to the `FilterFactoryContext`: + +```java + interface Context { + + /** + * An executor backed by the single Thread responsible for dispatching + * work to a ServerTlsCredentialSupplier instance for a channel. + * It is safe to mutate ServerTlsCredentialSupplier members from this executor. + * @return executor + * @throws IllegalStateException if the factory is not bound to a channel yet. + */ + ScheduledExecutorService filterDispatchExecutor(); + + /** + * Gets a plugin instance for the given plugin type and name + * @param pluginClass The plugin type + * @param instanceName The plugin instance name + * @return The plugin instance + * @param

The plugin manager type + * @throws UnknownPluginInstanceException If the plugin could not be instantiated. + */ +

P pluginInstance(Class

pluginClass, + String instanceName) + throws UnknownPluginInstanceException; + + /** + * Creates some TLS credentials for the given parameters. + * @param key The key corresponding to the given client certificate. + * @param certificateChain The client certificate corresponding to the given {@code key}, plus any intermediate certificates forming the certificate chain up to (but not including) the TLS certificate trusted by the peer. + * @return The TLS credentials instance. + * @see ServerTlsCredentialSupplier.Context#tlsCredentials(PrivateKey, Certificate[]) + */ + TlsCredentials tlsCredentials(PrivateKey key, + Certificate[] certificateChain); + } + +``` + +So what is a `ServerTlsCredentialSupplier` that this factory creates? + +```java +package io.kroxylicious.proxy.tls; + +import java.security.PrivateKey; +import java.security.cert.Certificate; +import java.util.Optional; +import java.util.concurrent.CompletionStage; + +import io.kroxylicious.proxy.authentication.ClientSaslContext; + +/** + * Implemented by a {@link io.kroxylicious.proxy.filter.Filter} that provides + * the credentials for the TLS connection between the proxy and the Kafka server. + */ +public interface ServerTlsCredentialSupplier { + /** + * Return the TlsCredentials for the connection. + * @param context The context. + * @return the TlsCredentials for the connection. + */ + CompletionStage tlsCredentials(Context context); +} +``` + +Where the `Context` will be a inner interface: + +```java + /** + * The context API for {@link ServerTlsCredentialSupplier}. + * This is implemented by the runtime for use by plugins. + */ + interface Context { + Optional clientTlsContext(); + Optional clientSaslContext(); + + /** + * Returns the default credentials for this target cluster (e.g. from the proxy configuration file). + * Implementations of {@link ServerTlsCredentialSupplier} may use this as a fall-back + * or default, for example if the apply a certificiate-per-client-principal pattern + * but are being used with an anonymous principal. + * @return the default credentials. + */ + TlsCredentials defaultTlsCredentials(); + + /** + *

Factory methods for creating TLS credentials for the given parameters.

+ * + *

The equivalent method on {@code FilterFactoryContext} can be used when the credentials + * are known at plugin configuration time.

+ * + * @param key The key corresponding to the given client certificate. + * @param certificateChain The client certificate corresponding to the given {@code key}, plus any intermediate certificates forming the certificate chain up to (but not including) the TLS certificate trusted by the peer. + * @return The TLS credentials instance. + * see io.kroxylicious.proxy.filter.ServerTlsCredentialSupplierFactory.Context#tlsCredentials(PrivateKey, Certificate[]) + */ + TlsCredentials tlsCredentials(Certificate certificate, + PrivateKey key, + Certificate[] intermediateCertificates); + } +``` + +And `TlsCredentials` looks like this: + +```java +package io.kroxylicious.proxy.authentication; + +interface TlsCredentials { + /* Intentionally empty: implemented and accessed only in the runtime */ +} +``` + +// TODO Why not use the JDK's `X500PrivateCredential`? + +### Protections for those configuring a virtual cluster + +So far this proposal has provided a classification of `Filters` consuming the SASL Kafka protocol messages, and described Java APIs to be used by `Filter` developers producing or consumer authenticated identity information. +However, we also need to consider the task of constructing a working proxy (more specifically virtual cluster) from those building blocks. +We would like to make it a startup-time error to configure a virtual cluster in a way that cannot possibly work. +Examples of such illogical configurations include: + +* Configuring a virtual cluster with `principalType: SASL` without a SASL terminator or SASL inspector in the virtual cluster's `filters` (because where is the SASL principal going to come from?) +* Configuring a virtual cluster with `principalType: X500` in a virtual cluster not configured for client mTLS (because where is the TLS principal going to come from?) +* Configuring multiple SASL terminators and/or SASL inspectors in the `filters` of a virtual cluster (because there should be a single producer of client identity). Similarly for SASL initiators (becausde there should be a single producer of server identity). + +To provide this kind of fail-safe, the proxy runtime needs to know which filters are SASL inspectors, terminators or initiators, and what sort of identity information a filter consumes. +At proxy start up time, the runtime only knows about `FilterFactories`, not about any `Filter` instances or their types, +The `FilterFactory` service interface doesn't provide a way for the runtime to know what kind of filters it may create. +Therefore we will introduce the following runtime-retained annotation types to be applied to `FilterFactory` implementations: + +```java +/** + * Annotation to be applied to `FilterFactory` implementations indicating that + * the factory create filters that call {@link FilterContext#clientSaslAuthenticationSuccess()}. + */ +@interface ClientSaslProducer{} + +/** + * Annotation to be applied to `FilterFactory` implementations indicating that + * the factory create filters that call {@link FilterContext#serverSaslAuthenticationSuccess()}. + */ +@interface ServerSaslProducer{} + +/** + * Annotation to be applied to `FilterFactory` implementations indicating that + * the factory create filters the call {@link FilterContext#clientPrincipal()}, + * or {@link FilterContext#clientSaslContext()}. + */ +@interface ClientPrincipalConsumer{ + Class[] value(); +} + +/** + * Annotation to be applied to `FilterFactory` implementations indicating that + * the factory create filters the call {@link FilterContext#serverPrincipal()}, + * or {@link FilterContext#serverSaslContext()}. + */ +@interface ServerPrincipalConsumer{ + Class[] value(); +} +``` + +|--------------------------------|-------------------------------------------------|----------------------------------------------------| +| `VirtualCluster.principalType` | FilterFactory annotation | Behaviour | +|--------------------------------|-------------------------------------------------|----------------------------------------------------| +| none | `@ClientPrincipalConsumer(Principal.class)` | Startup error | +| none | `@ClientPrincipalConsumer(X500Principal.class)` | Startup error | +| none | `@ClientPrincipalConsumer(SaslPrincipal.class)` | Startup error | +| `X500` | `@ClientPrincipalConsumer(Principal.class)` | All good (filter doesn't care about concrete type) | +| `X500` | `@ClientPrincipalConsumer(X500Principal.class)` | All good | +| `X500` | `@ClientPrincipalConsumer(SaslPrincipal.class)` | Startup error | +| `SASL` | `@ClientPrincipalConsumer(Principal.class)` | All good (filter doesn't care about concrete type) | +| `SASL` | `@ClientPrincipalConsumer(X500Principal.class)` | Startup error | +| `SASL` | `@ClientPrincipalConsumer(SaslPrincipal.class)` | All good | +|--------------------------------|-------------------------------------------------|----------------------------------------------------| + + +## Affected/not affected projects + +The `kroxylicous` repo. + +## Compatibility + +This change would be backwards compatible for `Filter` developers and proxy users (i.e. all existing proxy configurations files would still be valid). + + +# Future work + +* Implement a 1st party `SaslInspector` filter. +* Implement a 1st party `SaslTerminator` filter. +* Implement a 1st party `SaslInitiator` filter. +* A `PrincipalBuilder` API for customizing the concrete type of `Principal` exposed to `Filters` using `FilterContext.clientPrincipal()` +* A common authorization API. + + +## Rejected alternatives + + + +# References + +SASL was initially defined in [RFC 4422][RFC4422]. +Apache Kafka has built-in support for a number of mechanisms. +Apache Kafka also supports plugging-in custom mechanisms on both the server and the client. + +|---------------------|---------------------|--------------------------| +| Mechanism | Definition | Kafka implementation KIP | +|---------------------|---------------------|--------------------------| +| PLAIN | [RFC 4616][RFC4616] | [KIP-42][KIP43] | +| GSSAPI (Kerberos v5)| [RFC 4752][RFC4752] | [KIP-12][KIP12] | +| SCRAM | [RFC 5802][RFC5802] | [KIP-84][KIP84] | +| OAUTHBEARER | [RFC 6750][RFC6750] | [KIP-255][KIP255] | +|---------------------|---------------------|--------------------------| + +Note that the above list of KIPs is not exhaustive: Other KIPs have further refined some mechanisms, and defined reauthentication. + +[RFC4422]:https://www.rfc-editor.org/rfc/rfc4422 +[RFC4616]:https://www.rfc-editor.org/rfc/rfc4616 +[RFC4752]:https://www.rfc-editor.org/rfc/rfc4752 +[RFC5802]:https://www.rfc-editor.org/rfc/rfc5802 +[RFC6750]:https://www.rfc-editor.org/rfc/rfc6750 +[KIP12]:https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=51809888 +[KIP43]:https://cwiki.apache.org/confluence/display/KAFKA/KIP-43%3A+Kafka+SASL+enhancements +[KIP84]:https://cwiki.apache.org/confluence/display/KAFKA/KIP-84%3A+Support+SASL+SCRAM+mechanisms +[KIP255]:https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=75968876 + + From 0eadf4d75a9a3f6d8bf9570e3bfe7049e13bf3d1 Mon Sep 17 00:00:00 2001 From: Tom Bentley Date: Fri, 25 Jul 2025 15:46:03 +1200 Subject: [PATCH 2/4] Cut Signed-off-by: Tom Bentley --- ...pi-to-expose-client-and-server-tls-info.md | 479 ------------------ 1 file changed, 479 deletions(-) diff --git a/proposals/005-filter-api-to-expose-client-and-server-tls-info.md b/proposals/005-filter-api-to-expose-client-and-server-tls-info.md index 5631eb7..95fd3ff 100644 --- a/proposals/005-filter-api-to-expose-client-and-server-tls-info.md +++ b/proposals/005-filter-api-to-expose-client-and-server-tls-info.md @@ -113,164 +113,8 @@ public interface ClientTlsContext { Having a distinct type, `ClientTlsContext`, means we can easily expose the same information to future plugins that are not filters (and thus do not have access to a `FilterContext`). -### APIs for Filters to produce client SASL information - -SASL (in contrast to TLS) is embedded in the Kafka protocol (the `SaslHandshake` and `SaslAuthentication` messages), and therefore can be handled by `Filter` implementations. -The goals require decoupling the production and consumption of SASL information by plugins. -Let's consider the production side first: SASL terminators and inspectors require a way of announcing the outcome of a SASL authentication. -For this purpose we will add the following methods to the existing `FilterContext` interface: - -```java - /** - * Allows a filter (typically one which implements {@link SaslAuthenticateRequestFilter}) - * to announce a successful authentication outcome with the Kafka client to other plugins. - * After calling this method the result of {@link #clientSaslContext()} will - * be non-empty for this and other filters. - * - * In order to support reauthentication, calls to this method and - * {@link #clientSaslAuthenticationFailure(String, String, Exception)} - * may be arbitrarily interleaved during the lifetime of a given filter instance. - * - * This method can only be called by filters created from {@link FilterFactory FilterFactories} which - * have been annotated with {@link ClientSaslProducer @ClientSaslProducer}. - * Calls from filters where this is not the case will be logged but otherwise ignored. - * - * @param mechanism The SASL mechanism used. - * @param authorizationId The authorization ID. - */ - void clientSaslAuthenticationSuccess(String mechanism, String authorizationId); - - /** - * Allows a filter (typically one which implements {@link SaslAuthenticateRequestFilter}) - * to announce a failed authentication outcome with the Kafka client. - * It is the filter's responsilbity to return the right error response to a client, and/or disconnect. - * - * In order to support reauthentication, calls to this method and - * {@link #clientSaslAuthenticationSuccess(String, String)} - * may be arbitrarily interleaved during the lifetime of a given filter instance. - * - * This method can only be called by filters created from {@link FilterFactory FilterFactories} which - * have been annotated with {@link ClientSaslProducer @ClientSaslProducer}. - * Calls from filters where this is not the case will be logged but otherwise ignored. - * - * @param mechanism The SASL mechanism used, or null if this is not known. - * @param authorizationId The authorization ID, or null if this is not known. - * @param exception An exception describing the authentication failure. - */ - void clientSaslAuthenticationFailure(String mechanism, String authorizationId, Exception exception); -``` - -Note that [RFC 4422][RFC4422] defines the "authorization identity" as one of pieces of information transferred via the challenges and responses defined by a mechanism. -In SASL this need not be the same thing as a client's username or other identity, though it usually is in Apache Kafka's use of SASL. -We're sticking with the SASL terminology in this part of the API. - -### APIs for Filters to consume client SASL information - -Some filters, such as audit loggers, may need to use SASL authentication information specifically. -For such filters we will add the following methods to `FilterContext`: - -```java - /** - * Returns the SASL context for the client connection, or empty if the client - * has not successfully authenticated using SASL. - * Filters should use {@link #clientPrincipal()} in preference to this method, unless they require SASL-specific functionality. - * @return The SASL context for the client connection, or empty if the client - * has not successfully authenticated using SASL. - */ - Optional clientSaslContext(); -``` - -Where - -```java -package io.kroxylicious.proxy.authentication; - -import java.util.Optional; - -import io.kroxylicious.proxy.filter.FilterContext; - -/** - * Exposes SASL authentication information to plugins, for example using {@link FilterContext#clientSaslContext()}. - * This is implemented by the runtime for use by plugins. - */ -public interface ClientSaslContext { - - /** - * The name of the SASL mechanism used by the client. - * @return The name of the SASL mechanism used by the client. - */ - String mechanismName(); - - /** - * Returns the client's authorizationId that resulted from the SASL exchange. - * @return the client's authorizationId. - */ - String authorizationId(); -} -``` - -### APIs for using client principals in a generic way - -Most `Filters` don't need to be opinionated about how the client is identified. They only need to know: - -* that the client _is_ authenticated, somehow -* the name of the client's identity - -We want to avoid making `Filter` developers pick a source of authentication information (`clientTlsContext()` or `clientSaslContext()`) in order to maximise the reusabilty of `Filter` implementations. -For this purpose we will make use of `java.security.Principal` by providing the following method on `FilterContext`: - -```java - /** - *

Returns the authenticated principal for the client connection.

- * - *

The concrete type of principal returned depends on the proxy configuration. - * For example, - * it may be a {@link javax.security.auth.x500.X500Principal} if client identity is TLS-based, - * or it may be a {@link SaslPrincipal} if client identity is SASL based.

- * - *

Callers should not:

- *
    - *
  • assume any particular type of principal. - *
  • assume the principal does not change during the lifetime of a filter (due to reauthentication) - *
- * - * @return The authenticated principal for the client connection, or empty if the client - * has not successfully authenticated. - */ - Optional clientPrincipal(); -``` - -The choice about what concrete type of `Principal` this method returns will be left to the person configuring the proxy. -They will do this using a new `principalType` property in the `VirtualCluster` configuration YAML. -This will support the values `X500` or `SASL`. -When configured with `X500`, the `Principal` returned by `FilterContext.clientPrincipal()` will be the `javax.security.auth.x500.X500Principal` from the client's `java.security.cert.X509Certificate`. -When configured with `SASL`, the `principal` returned by `FilterContext.clientPrincipal()` will be an instance of `SaslPrincipal`, defined as follows: - -```java -package io.kroxylicious.proxy.authentication; - -import java.security.Principal; -import java.util.Objects; - -/** - * A principal established using SASL. - */ -public record SaslPrincipal(String name) implements Principal { - @Override - public String getName() { - return this.name; - } -} -``` - -The `name` will be the `authorizationId` argument from the SASL Terminator or Inspector's call to `FilterContext.clientSaslAuthenticationSuccess()`. - ### API for Filters to access server TLS information -So far we've only covered authentication on the _client-to-proxy connection_. -To cater for "client-side" proxy deployment topologies we must also consider authentication on the _proxy-to-server connection_. -Both TLS and SASL can provide for mutual authentication, so there may be a server identity which, logically a filter could make use of. - The API for exposing the proxy-to-broker TLS information to `Filters` is very similar to the client one. The following method will be added to `FilterContext`: ```java @@ -303,298 +147,6 @@ public interface ServerTlsContext { } ``` -### APIs for Filters to produce server SASL information - -A SASL Initiator will be able to use the follow methods on `FilterContext` to announce a successful, or failed, authentication with a Kafka server: - -```java - /** - * Allows a filter - * to announce a successful authentication outcome with the Kafka server to other plugins. - * After calling this method the result of {@link #serverSaslContext()} will - * be non-empty for this and other filters. - * This method may be called multiple times over the lifetime of - * a session if reauthentication is required. - * TODO define the semantics around reauth - * @param saslPrincipal The authenticated principal. - */ - void serverSaslAuthenticationSuccess(String mechanism, String serverName); - - /** - * Allows a filter - * to announce a failed authentication outcome with the Kafka server. - * @param exception An exception describing the authentication failure. - */ - void serverSaslAuthenticationFailure(String mechanism, String serverName, Exception exception); -``` - -### APIs for Filters to consume server SASL information - -```java - /** - * @return The SASL context for the server connection, or empty if the server - * has not successfully authenticated using SASL. - */ - Optional serverSaslContext(); -``` - -Where: - -```java -package io.kroxylicious.proxy.authentication; - -import java.util.Optional; - -import io.kroxylicious.proxy.filter.FilterContext; - -/** - * Exposes SASL authentication information to plugins, for example using {@link FilterContext#serverSaslContext()} ()}. - * This is implemented by the runtime for use by plugins. - */ -public interface ServerSaslContext { - - /** - * The name of the SASL mechanism used. - * @return The name of the SASL mechanism used. - */ - String mechanismName(); -} -``` - -### APIs for using server principals in a generic way - -This works similarly to the client-facing equivalent: - -```java - /** - * Returns the authenticated principal for the server connection, or empty if the server - * has not successfully authenticated, or if the server authentication was not mutual. - * The concrete type of principal returned depends on the proxy configuration. - * For example, - * it may be a {@link javax.security.auth.x500.X500Principal} if server identity is TLS-based, - * or it may be a {@link SaslPrincipal} is client identity is SASL based. - * @return The authenticated principal for the server connection, or empty if the server - * has not successfully authenticated. - */ - Optional serverPrincipal(); -``` - - -### API for selecting target cluster TLS credentials - -The APIs presented so far are sufficient to write plugins which: - -* Propagate SASL, letting a client's SASL identity reach the server unchanged -* Initiate SASL, injecting mechanism-specific credentials prior to letting client-originated requests reach the server. - The selection of the server-facing SASL credentials to use could be based on the client's identity - (e.g. SASL termination and SASL initiation in the same virtual cluster) - -What's missing is an API where the server-facing TLS client certificate is chosen based on the client's identity. -For this purpose we will add a new plugin interface, `ServerTlsCredentialSupplierFactory`. -It will use the usual Kroxylicious plugin mechanism, leveraging `java.util.Service`-based discovery. -However, this plugin is not the same thing as a `FilterFactory`. -Rather, an implementation class will be defined on the TargetCluster configuration object, and instantiated once for each target cluster. -The TargetCluster's `tls` object will gain a `tlsCredentialSupplier` property, supporting `type` and `config` properties (similarly to how filters are configured). -The interface itself is declared like this: - -```java -package io.kroxylicious.proxy.tls; - -/** - *

A pluggable source of {@link ServerTlsCredentialSupplier} instances.

- *

ServerTlsCredentialSupplierFactories are:

- *
    - *
  • {@linkplain java.util.ServiceLoader service} implementations provided by plugin authors
  • - *
  • called by the proxy runtime to {@linkplain #create(Context, Object) create} instances
  • - *
- * @param The type of configuration. - * @param The type of initialization data. - */ -public interface ServerTlsCredentialSupplierFactory { - I initialize(Context context, C config) throws PluginConfigurationException; - ServerTlsCredentialSupplier create(Context context, I initializationData); - default void close(I initializationData) { - } -} -``` - -`ServerTlsCredentialSupplierFactory` is following the convention established by `FilterFactory`, and the `Context` referenced above is similar to the `FilterFactoryContext`: - -```java - interface Context { - - /** - * An executor backed by the single Thread responsible for dispatching - * work to a ServerTlsCredentialSupplier instance for a channel. - * It is safe to mutate ServerTlsCredentialSupplier members from this executor. - * @return executor - * @throws IllegalStateException if the factory is not bound to a channel yet. - */ - ScheduledExecutorService filterDispatchExecutor(); - - /** - * Gets a plugin instance for the given plugin type and name - * @param pluginClass The plugin type - * @param instanceName The plugin instance name - * @return The plugin instance - * @param

The plugin manager type - * @throws UnknownPluginInstanceException If the plugin could not be instantiated. - */ -

P pluginInstance(Class

pluginClass, - String instanceName) - throws UnknownPluginInstanceException; - - /** - * Creates some TLS credentials for the given parameters. - * @param key The key corresponding to the given client certificate. - * @param certificateChain The client certificate corresponding to the given {@code key}, plus any intermediate certificates forming the certificate chain up to (but not including) the TLS certificate trusted by the peer. - * @return The TLS credentials instance. - * @see ServerTlsCredentialSupplier.Context#tlsCredentials(PrivateKey, Certificate[]) - */ - TlsCredentials tlsCredentials(PrivateKey key, - Certificate[] certificateChain); - } - -``` - -So what is a `ServerTlsCredentialSupplier` that this factory creates? - -```java -package io.kroxylicious.proxy.tls; - -import java.security.PrivateKey; -import java.security.cert.Certificate; -import java.util.Optional; -import java.util.concurrent.CompletionStage; - -import io.kroxylicious.proxy.authentication.ClientSaslContext; - -/** - * Implemented by a {@link io.kroxylicious.proxy.filter.Filter} that provides - * the credentials for the TLS connection between the proxy and the Kafka server. - */ -public interface ServerTlsCredentialSupplier { - /** - * Return the TlsCredentials for the connection. - * @param context The context. - * @return the TlsCredentials for the connection. - */ - CompletionStage tlsCredentials(Context context); -} -``` - -Where the `Context` will be a inner interface: - -```java - /** - * The context API for {@link ServerTlsCredentialSupplier}. - * This is implemented by the runtime for use by plugins. - */ - interface Context { - Optional clientTlsContext(); - Optional clientSaslContext(); - - /** - * Returns the default credentials for this target cluster (e.g. from the proxy configuration file). - * Implementations of {@link ServerTlsCredentialSupplier} may use this as a fall-back - * or default, for example if the apply a certificiate-per-client-principal pattern - * but are being used with an anonymous principal. - * @return the default credentials. - */ - TlsCredentials defaultTlsCredentials(); - - /** - *

Factory methods for creating TLS credentials for the given parameters.

- * - *

The equivalent method on {@code FilterFactoryContext} can be used when the credentials - * are known at plugin configuration time.

- * - * @param key The key corresponding to the given client certificate. - * @param certificateChain The client certificate corresponding to the given {@code key}, plus any intermediate certificates forming the certificate chain up to (but not including) the TLS certificate trusted by the peer. - * @return The TLS credentials instance. - * see io.kroxylicious.proxy.filter.ServerTlsCredentialSupplierFactory.Context#tlsCredentials(PrivateKey, Certificate[]) - */ - TlsCredentials tlsCredentials(Certificate certificate, - PrivateKey key, - Certificate[] intermediateCertificates); - } -``` - -And `TlsCredentials` looks like this: - -```java -package io.kroxylicious.proxy.authentication; - -interface TlsCredentials { - /* Intentionally empty: implemented and accessed only in the runtime */ -} -``` - -// TODO Why not use the JDK's `X500PrivateCredential`? - -### Protections for those configuring a virtual cluster - -So far this proposal has provided a classification of `Filters` consuming the SASL Kafka protocol messages, and described Java APIs to be used by `Filter` developers producing or consumer authenticated identity information. -However, we also need to consider the task of constructing a working proxy (more specifically virtual cluster) from those building blocks. -We would like to make it a startup-time error to configure a virtual cluster in a way that cannot possibly work. -Examples of such illogical configurations include: - -* Configuring a virtual cluster with `principalType: SASL` without a SASL terminator or SASL inspector in the virtual cluster's `filters` (because where is the SASL principal going to come from?) -* Configuring a virtual cluster with `principalType: X500` in a virtual cluster not configured for client mTLS (because where is the TLS principal going to come from?) -* Configuring multiple SASL terminators and/or SASL inspectors in the `filters` of a virtual cluster (because there should be a single producer of client identity). Similarly for SASL initiators (becausde there should be a single producer of server identity). - -To provide this kind of fail-safe, the proxy runtime needs to know which filters are SASL inspectors, terminators or initiators, and what sort of identity information a filter consumes. -At proxy start up time, the runtime only knows about `FilterFactories`, not about any `Filter` instances or their types, -The `FilterFactory` service interface doesn't provide a way for the runtime to know what kind of filters it may create. -Therefore we will introduce the following runtime-retained annotation types to be applied to `FilterFactory` implementations: - -```java -/** - * Annotation to be applied to `FilterFactory` implementations indicating that - * the factory create filters that call {@link FilterContext#clientSaslAuthenticationSuccess()}. - */ -@interface ClientSaslProducer{} - -/** - * Annotation to be applied to `FilterFactory` implementations indicating that - * the factory create filters that call {@link FilterContext#serverSaslAuthenticationSuccess()}. - */ -@interface ServerSaslProducer{} - -/** - * Annotation to be applied to `FilterFactory` implementations indicating that - * the factory create filters the call {@link FilterContext#clientPrincipal()}, - * or {@link FilterContext#clientSaslContext()}. - */ -@interface ClientPrincipalConsumer{ - Class[] value(); -} - -/** - * Annotation to be applied to `FilterFactory` implementations indicating that - * the factory create filters the call {@link FilterContext#serverPrincipal()}, - * or {@link FilterContext#serverSaslContext()}. - */ -@interface ServerPrincipalConsumer{ - Class[] value(); -} -``` - -|--------------------------------|-------------------------------------------------|----------------------------------------------------| -| `VirtualCluster.principalType` | FilterFactory annotation | Behaviour | -|--------------------------------|-------------------------------------------------|----------------------------------------------------| -| none | `@ClientPrincipalConsumer(Principal.class)` | Startup error | -| none | `@ClientPrincipalConsumer(X500Principal.class)` | Startup error | -| none | `@ClientPrincipalConsumer(SaslPrincipal.class)` | Startup error | -| `X500` | `@ClientPrincipalConsumer(Principal.class)` | All good (filter doesn't care about concrete type) | -| `X500` | `@ClientPrincipalConsumer(X500Principal.class)` | All good | -| `X500` | `@ClientPrincipalConsumer(SaslPrincipal.class)` | Startup error | -| `SASL` | `@ClientPrincipalConsumer(Principal.class)` | All good (filter doesn't care about concrete type) | -| `SASL` | `@ClientPrincipalConsumer(X500Principal.class)` | Startup error | -| `SASL` | `@ClientPrincipalConsumer(SaslPrincipal.class)` | All good | -|--------------------------------|-------------------------------------------------|----------------------------------------------------| - - ## Affected/not affected projects The `kroxylicous` repo. @@ -613,35 +165,4 @@ This change would be backwards compatible for `Filter` developers and proxy user * A common authorization API. -## Rejected alternatives - - - -# References - -SASL was initially defined in [RFC 4422][RFC4422]. -Apache Kafka has built-in support for a number of mechanisms. -Apache Kafka also supports plugging-in custom mechanisms on both the server and the client. - -|---------------------|---------------------|--------------------------| -| Mechanism | Definition | Kafka implementation KIP | -|---------------------|---------------------|--------------------------| -| PLAIN | [RFC 4616][RFC4616] | [KIP-42][KIP43] | -| GSSAPI (Kerberos v5)| [RFC 4752][RFC4752] | [KIP-12][KIP12] | -| SCRAM | [RFC 5802][RFC5802] | [KIP-84][KIP84] | -| OAUTHBEARER | [RFC 6750][RFC6750] | [KIP-255][KIP255] | -|---------------------|---------------------|--------------------------| - -Note that the above list of KIPs is not exhaustive: Other KIPs have further refined some mechanisms, and defined reauthentication. - -[RFC4422]:https://www.rfc-editor.org/rfc/rfc4422 -[RFC4616]:https://www.rfc-editor.org/rfc/rfc4616 -[RFC4752]:https://www.rfc-editor.org/rfc/rfc4752 -[RFC5802]:https://www.rfc-editor.org/rfc/rfc5802 -[RFC6750]:https://www.rfc-editor.org/rfc/rfc6750 -[KIP12]:https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=51809888 -[KIP43]:https://cwiki.apache.org/confluence/display/KAFKA/KIP-43%3A+Kafka+SASL+enhancements -[KIP84]:https://cwiki.apache.org/confluence/display/KAFKA/KIP-84%3A+Support+SASL+SCRAM+mechanisms -[KIP255]:https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=75968876 - From e5264a317a7a8789cb2c1ef08153db6bf32d4c2b Mon Sep 17 00:00:00 2001 From: Tom Bentley Date: Wed, 30 Jul 2025 10:43:56 +1200 Subject: [PATCH 3/4] Rob's comments Signed-off-by: Tom Bentley --- ...pi-to-expose-client-and-server-tls-info.md | 67 ++----------------- 1 file changed, 5 insertions(+), 62 deletions(-) diff --git a/proposals/005-filter-api-to-expose-client-and-server-tls-info.md b/proposals/005-filter-api-to-expose-client-and-server-tls-info.md index 95fd3ff..b9e2c51 100644 --- a/proposals/005-filter-api-to-expose-client-and-server-tls-info.md +++ b/proposals/005-filter-api-to-expose-client-and-server-tls-info.md @@ -2,59 +2,15 @@ # Authentication APIs This proposal describes a set of public APIs to expose client identity to plugin implementations. +It makes use of [proposal-004](proposal-004) for the terminology used. -## Terminology - -Let's define some terminology to make discussion easier. -For the purposes of this proposal we consider TLS and SASL as the only authentication mechanisms we care about. - -Let's first define terms for TLS: - -* A **mutual authentication mechanism** is one which proves the identity of a server to a client. -* When the proxy is configured to accept TLS connections from clients it is performing **TLS Termination**, which does not imply mutual authentication. - (For the avoidance of doubt, Kroxylicious does not support TLS passthrough; the alternative to TLS Termination is simply using TCP as the application transport). -* A **TLS client certificate** is a TLS certificate for the client-side of a TLS Handshake. - For a given client and server pairing, a proxy might have _two_ of these: the Kafka client's TLS client certificate, and the proxy's own TLS client certificate for its connection to the server. -* When the proxy is configured to require TLS client certificates from clients and validates these against a set of trusted signing certificates (CA certificate) it is performing **client mutual TLS authentication** ("client mTLS"). -* A **TLS server certificate** is a TLS certificate for the server-side of a TLS Handshake. - As above, there could be two of these for a given connection through a proxy. -* When the proxy is configured to use a TLS client certificate when making a TLS connection to a server, we will use the term **server mutual TLS authentication_** ("server mTLS"). - -Now let's talk about SASL. In the following, the word "component" is generalising over filters, other plugins, and a proxy virtual cluster as a whole: - -* a component which forwards a client's `SaslAuthenticate` requests to the server, and conveys the responses back to the client, is performing **SASL Passthrough**. -* SASL Passthrough is one way to for a proxy to be **identity preserving**, which means that, for all client principals in the virtual cluster, each of those principals will have the same name as the corresponding client principal in the broker. -* a component performing SASL Passthrough and looking at the requests and responses to infer the client's principal is performing **SASL Passthrough Inspection**. - Note that this technique does not work with all SASL mechanisms. -* a component that responds to a client's `SaslAuthenticate` requests _itself_, without forwarding those requests to the server, is performing **SASL Termination**. -* a component that injects its own `SaslAuthenticate` requests into a SASL exchange with the server is performing **SASL Initiation**. - -When _all_ the filters/plugins on the path between client and server are performing "SASL passthrough" (or don't intercept SASL messages at all) then the virtual cluster as a whole is performing "SASL passthrough". -Alternatively, if any filters/plugins on the path between client and server is performing "SASL Termination", then we might say that the virtual cluster as a whole is performing "SASL Termination". - -It is possible for a proxy to be perform neither, one, or both, of SASL Termination and SASL Initiation. - -Finally, let's define some concepts from JAAS: - -* A **subject** represents a participant in the protcol (a client or server). -* A **principal** identifies a subject. - A subject may have zero or more principals. - Subjects that haven't authenticated will have no principals. - A subject gains a principal following a successful TLS handshake. - A subject also gains a principal following a successful `SaslAuthenticate` exchange. -* A **credential** is information used to prove the authenticity of a principal. -* A **public credential**, such as a TLS certificate, does not need not be kept secret. -* A **private credential**, such as a TLS private key or a password, must be kept secret, otherwise the authenticity of a principal is compromised. ## Current situation The `Filter` and `FilterContext` APIs currently don't directly expose any authenticated client identity information. -Specifically: - -* If proxy uses client mTLS, then filters don't have access to a `Subject` or `Principal` corresponding to the client's TLS client certificate. -* If clients are authenticating using SASL, the only way a `Filter` can know about that is by intercepting those frames. - - identity-using filters in the chain must _each_ implement SASL passthrough inspection. - - but this is usually incompatible with use of a filter performing SASL Termination or SASL Initiation. +Specifically if clients are authenticating using SASL, the only way a `Filter` can know about that is by intercepting those frames. +* identity-using filters in the chain must _each_ implement SASL passthrough inspection. +* but this is usually incompatible with use of a filter performing SASL Termination or SASL Initiation. ## Motivation @@ -63,11 +19,8 @@ The lack of API support makes implementing client identity aware plugins difficu Goals: * Allow the possibility for new kinds of KRPC intercepting plugins in the future by not assuming that `Filters` are the only kind of KRPC intercepting plugin. We'll use the term **plugin**, unless saying something specifically about `Filters`. -* Enable plugins to access a client's identity using a single, consistent API, irrespective of which authentication mechanism(s) are being used, TLS or SASL, and whether they're implemented by the proxy runtime (in the TLS case), or a prior plugin in the chain (in the SASL termination case). -* Allow access to TLS- or SASL-specific details by plugins should they need them. -* Don't require a plugin to handle `SaslAuthenticate` unless it is performing SASL termination or initiation. +* Allow access to TLS-specific details by plugins should they need them. * Provide a flexible API to make serving niche use cases possible (though perhaps not simple). -* Drop support for the "raw" (i.e. not encapsulated within the Kafka protocol) support for SASL, as [Kafka itself has does from Kafka 4.0](https://cwiki.apache.org/confluence/display/KAFKA/KIP-896%3A+Remove+old+client+protocol+API+versions+in+Kafka+4.0) ## Proposal @@ -156,13 +109,3 @@ The `kroxylicous` repo. This change would be backwards compatible for `Filter` developers and proxy users (i.e. all existing proxy configurations files would still be valid). -# Future work - -* Implement a 1st party `SaslInspector` filter. -* Implement a 1st party `SaslTerminator` filter. -* Implement a 1st party `SaslInitiator` filter. -* A `PrincipalBuilder` API for customizing the concrete type of `Principal` exposed to `Filters` using `FilterContext.clientPrincipal()` -* A common authorization API. - - - From bb3af02755e03671410bdfe6a15329adb296b248 Mon Sep 17 00:00:00 2001 From: Tom Bentley Date: Thu, 7 Aug 2025 15:10:05 +1200 Subject: [PATCH 4/4] Rob comments Signed-off-by: Tom Bentley --- .../005-filter-api-to-expose-client-and-server-tls-info.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/proposals/005-filter-api-to-expose-client-and-server-tls-info.md b/proposals/005-filter-api-to-expose-client-and-server-tls-info.md index b9e2c51..64cb0ca 100644 --- a/proposals/005-filter-api-to-expose-client-and-server-tls-info.md +++ b/proposals/005-filter-api-to-expose-client-and-server-tls-info.md @@ -1,16 +1,13 @@ # Authentication APIs -This proposal describes a set of public APIs to expose client identity to plugin implementations. +This proposal describes a set of public APIs to expose client and server TLS certificates for downstream-to-proxy and proxy-to-upstream connections to plugin implementations. It makes use of [proposal-004](proposal-004) for the terminology used. ## Current situation The `Filter` and `FilterContext` APIs currently don't directly expose any authenticated client identity information. -Specifically if clients are authenticating using SASL, the only way a `Filter` can know about that is by intercepting those frames. -* identity-using filters in the chain must _each_ implement SASL passthrough inspection. -* but this is usually incompatible with use of a filter performing SASL Termination or SASL Initiation. ## Motivation @@ -89,7 +86,7 @@ import java.util.Optional; public interface ServerTlsContext { /** * @return The TLS server certificate that the proxy presented to the server during TLS handshake, - * or empty if no TLS client certificate was presented during TLS handshake. + * or empty if no TLS server certificate was presented during TLS handshake. */ Optional proxyClientCertificate();