From 08f7d4eab4f3483495f8ff11be0e107dca745551 Mon Sep 17 00:00:00 2001 From: Jeremy Parr-Pearson Date: Fri, 23 Oct 2026 22:00:55 +0000 Subject: [PATCH 1/2] fix(ruby): require TLS for IAM auth Signed-off-by: Jeremy Parr-Pearson --- CHANGELOG.md | 2 + README.md | 2 +- .../valkey/iam_authentication_test.rb | 52 +++++++++++++++++-- valkey-glide | 2 +- 4 files changed, 51 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ae580ff..21be7fb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ ### Fixes +* fix(ruby): IAM authentication now requires TLS. Configuring IAM auth without TLS is rejected at client creation instead of sending the IAM token over a plaintext connection ([#397](https://github.com/valkey-io/valkey-glide-ruby/pull/397)) + * fix(ruby): reject `cluster_mode: true` without an explicit seed instead of silently connecting to the standalone `127.0.0.1:6379` default ([#344](https://github.com/valkey-io/valkey-glide-ruby/pull/344)) * fix(ruby): treat an empty `lib_name` or `client_info_tag` as "not configured". Previously `lib_name: ""` plus a tag composed `(tag)` instead of `GlideRuby(tag)`, which glide-core rejects, surfacing as `Valkey::CannotConnectError` at client creation ([#246](https://github.com/valkey-io/valkey-glide-ruby/pull/246)) diff --git a/README.md b/README.md index 3d0cc214..48e1321e 100644 --- a/README.md +++ b/README.md @@ -106,7 +106,7 @@ client.get("foo") | `url` | `redis://`, `rediss://`, `valkey://`, or `valkeys://` URI (merged with explicit options) | | `db` | Database index (standalone only) | | `password`, `username` | Authentication | -| `iam_config` | A `Valkey::IamAuthConfig` for AWS IAM authentication. Requires `username` and cannot be combined with `password`. | +| `iam_config` | A `Valkey::IamAuthConfig` for AWS IAM authentication. Requires `username` and TLS (`ssl: true`), and cannot be combined with `password`. | | `timeout` | Request timeout in seconds (default `5.0`) | | `connect_timeout` | Connection timeout in seconds | | `ssl`| Enable TLS if true | diff --git a/test/integration/valkey/iam_authentication_test.rb b/test/integration/valkey/iam_authentication_test.rb index e5c67db5..5f542ce8 100644 --- a/test/integration/valkey/iam_authentication_test.rb +++ b/test/integration/valkey/iam_authentication_test.rb @@ -5,9 +5,13 @@ module ValkeyTests # server without IAM enforcement, using the placeholder credentials installed by # Helper::MockAwsCredentials. # + # IAM auth requires TLS (an IAM token must never traverse plaintext), so the + # client-creation test connects over the standalone TLS server and skips when no + # TLS server is available (including the cluster context, which has no TLS endpoint). + # # What they confirm: # - `iam_config:` and `username:` reach GLIDE Core, which resolves credentials, - # signs a token, and authenticates with it in standalone and cluster mode. + # signs a token, and authenticates with it over a TLS connection. # - `refresh_iam_token` round-trips through FFI for every ServiceType. # - Ruby rejects IAM configuration without a username, Core's IAM interval # validation errors surface as Valkey::CannotConnectError, and refreshing @@ -18,8 +22,10 @@ module ValkeyTests # signature, cluster name, region, service, and expiry are never checked. # - Real AWS credential resolution (profiles, instance metadata, STS). # - Automatic refresh at the configured interval. - # - Behavior against real ElastiCache or MemoryDB, including required TLS. + # - Behavior against real ElastiCache or MemoryDB. module IamAuthentication + include SslHelper + IAM_USERNAME = "default" IAM_CLUSTER_NAME = "test-cluster" IAM_REGION = "us-east-1" @@ -30,7 +36,9 @@ module IamAuthentication :test_iam_client_creation_and_manual_refresh, service: [Valkey::ServiceType::ELASTICACHE, Valkey::ServiceType::MEMORYDB] ) do |service| - client = _new_client(username: IAM_USERNAME, iam_config: iam_config(service: service)) + skip_unless_tls_available + + client = _new_iam_tls_client(username: IAM_USERNAME, iam_config: iam_config(service: service)) assert_equal "PONG", client.ping assert_equal "OK", client.refresh_iam_token @@ -45,8 +53,14 @@ def test_iam_requires_username assert_includes error.message, "username is required for iam_config" end + # The refresh-interval checks live in GLIDE Core, behind the IAM-requires-TLS + # guard, so these must connect over TLS to reach the interval validation + # instead of being rejected for plaintext first. def test_invalid_iam_refresh_interval_fails_creation + skip_unless_tls_available + error = assert_iam_creation_fails( + tls: true, username: IAM_USERNAME, iam_config: iam_config(refresh_interval_seconds: 0) ) @@ -55,7 +69,10 @@ def test_invalid_iam_refresh_interval_fails_creation end def test_max_iam_refresh_interval_fails_creation + skip_unless_tls_available + error = assert_iam_creation_fails( + tls: true, username: IAM_USERNAME, iam_config: iam_config(refresh_interval_seconds: 43_200) ) @@ -63,6 +80,8 @@ def test_max_iam_refresh_interval_fails_creation assert_includes error.message, "Invalid refresh interval" end + # "Unknown service type" is rejected in Ruby before Client::new, so this needs + # no TLS connection and runs in every context. def test_invalid_iam_service_fails_creation error = assert_iam_creation_fails( username: IAM_USERNAME, @@ -80,6 +99,27 @@ def test_refresh_iam_token_without_iam private + # IAM auth requires TLS, so build the client against the standalone TLS server + # (port SSL_PORT) with the suite's CA cert, mirroring the TLS connection tests. + def _new_iam_tls_client(**options) + Valkey.new( + host: "127.0.0.1", + port: defined?(SSL_PORT) ? SSL_PORT : 6380, + ssl: true, + ssl_params: { ca_file: ssl_ca_cert_path }, + timeout: TIMEOUT, + **options + ) + end + + # No TLS server exists for the cluster context, and TLS is skipped on platforms + # where the harness can't stand one up (e.g. macOS CI via SKIP_TLS_TESTS). + def skip_unless_tls_available + skip("TLS tests disabled via SKIP_TLS_TESTS") if defined?(SKIP_TLS_TESTS) && SKIP_TLS_TESTS + skip("no TLS server for the cluster context") if cluster_mode? + skip("TLS_CERT_DIR is not set") if ENV.fetch("TLS_CERT_DIR", "").empty? + end + def iam_config(service: Valkey::ServiceType::ELASTICACHE, refresh_interval_seconds: nil) Valkey::IamAuthConfig.new( cluster_name: IAM_CLUSTER_NAME, @@ -89,9 +129,11 @@ def iam_config(service: Valkey::ServiceType::ELASTICACHE, refresh_interval_secon ) end - def assert_iam_creation_fails(**options) + def assert_iam_creation_fails(tls: false, **options) client = nil - assert_raises(Valkey::CannotConnectError) { client = _new_client(**options) } + assert_raises(Valkey::CannotConnectError) do + client = tls ? _new_iam_tls_client(**options) : _new_client(**options) + end ensure client&.close end diff --git a/valkey-glide b/valkey-glide index ae02fdb7..b360e69b 160000 --- a/valkey-glide +++ b/valkey-glide @@ -1 +1 @@ -Subproject commit ae02fdb78750dac4621633a10826c76fa5c9e647 +Subproject commit b360e69bcc72a06b7bb933ba3fdbdf9708f5059f From da522206458a82aaaa6370e6f25346496bcedc23 Mon Sep 17 00:00:00 2001 From: Jeremy Parr-Pearson Date: Tue, 27 Oct 2026 18:06:21 +0000 Subject: [PATCH 2/2] test(ruby): trim redundant comments in IAM auth test Signed-off-by: Jeremy Parr-Pearson --- test/integration/valkey/iam_authentication_test.rb | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/test/integration/valkey/iam_authentication_test.rb b/test/integration/valkey/iam_authentication_test.rb index 5f542ce8..1feac233 100644 --- a/test/integration/valkey/iam_authentication_test.rb +++ b/test/integration/valkey/iam_authentication_test.rb @@ -5,9 +5,7 @@ module ValkeyTests # server without IAM enforcement, using the placeholder credentials installed by # Helper::MockAwsCredentials. # - # IAM auth requires TLS (an IAM token must never traverse plaintext), so the - # client-creation test connects over the standalone TLS server and skips when no - # TLS server is available (including the cluster context, which has no TLS endpoint). + # Note: IAM requires TLS which is not set up for cluster tests. # # What they confirm: # - `iam_config:` and `username:` reach GLIDE Core, which resolves credentials, @@ -80,8 +78,6 @@ def test_max_iam_refresh_interval_fails_creation assert_includes error.message, "Invalid refresh interval" end - # "Unknown service type" is rejected in Ruby before Client::new, so this needs - # no TLS connection and runs in every context. def test_invalid_iam_service_fails_creation error = assert_iam_creation_fails( username: IAM_USERNAME, @@ -99,8 +95,6 @@ def test_refresh_iam_token_without_iam private - # IAM auth requires TLS, so build the client against the standalone TLS server - # (port SSL_PORT) with the suite's CA cert, mirroring the TLS connection tests. def _new_iam_tls_client(**options) Valkey.new( host: "127.0.0.1", @@ -112,8 +106,6 @@ def _new_iam_tls_client(**options) ) end - # No TLS server exists for the cluster context, and TLS is skipped on platforms - # where the harness can't stand one up (e.g. macOS CI via SKIP_TLS_TESTS). def skip_unless_tls_available skip("TLS tests disabled via SKIP_TLS_TESTS") if defined?(SKIP_TLS_TESTS) && SKIP_TLS_TESTS skip("no TLS server for the cluster context") if cluster_mode?