Skip to content

Add SAS token support to the Rust AMQP backend #7295

Description

@j7nw4r

Summary

The Rust AMQP backend cannot authenticate with a SAS token. Three defects block it, one in each layer of the stack. ServiceBusSasConnectionStringCredential still compiles and links against the Rust backend, and it returns an empty token.

The request: implement SAS token generation and CBS SAS put-token for the Rust AMQP backend, so connection string authentication works on the default backend.

Motivation

The three defects

1. The SAS token generator is a stub. GenerateSasToken at sdk/core/azure-core-amqp/src/impl/rust_amqp/amqp/connection_string_credential.cpp:15-20 discards the expiration time and returns an empty string. The uAMQP copy at sdk/core/azure-core-amqp/src/impl/uamqp/amqp/connection_string_credential.cpp:37 does the real work with SASToken_Create from azure_c_shared_utility. That library is not built when USE_RUST_AMQP is on, so the Rust copy was left empty.

The stub fails silently. GetToken at sdk/core/azure-core-amqp/src/amqp/connection_string_credential.cpp:127-137 copies the empty string into AccessToken::Token and reports success. No unit test asserts a generated token value, so the stub passes CI.

2. The C++ CBS layer rejects the SAS token type. ClaimsBasedSecurityImpl::PutToken at sdk/core/azure-core-amqp/src/impl/rust_amqp/amqp/claim_based_security.cpp:79-83 throws "Unsupported Token Type" for every value except CbsTokenType::Jwt.

3. The FFI boundary drops the token type. amqpclaimsbasedsecurity_authorize_path at sdk/core/azure-core-amqp/src/impl/rust_amqp/rust_amqp/rust_wrapper/src/amqp/cbs.rs:120-138 accepts no token type and passes None to the Rust crate. fe2o3/cbs.rs:81 turns None into "jwt".

Four things already work, so the change is small

The shared connection layer already selects the SAS token type. sdk/core/azure-core-amqp/src/amqp/connection.cpp:208 picks CbsTokenType::Sas when IsSasCredential() returns true.

The Rust crate already accepts a token type. azure_core_amqp/src/cbs.rs:50-56 declares token_type: Option<String>, and the doc comment names "servicebus.windows.net:sastoken" as a supported value.

The Rust connection already uses SASL ANONYMOUS. azure_core_amqp/src/fe2o3/connection.rs:49 sets SaslProfile::Anonymous. AMQP CBS requires that mechanism.

The connection string parser and the credential class are backend neutral, and they have unit tests at sdk/core/azure-core-amqp/test/ut/connection_string_tests.cpp.

Impact

PR #7293 restores the connection string constructors for uAMQP only. It throws a clear unsupported-backend error for Rust AMQP. Rust AMQP is the default backend, so most builds hit the error.

The Event Hubs emulator does not support Microsoft Entra ID. A connection string is the only way to authenticate against it. So the emulator stays out of reach for default builds.

ServiceBusSasConnectionStringCredential is public _internal API in azure-core-amqp. Any other library that uses it on the Rust backend receives an empty token today, and it receives no error. A future C++ Service Bus library hits the same wall.

Where to compute the HMAC

There are two options, and the owner must pick one.

Option A puts the work in Rust. The workspace root Cargo.toml already declares hmac (line 39), sha2 (line 61), and base64 (line 26). A new FFI entry point returns the token string. This needs no new CMake linking, and it is portable across all three platforms.

Option B puts the work in C++. This needs an HMAC-SHA256 primitive inside azure-core-amqp. azure-core exposes none. azure-storage-common has one at sdk/storage/azure-storage-common/src/crypt.cpp:158 (BCrypt) and :212 (OpenSSL), and azure-core-amqp cannot depend on storage. The Rust backend also links only -framework Security on macOS (sdk/core/azure-core-amqp/CMakeLists.txt:342), and not OpenSSL, so a C++ path needs a third platform implementation or a CMake change.

Option A is the smaller change. Option B keeps the token format in one language and avoids an FFI round trip.

One detail that a rewrite gets wrong easily

The Service Bus SAS documentation states that the shared access key is a plain text string in Base64 representation, and that callers must not decode it before use. The uAMQP path agrees. It Base64-encodes the raw key string, and SASToken_Create Base64-decodes its argument, so the HMAC key ends up as the raw UTF-8 bytes of SharedAccessKey.

A new implementation must use those raw bytes as the HMAC key. A Base64 decode of the key produces a signature that the service rejects.

The same page gives the token format:

SharedAccessSignature sig=<signature>&se=<expiry>&skn=<keyName>&sr=<URL-encoded-resourceURI>

And the signature:

urlencode(base64(hmacsha256(urlencode(resourceUri) + "\n" + expiry, key)))

The expiry is a count of seconds since the UNIX epoch. The uAMQP path builds the resource URI from GetEndpoint() with GetEntityPath() appended.

Proposal

  • Pick the Rust option or the C++ option for the HMAC, and record the reason in this issue.
  • Implement ServiceBusSasConnectionStringCredential::GenerateSasToken for the Rust backend (src/impl/rust_amqp/amqp/connection_string_credential.cpp:15). Sign with the raw UTF-8 bytes of SharedAccessKey. Build the resource URI from GetEndpoint() and GetEntityPath().
  • Make GenerateSasToken throw when it cannot build a token. An empty token must never reach PutToken.
  • Add a token type parameter to the FFI function amqpclaimsbasedsecurity_authorize_path (.../rust_wrapper/src/amqp/cbs.rs:120). Pass Some("servicebus.windows.net:sastoken") for a SAS token, and None for a JWT. Update the generated header and every caller in the same change.
  • Map CbsTokenType::Sas to that string in PutToken (src/impl/rust_amqp/amqp/claim_based_security.cpp:79), and delete the throw. Keep a throw for CbsTokenType::Invalid.
  • Remove the unsupported-backend guard that PR feat(eventhubs): restore connection string authentication #7293 adds to the ProducerClient and ConsumerClient connection string constructors, and delete the two tests that assert the guard.
  • Update sdk/eventhubs/azure-messaging-eventhubs/README.md. Delete the statement that connection string authentication needs the uAMQP backend.
  • Add a CHANGELOG.md entry for azure-core-amqp and for azure-messaging-eventhubs.
  • Add a unit test with a fixed key, a fixed resource URI, and a fixed expiry. Assert the exact token string. Run the test on both backends, so the two backends stay in agreement.

Validation

  • The Rust backend and the uAMQP backend produce the same token, byte for byte, for the same key, resource URI, and expiry.
  • azure-core-amqp builds, and its unit tests pass, on Windows, Linux, and macOS with USE_RUST_AMQP.
  • A ProducerClient built from a connection string sends an event to a live Event Hub on the Rust backend.
  • A ConsumerClient built from a connection string reads that event back on the Rust backend.
  • A client built from an emulator connection string connects to the Event Hubs emulator over amqp:// on the Rust backend.
  • A connection string with a wrong SharedAccessKey fails with an authentication error. It does not hang, and it does not crash.
  • The JWT path still works on the Rust backend. The Microsoft Entra ID live tests pass.

Metadata

Metadata

Labels

Event Hubsfeature-requestThis issue requires a new behavior in the product in order be resolved.needs-triageWorkflow: This is a new issue that needs to be triaged to the appropriate team.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions