Skip to content

Conversation

@michael-redpanda
Copy link
Contributor

This PR adds support for authenticating using SASL/PLAIN with Shadow Linking.

This adds a new authentication configuration called PlainConfig:

// Authentication config.  Supports:
// * SASL/SCRAM
// * SASL/PLAIN
message AuthenticationConfiguration {
    oneof authentication {
        // SASL/SCRAM configuration
        ScramConfig scram_configuration = 1;
        // SASL/PLAIN configuration
        PlainConfig plain_configuration = 2;
    }
}
// PLAIN settings
message PlainConfig {
    // PLAIN username
    string username = 1;
    // Password
    string password = 2 [(google.api.field_behavior) = INPUT_ONLY];
    // Indicates that the password has been set
    bool password_set = 3 [(google.api.field_behavior) = OUTPUT_ONLY];
    // Timestamp of when the password was last set - only valid if password_set
    // is true
    google.protobuf.Timestamp password_set_at = 4
        [(google.api.field_behavior) = OUTPUT_ONLY];
}

Backports Required

  • none - not a bug fix
  • none - this is a backport
  • none - issue does not exist in previous branches
  • none - papercut/not impactful enough to backport
  • v25.3.x
  • v25.2.x
  • v25.1.x
  • v24.3.x

Release Notes

Improvements

  • Adds support for SASL/PLAIN authentication for Shadow Linking

@michael-redpanda michael-redpanda self-assigned this Nov 23, 2025
Copilot AI review requested due to automatic review settings November 23, 2025 19:28
@michael-redpanda michael-redpanda requested review from a team and rockwotj as code owners November 23, 2025 19:28
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds support for SASL/PLAIN authentication to Shadow Linking, which previously only supported SASL/SCRAM. The implementation introduces a new PlainConfig message type in the protobuf schema, extends the authentication handling in the converter layer, and adds test coverage for PLAIN authentication.

  • Adds PlainConfig protobuf message with username/password fields similar to ScramConfig
  • Updates authentication handling to support PLAIN alongside existing SCRAM mechanisms
  • Implements PLAIN authentication flow in the Kafka client broker

Reviewed changes

Copilot reviewed 25 out of 43 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
proto/redpanda/core/admin/v2/shadow_link.proto Adds PlainConfig message and extends AuthenticationConfiguration oneof
src/v/redpanda/admin/services/shadow_link/converter.cc Implements conversion logic between PlainConfig proto and internal credentials
src/v/kafka/client/broker.cc Adds PLAIN authentication implementation with proper message formatting
src/v/kafka/client/broker.h Declares do_authenticate_plain method
src/v/cluster/cluster_link/frontend.cc Extends validation to accept "PLAIN" mechanism
src/v/redpanda/admin/services/shadow_link/tests/converter_test.cc Adds test coverage for PLAIN config creation and conversion
tests/rptest/tests/cluster_linking_topic_syncing_test.py Adds test class for PLAIN authentication scenario
tests/rptest/clients/admin/proto/* Generated Python protobuf bindings updates

Copy link
Contributor

@rockwotj rockwotj left a comment

Choose a reason for hiding this comment

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

you need to regenerate protos using the script in the tools directory of the redpanda repository. The old one in vtools is out of date (I will remove)

@vbotbuildovich
Copy link
Collaborator

vbotbuildovich commented Nov 23, 2025

CI test results

test results on build#76858
test_class test_method test_arguments test_kind job_url test_status passed reason test_history
EndToEndCloudTopicsTest test_delete_records null integration https://buildkite.com/redpanda/redpanda/builds/76858#019ab25a-7b1c-4d14-abb0-ea89bebbed5b FLAKY 20/21 upstream reliability is '99.75550122249389'. current run reliability is '95.23809523809523'. drift is 4.51741 and the allowed drift is set to 50. The test should PASS https://redpanda.metabaseapp.com/dashboard/87-tests?tab=142-dt-individual-test-history&test_class=EndToEndCloudTopicsTest&test_method=test_delete_records
MountUnmountIcebergTest test_simple_remount {"cloud_storage_type": 1} integration https://buildkite.com/redpanda/redpanda/builds/76858#019ab260-387a-45e2-b5f2-f27284937fc1 FLAKY 14/21 upstream reliability is '78.63910422049956'. current run reliability is '66.66666666666666'. drift is 11.97244 and the allowed drift is set to 50. The test should PASS https://redpanda.metabaseapp.com/dashboard/87-tests?tab=142-dt-individual-test-history&test_class=MountUnmountIcebergTest&test_method=test_simple_remount
test results on build#76874
test_class test_method test_arguments test_kind job_url test_status passed reason test_history
ConsumerOffsetsConsistencyTest test_flipping_leadership null integration https://buildkite.com/redpanda/redpanda/builds/76874#019ab5b7-de24-4933-8ffe-64f0e40e449f FLAKY 20/21 upstream reliability is '100.0'. current run reliability is '95.23809523809523'. drift is 4.7619 and the allowed drift is set to 50. The test should PASS https://redpanda.metabaseapp.com/dashboard/87-tests?tab=142-dt-individual-test-history&test_class=ConsumerOffsetsConsistencyTest&test_method=test_flipping_leadership
MountUnmountIcebergTest test_simple_remount {"cloud_storage_type": 1} integration https://buildkite.com/redpanda/redpanda/builds/76874#019ab5b2-0624-458f-ac3f-ae78e5bd69c7 FLAKY 18/21 upstream reliability is '78.66071428571428'. current run reliability is '85.71428571428571'. drift is -7.05357 and the allowed drift is set to 50. The test should PASS https://redpanda.metabaseapp.com/dashboard/87-tests?tab=142-dt-individual-test-history&test_class=MountUnmountIcebergTest&test_method=test_simple_remount

@michael-redpanda
Copy link
Contributor Author

you need to regenerate protos using the script in the tools directory of the redpanda repository. The old one in vtools is out of date (I will remove)

thanks! is that new?

@michael-redpanda
Copy link
Contributor Author

Force push:

  • Used correct proto gen script

Comment on lines +529 to +541
std::string bytes;
// 2 - number of null characters in the PLAIN auth message
bytes.reserve(2 + username.size() + password.size());
bytes.push_back('\0');
bytes.append(username.cbegin(), username.cend());
bytes.push_back('\0');
bytes.append(password.cbegin(), password.cend());
req.data.auth_bytes = bytes::from_string(std::move(bytes));
Copy link
Member

Choose a reason for hiding this comment

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

nitpick:

Suggested change
std::string bytes;
// 2 - number of null characters in the PLAIN auth message
bytes.reserve(2 + username.size() + password.size());
bytes.push_back('\0');
bytes.append(username.cbegin(), username.cend());
bytes.push_back('\0');
bytes.append(password.cbegin(), password.cend());
req.data.auth_bytes = bytes::from_string(std::move(bytes));
auto bytes = fmt::format("\0{}\0{}", username, password);
req.data.auth_bytes = bytes::from_string(bytes);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So I tried this but I think the std::string_view that gets created via the bytes::from_string reports a length of 0 since the string begins with a null character

Copy link
Member

Choose a reason for hiding this comment

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

You, my bad, you'll need:

    auto bytes = fmt::format("\0{}\0{}", username, password);
    req.data.auth_bytes = bytes::from_string(std::string_view{bytes.begin(), bytes.end()});

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That unfortunately won't work either... end() is start() + size() and in this case size() is 0

Copy link
Member

Choose a reason for hiding this comment

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

It should work if the format string is a std::string_view!

Copy link
Member

Choose a reason for hiding this comment

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

It should work if the format string is a std::string_view!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Can you provide an example of what you mean please


class ClusterLinkingTopicSyncingWithPlain(ClusterLinkingTopicSyncingTestBase):
"""
Run the same battery of tests with PLAIN
Copy link
Member

Choose a reason for hiding this comment

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

The same as what?

Is it necessary to run the whole test suite with each auth mechanism?

@rockwotj
Copy link
Contributor

you need to regenerate protos using the script in the tools directory of the redpanda repository. The old one in vtools is out of date (I will remove)

thanks! is that new?

Yes it is, see:

Also the rpk protogen stuff is required for now, although hopefully we can switch those to work off of the bufbuild protos instead of the checked in ones (cc: @r-vasquez)

For now you can fix those via:

buf generate --path proto/redpanda/core/admin/v2/shadow_link.proto --path proto/redpanda/core/common/v1/acl.proto --path proto/redpanda/core/common/v1/tls.proto

@michael-redpanda
Copy link
Contributor Author

Force push:

  • Addressed some PR comments

Second force push:

  • Rebased off of dev

@michael-redpanda
Copy link
Contributor Author

Force push:

  • another rebase

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.

} else {
throw std::invalid_argument(
ssx::sformat(
"Unknown SCRAM mechanism: {}", scram.mechanism));
Copy link

Copilot AI Nov 24, 2025

Choose a reason for hiding this comment

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

The error message refers to 'SCRAM mechanism' but this error can now be triggered when using PLAIN authentication (which is not a SCRAM mechanism). Consider changing to 'Unknown authentication mechanism: {}' or adding separate validation to distinguish between SCRAM and PLAIN cases.

Suggested change
"Unknown SCRAM mechanism: {}", scram.mechanism));
"Unknown authentication mechanism: {}", scram.mechanism));

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

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

If we're gonna do that, it can go in another else on the outer scope. It's unreachable, though.

Comment on lines 353 to 364
} else if (mechanism == security::scram_sha512_authenticator::name) {
co_await do_authenticate_scram512(username, password);
} else if (mechanism == security::oidc::sasl_authenticator::name) {
co_await do_authenticate_oauthbearer(password);
} else if (mechanism == security::plain_authenticator::name) {
co_await do_authenticate_plain(username, password);
} else {
throw broker_error{
_node_id,
error_code::sasl_authentication_failed,
fmt_with_ctx(ssx::sformat, "Unknown mechanism: {}", mechanism)};
}
Copy link

Copilot AI Nov 24, 2025

Choose a reason for hiding this comment

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

The newly added else-if at line 357-358 makes the earlier check at lines 321-328 redundant. The earlier check validates that mechanism must be one of scram_sha256, scram_sha512, oidc, or plain, but the else block at lines 359-364 now provides the same validation with a better error message. Consider removing the earlier validation block to avoid duplication.

Copilot uses AI. Check for mistakes.
&& c.mechanism != "PLAIN") {
vlog(
cluster::clusterlog.warn,
"Unsupported SCRAM mechanism: {}",
Copy link

Copilot AI Nov 24, 2025

Choose a reason for hiding this comment

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

The log message at line 988 still refers to 'Unsupported SCRAM mechanism' but PLAIN is not a SCRAM mechanism. The message should be updated to 'Unsupported authentication mechanism' to accurately reflect that both SCRAM and PLAIN mechanisms are now supported.

Suggested change
"Unsupported SCRAM mechanism: {}",
"Unsupported authentication mechanism: {}",

Copilot uses AI. Check for mistakes.
@michael-redpanda michael-redpanda merged commit 51fd551 into redpanda-data:dev Nov 25, 2025
31 checks passed
@vbotbuildovich
Copy link
Collaborator

/backport v25.3.x

@vbotbuildovich
Copy link
Collaborator

Failed to create a backport PR to v25.3.x branch. I tried:

git remote add upstream https://github.com/redpanda-data/redpanda.git
git fetch --all
git checkout -b backport-pr-28708-v25.3.x-922 remotes/upstream/v25.3.x
git cherry-pick -x 31614c7f68 61430f113e 8acce1791f

Workflow run logs.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants