@@ -2456,3 +2456,256 @@ def test_refresh_success_with_supplier(self, utcnow, mock_auth_lib_value):
24562456 assert credentials .quota_project_id == QUOTA_PROJECT_ID
24572457 assert credentials .scopes == SCOPES
24582458 assert credentials .default_scopes == ["ignored" ]
2459+
2460+ @mock .patch ("google.auth._helpers.utcnow" )
2461+ def test_retrieve_subject_token_success_ecs_relative_uri (
2462+ self , utcnow , monkeypatch
2463+ ):
2464+ monkeypatch .setenv (environment_vars .AWS_REGION , self .AWS_REGION )
2465+ monkeypatch .setenv (
2466+ environment_vars .AWS_CONTAINER_CREDENTIALS_RELATIVE_URI ,
2467+ "/v2/credentials/role-id" ,
2468+ )
2469+ utcnow .return_value = datetime .datetime .strptime (
2470+ self .AWS_SIGNATURE_TIME , "%Y-%m-%dT%H:%M:%SZ"
2471+ )
2472+ # Mock the ECS credentials endpoint response.
2473+ ecs_response = mock .create_autospec (transport .Response , instance = True )
2474+ ecs_response .status = http_client .OK
2475+ ecs_response .data = json .dumps (
2476+ self .AWS_SECURITY_CREDENTIALS_RESPONSE
2477+ ).encode ("utf-8" )
2478+ request = mock .create_autospec (transport .Request )
2479+ request .side_effect = [ecs_response ]
2480+
2481+ credentials = self .make_credentials (credential_source = self .CREDENTIAL_SOURCE )
2482+ subject_token = credentials .retrieve_subject_token (request )
2483+
2484+ assert subject_token == self .make_serialized_aws_signed_request (
2485+ aws .AwsSecurityCredentials (ACCESS_KEY_ID , SECRET_ACCESS_KEY , TOKEN )
2486+ )
2487+ # Assert ECS credentials request.
2488+ assert request .call_args_list [0 ][1 ]["url" ] == (
2489+ "http://169.254.170.2/v2/credentials/role-id"
2490+ )
2491+ assert request .call_args_list [0 ][1 ]["method" ] == "GET"
2492+
2493+ @mock .patch ("google.auth._helpers.utcnow" )
2494+ def test_retrieve_subject_token_success_ecs_full_uri_loopback (
2495+ self , utcnow , monkeypatch
2496+ ):
2497+ monkeypatch .setenv (environment_vars .AWS_REGION , self .AWS_REGION )
2498+ monkeypatch .setenv (
2499+ environment_vars .AWS_CONTAINER_CREDENTIALS_FULL_URI ,
2500+ "http://127.0.0.1/v2/credentials/role-id" ,
2501+ )
2502+ utcnow .return_value = datetime .datetime .strptime (
2503+ self .AWS_SIGNATURE_TIME , "%Y-%m-%dT%H:%M:%SZ"
2504+ )
2505+ ecs_response = mock .create_autospec (transport .Response , instance = True )
2506+ ecs_response .status = http_client .OK
2507+ ecs_response .data = json .dumps (
2508+ self .AWS_SECURITY_CREDENTIALS_RESPONSE
2509+ ).encode ("utf-8" )
2510+ request = mock .create_autospec (transport .Request )
2511+ request .side_effect = [ecs_response ]
2512+
2513+ credentials = self .make_credentials (credential_source = self .CREDENTIAL_SOURCE )
2514+ subject_token = credentials .retrieve_subject_token (request )
2515+
2516+ assert subject_token == self .make_serialized_aws_signed_request (
2517+ aws .AwsSecurityCredentials (ACCESS_KEY_ID , SECRET_ACCESS_KEY , TOKEN )
2518+ )
2519+ assert request .call_args_list [0 ][1 ]["url" ] == (
2520+ "http://127.0.0.1/v2/credentials/role-id"
2521+ )
2522+
2523+ @mock .patch ("google.auth._helpers.utcnow" )
2524+ def test_retrieve_subject_token_success_ecs_full_uri_loopback_ipv6 (
2525+ self , utcnow , monkeypatch
2526+ ):
2527+ monkeypatch .setenv (environment_vars .AWS_REGION , self .AWS_REGION )
2528+ monkeypatch .setenv (
2529+ environment_vars .AWS_CONTAINER_CREDENTIALS_FULL_URI ,
2530+ "http://[::1]/v2/credentials/role-id" ,
2531+ )
2532+ utcnow .return_value = datetime .datetime .strptime (
2533+ self .AWS_SIGNATURE_TIME , "%Y-%m-%dT%H:%M:%SZ"
2534+ )
2535+ ecs_response = mock .create_autospec (transport .Response , instance = True )
2536+ ecs_response .status = http_client .OK
2537+ ecs_response .data = json .dumps (
2538+ self .AWS_SECURITY_CREDENTIALS_RESPONSE
2539+ ).encode ("utf-8" )
2540+ request = mock .create_autospec (transport .Request )
2541+ request .side_effect = [ecs_response ]
2542+
2543+ credentials = self .make_credentials (credential_source = self .CREDENTIAL_SOURCE )
2544+ subject_token = credentials .retrieve_subject_token (request )
2545+
2546+ assert subject_token == self .make_serialized_aws_signed_request (
2547+ aws .AwsSecurityCredentials (ACCESS_KEY_ID , SECRET_ACCESS_KEY , TOKEN )
2548+ )
2549+
2550+ @mock .patch ("google.auth._helpers.utcnow" )
2551+ def test_retrieve_subject_token_success_ecs_full_uri_with_auth_token (
2552+ self , utcnow , monkeypatch
2553+ ):
2554+ monkeypatch .setenv (environment_vars .AWS_REGION , self .AWS_REGION )
2555+ monkeypatch .setenv (
2556+ environment_vars .AWS_CONTAINER_CREDENTIALS_FULL_URI ,
2557+ "http://192.168.1.1/v2/credentials/role-id" ,
2558+ )
2559+ monkeypatch .setenv (
2560+ environment_vars .AWS_CONTAINER_AUTHORIZATION_TOKEN ,
2561+ "my-auth-token" ,
2562+ )
2563+ utcnow .return_value = datetime .datetime .strptime (
2564+ self .AWS_SIGNATURE_TIME , "%Y-%m-%dT%H:%M:%SZ"
2565+ )
2566+ ecs_response = mock .create_autospec (transport .Response , instance = True )
2567+ ecs_response .status = http_client .OK
2568+ ecs_response .data = json .dumps (
2569+ self .AWS_SECURITY_CREDENTIALS_RESPONSE
2570+ ).encode ("utf-8" )
2571+ request = mock .create_autospec (transport .Request )
2572+ request .side_effect = [ecs_response ]
2573+
2574+ credentials = self .make_credentials (credential_source = self .CREDENTIAL_SOURCE )
2575+ subject_token = credentials .retrieve_subject_token (request )
2576+
2577+ assert subject_token == self .make_serialized_aws_signed_request (
2578+ aws .AwsSecurityCredentials (ACCESS_KEY_ID , SECRET_ACCESS_KEY , TOKEN )
2579+ )
2580+ # Assert Authorization header was sent.
2581+ assert request .call_args_list [0 ][1 ]["headers" ]["Authorization" ] == "my-auth-token"
2582+
2583+ @mock .patch ("google.auth._helpers.utcnow" )
2584+ def test_retrieve_subject_token_ecs_full_uri_non_loopback_without_token_raises (
2585+ self , utcnow , monkeypatch
2586+ ):
2587+ monkeypatch .setenv (environment_vars .AWS_REGION , self .AWS_REGION )
2588+ monkeypatch .setenv (
2589+ environment_vars .AWS_CONTAINER_CREDENTIALS_FULL_URI ,
2590+ "http://192.168.1.1/v2/credentials/role-id" ,
2591+ )
2592+ utcnow .return_value = datetime .datetime .strptime (
2593+ self .AWS_SIGNATURE_TIME , "%Y-%m-%dT%H:%M:%SZ"
2594+ )
2595+ credentials = self .make_credentials (credential_source = self .CREDENTIAL_SOURCE )
2596+
2597+ with pytest .raises (exceptions .RefreshError ) as exc_info :
2598+ credentials .retrieve_subject_token (None )
2599+
2600+ assert "AWS_CONTAINER_CREDENTIALS_FULL_URI" in str (exc_info .value )
2601+ assert "AWS_CONTAINER_AUTHORIZATION_TOKEN" in str (exc_info .value )
2602+
2603+ @mock .patch ("google.auth._helpers.utcnow" )
2604+ def test_retrieve_subject_token_ecs_full_uri_takes_precedence_over_relative (
2605+ self , utcnow , monkeypatch
2606+ ):
2607+ monkeypatch .setenv (environment_vars .AWS_REGION , self .AWS_REGION )
2608+ monkeypatch .setenv (
2609+ environment_vars .AWS_CONTAINER_CREDENTIALS_FULL_URI ,
2610+ "http://127.0.0.1/full-uri-path" ,
2611+ )
2612+ monkeypatch .setenv (
2613+ environment_vars .AWS_CONTAINER_CREDENTIALS_RELATIVE_URI ,
2614+ "/relative-uri-path" ,
2615+ )
2616+ utcnow .return_value = datetime .datetime .strptime (
2617+ self .AWS_SIGNATURE_TIME , "%Y-%m-%dT%H:%M:%SZ"
2618+ )
2619+ ecs_response = mock .create_autospec (transport .Response , instance = True )
2620+ ecs_response .status = http_client .OK
2621+ ecs_response .data = json .dumps (
2622+ self .AWS_SECURITY_CREDENTIALS_RESPONSE
2623+ ).encode ("utf-8" )
2624+ request = mock .create_autospec (transport .Request )
2625+ request .side_effect = [ecs_response ]
2626+
2627+ credentials = self .make_credentials (credential_source = self .CREDENTIAL_SOURCE )
2628+ credentials .retrieve_subject_token (request )
2629+
2630+ # Full URI should be used, not relative URI.
2631+ assert request .call_args_list [0 ][1 ]["url" ] == "http://127.0.0.1/full-uri-path"
2632+
2633+ @mock .patch ("google.auth._helpers.utcnow" )
2634+ def test_retrieve_subject_token_ecs_takes_precedence_over_imds (
2635+ self , utcnow , monkeypatch
2636+ ):
2637+ monkeypatch .setenv (environment_vars .AWS_REGION , self .AWS_REGION )
2638+ monkeypatch .setenv (
2639+ environment_vars .AWS_CONTAINER_CREDENTIALS_RELATIVE_URI ,
2640+ "/v2/credentials/role-id" ,
2641+ )
2642+ utcnow .return_value = datetime .datetime .strptime (
2643+ self .AWS_SIGNATURE_TIME , "%Y-%m-%dT%H:%M:%SZ"
2644+ )
2645+ ecs_response = mock .create_autospec (transport .Response , instance = True )
2646+ ecs_response .status = http_client .OK
2647+ ecs_response .data = json .dumps (
2648+ self .AWS_SECURITY_CREDENTIALS_RESPONSE
2649+ ).encode ("utf-8" )
2650+ request = mock .create_autospec (transport .Request )
2651+ request .side_effect = [ecs_response ]
2652+
2653+ credentials = self .make_credentials (credential_source = self .CREDENTIAL_SOURCE )
2654+ subject_token = credentials .retrieve_subject_token (request )
2655+
2656+ assert subject_token == self .make_serialized_aws_signed_request (
2657+ aws .AwsSecurityCredentials (ACCESS_KEY_ID , SECRET_ACCESS_KEY , TOKEN )
2658+ )
2659+ # Only 1 request (ECS), no IMDS requests.
2660+ assert len (request .call_args_list ) == 1
2661+
2662+ @mock .patch ("google.auth._helpers.utcnow" )
2663+ def test_retrieve_subject_token_env_vars_take_precedence_over_ecs (
2664+ self , utcnow , monkeypatch
2665+ ):
2666+ monkeypatch .setenv (environment_vars .AWS_ACCESS_KEY_ID , ACCESS_KEY_ID )
2667+ monkeypatch .setenv (environment_vars .AWS_SECRET_ACCESS_KEY , SECRET_ACCESS_KEY )
2668+ monkeypatch .setenv (environment_vars .AWS_SESSION_TOKEN , TOKEN )
2669+ monkeypatch .setenv (environment_vars .AWS_REGION , self .AWS_REGION )
2670+ monkeypatch .setenv (
2671+ environment_vars .AWS_CONTAINER_CREDENTIALS_RELATIVE_URI ,
2672+ "/v2/credentials/role-id" ,
2673+ )
2674+ utcnow .return_value = datetime .datetime .strptime (
2675+ self .AWS_SIGNATURE_TIME , "%Y-%m-%dT%H:%M:%SZ"
2676+ )
2677+ credentials = self .make_credentials (credential_source = self .CREDENTIAL_SOURCE )
2678+
2679+ # No request mock needed - env vars should be used directly.
2680+ subject_token = credentials .retrieve_subject_token (None )
2681+
2682+ assert subject_token == self .make_serialized_aws_signed_request (
2683+ aws .AwsSecurityCredentials (ACCESS_KEY_ID , SECRET_ACCESS_KEY , TOKEN )
2684+ )
2685+
2686+ @mock .patch ("google.auth._helpers.utcnow" )
2687+ def test_retrieve_subject_token_ecs_endpoint_error (
2688+ self , utcnow , monkeypatch
2689+ ):
2690+ monkeypatch .setenv (environment_vars .AWS_REGION , self .AWS_REGION )
2691+ monkeypatch .setenv (
2692+ environment_vars .AWS_CONTAINER_CREDENTIALS_RELATIVE_URI ,
2693+ "/v2/credentials/role-id" ,
2694+ )
2695+ utcnow .return_value = datetime .datetime .strptime (
2696+ self .AWS_SIGNATURE_TIME , "%Y-%m-%dT%H:%M:%SZ"
2697+ )
2698+ ecs_response = mock .create_autospec (transport .Response , instance = True )
2699+ ecs_response .status = http_client .UNAUTHORIZED
2700+ ecs_response .data = b"Unauthorized"
2701+ request = mock .create_autospec (transport .Request )
2702+ request .side_effect = [ecs_response ]
2703+
2704+ credentials = self .make_credentials (credential_source = self .CREDENTIAL_SOURCE )
2705+
2706+ with pytest .raises (exceptions .RefreshError ) as exc_info :
2707+ credentials .retrieve_subject_token (request )
2708+
2709+ assert "Unable to retrieve AWS security credentials from ECS" in str (
2710+ exc_info .value
2711+ )
0 commit comments