Skip to content

Commit 3d94ecb

Browse files
authored
feat: Add IAM authentication support (#125)
Signed-off-by: currantw <[email protected]>
1 parent 5c81c7e commit 3d94ecb

File tree

13 files changed

+650
-39
lines changed

13 files changed

+650
-39
lines changed

README.md

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,13 +97,24 @@ Console.WriteLine($"User: {user}");
9797
### With Authentication and TLS
9898

9999
```csharp
100-
var config = new StandaloneClientConfigurationBuilder()
101-
.WithAddress("secure-server.example.com", 6380)
100+
// Password-based authentication with TLS.
101+
var passwordConfig = new StandaloneClientConfigurationBuilder()
102+
.WithAddress(host, port)
102103
.WithAuthentication("username", "password")
103104
.WithTls()
104105
.Build();
105106

106-
using var client = await GlideClient.CreateClient(config);
107+
using var passwordClient = await GlideClient.CreateClient(passwordConfig);
108+
109+
// IAM authentication with TLS.
110+
var iamAuthConfig = new IamAuthConfig("my-cluster", ServiceType.ElastiCache, "us-east-1");
111+
var iamConfig = new ClusterClientConfigurationBuilder()
112+
.WithAddress(host, port)
113+
.WithAuthentication("username", iamAuthConfig)
114+
.WithTls(true)
115+
.Build();
116+
117+
using var iamClient = await GlideClient.CreateClient(iamConfig);
107118
```
108119

109120
## Core API Examples

rust/src/ffi.rs

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ use std::{
77

88
use glide_core::{
99
client::{
10-
AuthenticationInfo, ConnectionRequest, ConnectionRetryStrategy, NodeAddress,
11-
ReadFrom as coreReadFrom, TlsMode,
10+
ConnectionRequest, ConnectionRetryStrategy, NodeAddress, ReadFrom as coreReadFrom, TlsMode,
1211
},
1312
request_type::RequestType,
1413
};
@@ -65,7 +64,7 @@ pub struct ConnectionConfig {
6564
pub has_connection_retry_strategy: bool,
6665
pub connection_retry_strategy: ConnectionRetryStrategy,
6766
pub has_authentication_info: bool,
68-
pub authentication_info: Credentials,
67+
pub authentication_info: AuthenticationInfo,
6968
pub database_id: u32,
7069
pub has_protocol: bool,
7170
pub protocol: redis::ProtocolVersion,
@@ -114,10 +113,32 @@ pub(crate) unsafe fn create_connection_request(
114113
client_name: unsafe { ptr_to_opt_str(config.client_name) },
115114
lib_name: option_env!("GLIDE_NAME").map(|s| s.to_string()),
116115
authentication_info: if config.has_authentication_info {
117-
Some(AuthenticationInfo {
118-
username: unsafe { ptr_to_opt_str(config.authentication_info.username) },
119-
password: unsafe { ptr_to_opt_str(config.authentication_info.password) },
120-
iam_config: None,
116+
let auth_info = config.authentication_info;
117+
let iam_config = if auth_info.has_iam_credentials {
118+
Some(glide_core::client::IamAuthenticationConfig {
119+
cluster_name: unsafe { ptr_to_str(auth_info.iam_credentials.cluster_name) },
120+
region: unsafe { ptr_to_str(auth_info.iam_credentials.region) },
121+
service_type: match auth_info.iam_credentials.service_type {
122+
ServiceType::ElastiCache => glide_core::iam::ServiceType::ElastiCache,
123+
ServiceType::MemoryDB => glide_core::iam::ServiceType::MemoryDB,
124+
},
125+
refresh_interval_seconds: if auth_info
126+
.iam_credentials
127+
.has_refresh_interval_seconds
128+
{
129+
Some(auth_info.iam_credentials.refresh_interval_seconds)
130+
} else {
131+
None
132+
},
133+
})
134+
} else {
135+
None
136+
};
137+
138+
Some(glide_core::client::AuthenticationInfo {
139+
username: unsafe { ptr_to_opt_str(auth_info.username) },
140+
password: unsafe { ptr_to_opt_str(auth_info.password) },
141+
iam_config,
121142
})
122143
} else {
123144
None
@@ -210,11 +231,29 @@ pub enum ReadFromStrategy {
210231
/// A mirror of [`AuthenticationInfo`] adopted for FFI.
211232
#[repr(C)]
212233
#[derive(Debug, Clone, Copy)]
213-
pub struct Credentials {
214-
/// zero pointer is valid, means no username is given (`None`)
234+
pub struct AuthenticationInfo {
215235
pub username: *const c_char,
216-
/// zero pointer is valid, means no password is given (`None`)
217236
pub password: *const c_char,
237+
pub has_iam_credentials: bool,
238+
pub iam_credentials: IamCredentials,
239+
}
240+
241+
/// A mirror of [`IamCredentials`] adopted for FFI.
242+
#[repr(C)]
243+
#[derive(Debug, Clone, Copy)]
244+
pub struct IamCredentials {
245+
pub cluster_name: *const c_char,
246+
pub region: *const c_char,
247+
pub service_type: ServiceType,
248+
pub has_refresh_interval_seconds: bool,
249+
pub refresh_interval_seconds: u32,
250+
}
251+
252+
#[repr(C)]
253+
#[derive(Debug, Clone, Copy)]
254+
pub enum ServiceType {
255+
ElastiCache = 0,
256+
MemoryDB = 1,
218257
}
219258

220259
#[repr(C)]

rust/src/lib.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -741,3 +741,66 @@ unsafe fn convert_string_pointer_array_to_vector<'a>(
741741

742742
result
743743
}
744+
745+
/// Manually refresh the IAM authentication token.
746+
///
747+
/// This function triggers an immediate refresh of the IAM token and updates the connection.
748+
/// It is only available if the client was created with IAM authentication.
749+
///
750+
/// # Arguments
751+
///
752+
/// * `client_ptr` - A pointer to a valid client returned from [`create_client`].
753+
/// * `callback_index` - A unique identifier for the callback to be called when the command completes.
754+
///
755+
/// # Safety
756+
///
757+
/// * `client_ptr` must not be `null`.
758+
/// * `client_ptr` must be able to be safely casted to a valid [`Arc<Client>`] via [`Arc::from_raw`]. See the safety documentation of [`Arc::from_raw`].
759+
/// * This function should only be called with a `client_ptr` created by [`create_client`], before [`close_client`] was called with the pointer.
760+
#[unsafe(no_mangle)]
761+
pub unsafe extern "C-unwind" fn refresh_iam_token(
762+
client_ptr: *const c_void,
763+
callback_index: usize,
764+
) {
765+
let client = unsafe {
766+
Arc::increment_strong_count(client_ptr);
767+
Arc::from_raw(client_ptr as *mut Client)
768+
};
769+
let core = client.core.clone();
770+
771+
let mut panic_guard = PanicGuard {
772+
panicked: true,
773+
failure_callback: core.failure_callback,
774+
callback_index,
775+
};
776+
777+
client.runtime.spawn(async move {
778+
let mut async_panic_guard = PanicGuard {
779+
panicked: true,
780+
failure_callback: core.failure_callback,
781+
callback_index,
782+
};
783+
784+
let result = core.client.clone().refresh_iam_token().await;
785+
match result {
786+
Ok(()) => {
787+
let response = ResponseValue::from_value(redis::Value::Okay);
788+
let ptr = Box::into_raw(Box::new(response));
789+
unsafe { (core.success_callback)(callback_index, ptr) };
790+
}
791+
Err(err) => unsafe {
792+
report_error(
793+
core.failure_callback,
794+
callback_index,
795+
error_message(&err),
796+
error_type(&err),
797+
);
798+
},
799+
};
800+
async_panic_guard.panicked = false;
801+
drop(async_panic_guard);
802+
});
803+
804+
panic_guard.panicked = false;
805+
drop(panic_guard);
806+
}

sources/Valkey.Glide/Abstract/ConnectionMultiplexer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ internal static T CreateClientConfigBuilder<T>(ConfigurationOptions configuratio
180180
config.UseTls = configuration.Ssl;
181181
_ = configuration.ConnectTimeout.HasValue ? config.ConnectionTimeout = TimeSpan.FromMilliseconds(configuration.ConnectTimeout.Value) : new();
182182
_ = configuration.ResponseTimeout.HasValue ? config.RequestTimeout = TimeSpan.FromMilliseconds(configuration.ResponseTimeout.Value) : new();
183-
_ = (configuration.User ?? configuration.Password) is not null ? config.Authentication = (configuration.User, configuration.Password!) : new();
183+
_ = (configuration.User ?? configuration.Password) is not null ? config.WithAuthentication(configuration.User, configuration.Password!) : new();
184184
_ = configuration.ClientName is not null ? config.ClientName = configuration.ClientName : "";
185185
if (configuration.Protocol is not null)
186186
{

sources/Valkey.Glide/BaseClient.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,26 @@ public void Dispose()
3838

3939
public override int GetHashCode() => (int)ClientPointer;
4040

41+
/// <summary>
42+
/// Manually refresh the IAM authentication token.
43+
/// This method is only available when the client is configured with IAM authentication.
44+
/// </summary>
45+
/// <returns>A task that completes when the refresh attempt finishes.</returns>
46+
public async Task RefreshIamTokenAsync()
47+
{
48+
Message message = MessageContainer.GetMessageForCall();
49+
RefreshIamTokenFfi(ClientPointer, (ulong)message.Index);
50+
IntPtr response = await message;
51+
try
52+
{
53+
HandleResponse(response);
54+
}
55+
finally
56+
{
57+
FreeResponse(response);
58+
}
59+
}
60+
4161
#endregion public methods
4262

4363
#region protected methods

0 commit comments

Comments
 (0)