From 704442d6a0e28584376af4597f255db438e8f430 Mon Sep 17 00:00:00 2001 From: Torrey Payne <11740989+torreypayne@users.noreply.github.com> Date: Wed, 21 Oct 2026 17:31:09 +0000 Subject: [PATCH] fix(external_account): convert executable timeout_millis to seconds for Timeout.timeout External-account executable configuration defines timeout_millis in milliseconds, but PluggableAuthCredentials#subprocess_with_timeout passes the value to Timeout.timeout, which expects seconds. Convert @credential_source_executable_timeout_millis to seconds (timeout_millis / 1000.0) when invoking subprocess_with_timeout, and add specs verifying the converted timeout duration. Fixes #595 --- .../external_account/pluggable_credentials.rb | 3 +- .../pluggable_credentials_spec.rb | 42 +++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/lib/googleauth/external_account/pluggable_credentials.rb b/lib/googleauth/external_account/pluggable_credentials.rb index 82db663a..545ecc0f 100644 --- a/lib/googleauth/external_account/pluggable_credentials.rb +++ b/lib/googleauth/external_account/pluggable_credentials.rb @@ -13,6 +13,7 @@ # limitations under the License. require "open3" +require "timeout" require "time" require "json" require "googleauth/errors" @@ -88,7 +89,7 @@ def retrieve_subject_token! # environment variable injection env = inject_environment_variables output = subprocess_with_timeout env, @credential_source_executable_command, - @credential_source_executable_timeout_millis + (@credential_source_executable_timeout_millis / 1000.0) response = JSON.parse output, symbolize_names: true parse_subject_token response end diff --git a/spec/googleauth/external_account/pluggable_credentials_spec.rb b/spec/googleauth/external_account/pluggable_credentials_spec.rb index 3c242558..1107df2c 100644 --- a/spec/googleauth/external_account/pluggable_credentials_spec.rb +++ b/spec/googleauth/external_account/pluggable_credentials_spec.rb @@ -531,4 +531,46 @@ end end end + + describe "test retrieve subject token executable timeout" do + let(:command) { "fake_cmd" } + let(:options) do + { + audience: AUDIENCE, + subject_token_type: SUBJECT_TOKEN_TYPE, + token_url: TOKEN_URL, + credential_source: { + executable: { + command: command, + timeout_millis: 12_000 + } + } + } + end + + before :example do + ENV[Google::Auth::ExternalAccount::PluggableAuthCredentials::ENABLE_PLUGGABLE_ENV] = "1" + end + + it "converts configured timeout_millis to seconds for Timeout.timeout" do + expect(Timeout).to receive(:timeout).with(12.0).and_yield + allow(Open3).to receive(:capture3).and_return( + [JSON.generate(EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_ID_TOKEN), "", double("Process::Status", exitstatus: 0, success?: true)] + ) + + credentials = PluggableAuthCredentials.new options + expect(credentials.retrieve_subject_token!).to eq(EXECUTABLE_OIDC_TOKEN) + end + + it "converts default timeout_millis to seconds for Timeout.timeout when unspecified" do + options[:credential_source][:executable].delete :timeout_millis + expect(Timeout).to receive(:timeout).with(30.0).and_yield + allow(Open3).to receive(:capture3).and_return( + [JSON.generate(EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_ID_TOKEN), "", double("Process::Status", exitstatus: 0, success?: true)] + ) + + credentials = PluggableAuthCredentials.new options + expect(credentials.retrieve_subject_token!).to eq(EXECUTABLE_OIDC_TOKEN) + end + end end