January 13, 2020·4 min read·engineeringkafkaconnectsecurity

Kafka Connect secrets leaked when a reference occupied part of a value

CVE-2019-12399 exposes a mismatch between variable substitution and reverse transformation. A reference embedded inside a longer configuration value could return as plaintext through the task API.

A secret reference can protect stored configuration only if every later representation preserves the distinction between the reference and its resolved value. Kafka Connect's newly disclosed CVE-2019-12399 concerns a case where that distinction failed. The 13 January advisory describes plaintext secret exposure when an externalized variable occupies a substring of a connector configuration value. A client requesting task configurations can receive the resolved secret instead of the reference.

The condition is specific. A worker must have one or more configuration providers, and the connector must use an externalized secret inside a larger string. The advisory lists affected versions in the 2.0, 2.1 and 2.2 lines, plus 2.3.0. It identifies 2.2.2 and 2.3.1 as fixed releases. This is a disclosure about existing secret-handling behavior, not the introduction of a new secret-provider interface.

I regard Kafka's durable log model as useful because several systems can consume the same history under their own control. Connect extends that usefulness by integrating external systems, which often requires credentials. The configuration used to establish those integrations deserves a separate confidentiality model from the records the integration moves.

External references have to become values somewhere

KIP-297, accepted for Kafka 2.0.0, explains why Connect needs external configuration providers. The proposal describes connector configuration containing plaintext passwords in local files or internal Kafka topics. Its design keeps indirect references in stored configuration and resolves them before passing configuration to the connector or task.

That preserves compatibility with existing connectors. A connector expecting a password receives the resolved password, while the configuration system can retain a reference. The provider interface is the extension point for retrieving the value. The proposal includes a file-based provider and allows custom integrations, rather than assuming that every organization uses the same secret-management service.

For illustration, consider these two property values using the proposal's reference syntax. The names identify an imaginary file and property, not an actual credential.

password=${file:/etc/connect/example.properties:password}
connection.options=password=${file:/etc/connect/example.properties:password}

In the first value, the reference is the entire property value. In the second, it is embedded after a literal prefix. A connector can require the latter shape because its configuration expects a combined string. The advisory's condition concerns that embedded use, so restricting the explanation to properties named password would miss the underlying issue.

Reverse transformation had a different matching rule

The mechanism is visible by comparing the released implementations. In Kafka 2.3.0's AbstractHerder, reverseTransform identifies configuration keys whose raw connector values contain references. For each task configuration, it replaces the corresponding key's value with the raw connector configuration value. The intention is to restore the reference after the connector has generated task settings.

However, the helper identifying those keys calls Matcher.matches. That asks whether the pattern matches the entire value. A variable occupying only part of the value does not satisfy the same condition. The reverse transformation can therefore fail to identify a property whose value was resolved through the provider mechanism.

The 2.3.1 implementation uses Matcher.find instead. That searches for a matching part of the string. It then restores the whole raw value for the relevant key, including any literal text surrounding the reference. This is a small code difference with a specific semantic effect, aligning the detection step with references embedded inside longer values.

The comparison explains why externalizing a secret was insufficient in the affected case. Resolving the reference for execution was expected behavior. The failure occurred when constructing another representation that should have returned the reference. A later transformation needed to recognize every form accepted by the earlier substitution, including the surrounding text.

Task configuration is an observable representation

The Connect REST resource includes a GET endpoint at /connectors/{connector}/tasks. It asks the herder for task configurations and returns task information. That is the endpoint identified by the advisory's description of retrieving connector task configuration.

This makes the issue different from a secret existing transiently inside a task that legitimately needs it. The connector must receive a resolved value to authenticate to the external system. Returning that value through a management representation creates another route to the secret, potentially reaching clients that were not meant to obtain it from the provider itself.

The advisory says a client can issue the request to the same Connect cluster. It does not justify treating every deployment as publicly reachable or assuming a particular authentication arrangement around that REST service. The concrete exposure is plaintext in the response when the listed configuration conditions hold. Reachability determines who can request that response in a particular environment.

I find the distinction between execution configuration and management configuration useful here. They can contain the same property keys while serving different purposes. The task needs a usable credential. The management API needs a representation that does not silently undo the operator's decision to externalize it.

The fix preserves the original configuration value

The repaired reverse transformation does not invent a new secret value or redact the entire configuration arbitrarily. For a matching property key, it restores the raw connector value. That retains the literal structure and the indirect reference the operator originally supplied. A client can understand the configured expression without receiving the provider's resolved plaintext through this path.

This is why the substring condition matters beyond the individual CVE. Configuration substitution and configuration presentation are related transformations with a shared contract. Both must agree on what constitutes a reference. Kafka's fixed releases correct that agreement for embedded variables, so that a task-configuration response can preserve the external reference even when a connector property combines it with other text.