Status
Current state: "Under Discussion"
Discussion thread: here
JIRA: here
Please keep the discussion on the mailing list rather than commenting on the wiki (wiki discussions get unwieldy fast).
Motivation
Kafka currently supports non-configurable SASL extensions in its SCRAM authentication protocol for delegation token validation. It would be useful to provide configurable SASL extensions for the OAuthBearer authentication mechanism as well, such that clients could attach arbitrary data for the principal authenticating into Kafka.
This way, a custom principal can hold information derived from the authentication mechanism, which could prove useful for better tracing and troubleshooting, for example. This can be done in a way which allows for easier extendability in future SASL mechanisms.
It is worth noting that these extensions would lack a digital signature and therefore should not be used for critical use-cases where security is a concern.
Public Interfaces
`SaslExtensions` class - encapsulates extensions and parses them to/from strings
public class SaslExtensions { public SaslExtensions() public SaslExtensions(String extensions) public SaslExtensions(Map<String, String> extensionMap) public String extensionValue(String name) public Set<String> extensionNames() @Override public String toString() }
`SaslExtensionsCallback` - generic callback to hold extensions
public class SaslExtensionsCallback implements Callback { /** * Returns the extension names and values that are sent by the client to * the server in the initial client SASL authentication message. * Default is an empty map. */ public Map<String, String> extensions() /** * Sets the SASL extensions on this callback. */ public void extensions(Map<String, String> extensions) }
Proposed Changes
Describe the new thing you want to do in appropriate detail. This may be fairly extensive and have large subsections of its own. Or it may be a few sentences. Use judgement based on the scope of the change.
Create a new `SaslExtensions` class that takes most of the generalizable logic from `ScramExtensions`. `ScramExtensions` will extend `SaslExtensions`
Create a new `SaslExtensionsCallback` which will be exactly the same as `ScramExtensionsCallback`. `ScramExtensionsCallback` cannot be deleted since it is a public class - it will extend `SaslExtensionsCallback` to preserve backwards-compatibility.
Pass `SaslExtensionsCallback` to the callback handler of `OAuthBearerSaslClient` so that the handler can populate the extensions in the callback. `OAuthBearerSaslClient` will then attach the extensions (if any) to the first client message.
Have `OAuthBearerServer` parse sent extensions and expose them via its `OAuthBearerServer#getNegotiatedProperty()` method. It will use a strict regex, parsing only letters for keys and only ASCII characters for values without commas (","), as they are used for separators in the OAuth message
This will allow custom principals to access them through the `SaslServer` instance in `SaslAuthenticationContext#server()`
Note that the default callback handler `OAuthBearerSaslClientCallbackHandler` will not attach any extensions - it is up to the custom user-defined callback handler to attach the appropriate extensions.
Example
A user would make use of the changes in this KIP in the following way:
- Create a custom ClientCallbackHandler and configure the client to use it
- This handler should handle the `SaslExtensionsCallback` in his `handle()` method and attach custom extensions
@Override public void handle(Callback[] callbacks) { for (Callback callback : callbacks) { if (callback instanceof OAuthBearerTokenCallback) handleTokenCallback((OAuthBearerTokenCallback) callback); else if (callback instanceof SaslExtensionsCallback) { Map<String, String> extensions = new HashMap<>(); extensions.put("trace-id", "123"); ((SaslExtensionsCallback) callback).extensions(extensions); } else throw new UnsupportedCallbackException(callback); } }
- This handler should handle the `SaslExtensionsCallback` in his `handle()` method and attach custom extensions
The extensions can be fetched in the server-side through `OAuthBearerSaslServer#getNegotiatedProperty("trace-id")`
- A custom principal builder can then make use of the new extension
public class CustomPrincipalBuilder implements KafkaPrincipalBuilder { @Override public KafkaPrincipal build(AuthenticationContext context) { if (context instanceof SaslAuthenticationContext) { SaslServer saslServer = ((SaslAuthenticationContext) context).server(); String traceId = saslServer.getNegotiatedPropery("trace-id"); return new CustomPrincipal("", saslServer, traceId); } throw new Exception(); } }
Compatibility, Deprecation, and Migration Plan
- What impact (if any) will there be on existing users? None
- If we are changing behavior how will we phase out the older behavior? We are simply adding better extendability options
Mark `ScramExtensionsCallback` as deprecated and remove it in next major release (3.0)
Rejected Alternatives
- Add customizable extensions to every SASL client
- | Not easily implementable, as we depend on a third-party library for PLAIN authentication
- It is possible we implement configurable extensions for SCRAM as well. We would need to simply remove the checks in `ScramSaslServer` and then any custom callback handler could populate the extensions
As such, I decided it is best we limit the scope of this KIP while still implementing it in a way which would support future extensions by other SASL mechanisms