Skip to content

Add SASL/PLAIN password protection to the Kafka container - #19117

Open
Guillaume Delahaye (g7ed6e) wants to merge 1 commit into
microsoft:mainfrom
g7ed6e:issue-6155-kafka-sasl
Open

Add SASL/PLAIN password protection to the Kafka container#19117
Guillaume Delahaye (g7ed6e) wants to merge 1 commit into
microsoft:mainfrom
g7ed6e:issue-6155-kafka-sasl

Conversation

@g7ed6e

Copy link
Copy Markdown
Contributor

Description

Kafka is the last unchecked container in #6155 — every other integration (Garnet, MongoDB, Nats, Redis, Seq, Valkey) already enables authentication by default. This adds SASL/PLAIN over SASL_PLAINTEXT, with a generated password when none is supplied, following the pattern those integrations established.

Hosting

  • KafkaServerResource gains UserNameParameter/PasswordParameter, a UserNameReference defaulting to kafka, and Username/Password connection properties.

  • AddKafka takes optional userName/password parameters (plus a convenience overload preserving the previous signature). WithPassword/WithUserName allow reconfiguring afterwards, and WithPassword(null) turns authentication off.

  • When a password is configured, the two client-facing listeners are renamed EXTERNAL/INTERNAL and mapped to SASL_PLAINTEXT. The KRaft controller and inter-broker listeners stay plaintext on the loopback interface inside the container.

    The new names deliberately contain no underscore: the Confluent image translates KAFKA_FOO_BAR into foo.bar, so an underscore inside a listener name would have to be escaped as a double underscore in KAFKA_LISTENER_NAME_<LISTENER>_PLAIN_SASL_JAAS_CONFIG. Underscore-free names avoid relying on that poorly-documented rule. The listener names are unchanged on the no-password path.

  • WithKafkaUI configures the matching security protocol, mechanism and JAAS properties so the UI can still reach the broker.

  • Bug fix along the way: the health check built its ProducerConfig by assigning the whole connection string to BootstrapServers. That breaks once the connection string is a keyed list, and it carried no credentials either way, so it now resolves the endpoint and the credentials separately.

Client

Aspire.Confluent.Kafka applies the connection string onto the client configuration rather than assuming it is a bootstrap server list. A value containing no = is still assigned to BootstrapServers unchanged; otherwise it is parsed with DbConnectionStringBuilder and BootstrapServers, SecurityProtocol, SaslMechanism, SaslUsername and SaslPassword are applied. This mirrors QdrantClientSettings/MilvusClientSettings.

Connection string format

# no password (unchanged):
localhost:9092

# with SASL:
BootstrapServers=localhost:9092;SecurityProtocol=SaslPlaintext;SaslMechanism=Plain;SaslUsername=kafka;SaslPassword="<generated>"

The password is quoted so it may contain ; and =. Generated passwords use special: false since they also land in a JAAS configuration string.

Breaking change

Consistent with the Nats/Valkey/Garnet/Redis changes for this issue, existing app models get authentication turned on and a different connection string shape. Consumers going through Aspire.Confluent.Kafka are unaffected, but anything reading ConnectionStrings__<name> directly — a non-.NET client, for instance — needs to authenticate. WithPassword(null) restores the previous behavior.

Not included

  • api/Aspire.Hosting.Kafka.cs and api/Aspire.Hosting.Kafka.ats.txt are left untouched, per .agents/skills/hosting-integration-authoring/resources/polyglot-exports.md — these are release compatibility baselines updated by the release workflow after API changes are accepted. Happy to regenerate if you'd prefer them in this PR.
  • playground/kafka/KafkaBasic.AppHost/aspire-manifest.json is left to the automated "Update Playground Manifests" job. Note it is already stale on main (pinned to image tag 8.1.1 vs 8.2.0 in code).

Verification

Beyond the unit tests, this was verified against real containers:

  • A confluent-local:8.2.0 broker started with the emitted environment resolves exactly the intended configuration — listener.security.protocol.map = CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT,EXTERNAL:SASL_PLAINTEXT,INTERNAL:SASL_PLAINTEXT and sasl.enabled.mechanisms = [PLAIN].
  • Produce/consume round-trips over SASL, and a client that drops the credentials is rejected (new functional test).
  • kafbat/kafka-ui:v1.5.0 reports "status":"ONLINE" against the SASL broker, confirming the UI properties authenticate.

Contributes to #6155

Checklist

  • Is this feature complete?
    • Yes. Ready to ship.
    • No. Follow-up changes expected.
  • Are you including unit tests for the changes and scenario tests if relevant?
    • Yes
    • No
  • Did you add public API?
    • Yes
      • If yes, did you have an API Review for it?
        • Yes
        • No
      • Did you add <remarks /> and <code /> elements on your triple slash comments?
        • Yes
        • No
    • No
  • Does the change make any security assumptions or guarantees?
    • Yes
      • If yes, have you done a threat model and had a security review?
        • Yes
        • No
    • No

Authentication only — no authorizer is configured, so any authenticated client keeps full access. Traffic is not encrypted (SASL_PLAINTEXT), matching the "plain sasl is sufficient" scope in the issue. The goal is the defense-in-depth layer described in #6155, not transport security.

🤖 Generated with Claude Code

Kafka was the last container integration in microsoft#6155 without authentication.
Enable SASL/PLAIN over SASL_PLAINTEXT on the two client facing listeners,
with a generated password when none is supplied.

Hosting:
- KafkaServerResource gains UserNameParameter/PasswordParameter, a
  UserNameReference defaulting to "kafka", and Username/Password connection
  properties.
- The connection string stays a bare host:port when no password is
  configured. With a password it becomes a semicolon separated list of
  Confluent client configuration properties so the credentials can travel
  with it.
- AddKafka takes optional userName/password parameters, alongside a
  convenience overload preserving the previous signature. WithPassword and
  WithUserName allow reconfiguring afterwards, and WithPassword(null) turns
  authentication off.
- When a password is configured the client facing listeners are renamed to
  EXTERNAL/INTERNAL and mapped to SASL_PLAINTEXT. The names deliberately
  contain no underscore because the Confluent image translates
  KAFKA_FOO_BAR into foo.bar, which would otherwise require escaping the
  listener name in KAFKA_LISTENER_NAME_<LISTENER>_PLAIN_SASL_JAAS_CONFIG.
  The KRaft controller and inter broker listeners stay plaintext on the
  loopback interface.
- The health check built its ProducerConfig by assigning the connection
  string to BootstrapServers. That is wrong once the connection string is a
  keyed list, and had no credentials either way, so it now resolves the
  endpoint and the credentials separately.
- WithKafkaUI configures the matching security protocol, mechanism and JAAS
  properties so the UI can still reach the broker.

Client:
- Aspire.Confluent.Kafka applies the connection string onto the client
  configuration. A value without '=' is still treated as a bare bootstrap
  server list, otherwise it is parsed with DbConnectionStringBuilder and
  BootstrapServers, SecurityProtocol, SaslMechanism, SaslUsername and
  SaslPassword are applied.

Contributes to microsoft#6155

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 19117

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 19117"

@github-actions github-actions Bot added the area-integrations Issues pertaining to Aspire Integrations packages label Aug 7, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds default SASL/PLAIN authentication to the Kafka container-backed integration and updates Aspire clients to consume authenticated connection strings.

Changes:

  • Configures Kafka, health checks, and Kafka UI with generated or supplied credentials.
  • Parses keyed Kafka connection strings in producer and consumer integrations.
  • Adds authentication-focused tests and documentation.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/Aspire.Hosting.Kafka/KafkaBuilderExtensions.cs Configures SASL, health checks, and Kafka UI.
src/Aspire.Hosting.Kafka/KafkaServerResource.cs Adds credentials and authenticated connection strings.
src/Aspire.Hosting.Kafka/README.md Documents authentication and connection properties.
src/Components/Aspire.Confluent.Kafka/KafkaConnectionString.cs Parses keyed Kafka connection strings.
src/Components/Aspire.Confluent.Kafka/KafkaProducerSettings.cs Applies connection strings to producer configuration.
src/Components/Aspire.Confluent.Kafka/KafkaConsumerSettings.cs Applies connection strings to consumer configuration.
src/Components/Aspire.Confluent.Kafka/README.md Documents keyed connection-string support.
tests/Aspire.Hosting.Kafka.Tests/AddKafkaTests.cs Tests SASL configuration and manifests.
tests/Aspire.Hosting.Kafka.Tests/ConnectionPropertiesTests.cs Tests credential connection properties.
tests/Aspire.Hosting.Kafka.Tests/KafkaFunctionalTests.cs Tests rejection of unauthenticated clients.
tests/Aspire.Hosting.Kafka.Tests/KafkaPublicApiTests.cs Tests new API validation.
tests/Aspire.Confluent.Kafka.Tests/CommonHelpers.cs Adds shared SASL test data.
tests/Aspire.Confluent.Kafka.Tests/ProducerConfigurationTests.cs Tests producer SASL parsing.
tests/Aspire.Confluent.Kafka.Tests/ConsumerConfigurationTests.cs Tests consumer SASL parsing.

if (ConnectionString is not null)
{
Config.BootstrapServers = ConnectionString;
KafkaConnectionString.Apply(ConnectionString, Config);
if (ConnectionString is not null)
{
Config.BootstrapServers = ConnectionString;
KafkaConnectionString.Apply(ConnectionString, Config);

[Fact]
[RequiresFeature(TestFeature.Docker)]
[ActiveIssue("https://github.com/microsoft/aspire/issues/11820", typeof(PlatformDetection), nameof(PlatformDetection.IsRunningFromAzdo))]
Comment on lines +361 to +362
return ReferenceExpression.Create(
$"org.apache.kafka.common.security.plain.PlainLoginModule required username=\"{userName}\" password=\"{password}\" user_{userName}=\"{password}\";");
[ActiveIssue("https://github.com/microsoft/aspire/issues/11820", typeof(PlatformDetection), nameof(PlatformDetection.IsRunningFromAzdo))]
public async Task VerifyKafkaResourceRejectsUnauthenticatedClients()
{
var cts = new CancellationTokenSource(TimeSpan.FromMinutes(3));
Comment on lines +43 to +48
```csharp
var userName = builder.AddParameter("kafka-user");
var password = builder.AddParameter("kafka-password", secret: true);

var kafka = builder.AddKafka("messaging", userName: userName, password: password);
```
Comment on lines +103 to +104
builder.Append($"{UserNameReference}");
builder.Append($";SaslPassword=\"{PasswordParameter}\"");
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-integrations Issues pertaining to Aspire Integrations packages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants