Skip to content

Commit a62df9c

Browse files
authored
[Identity] Update AzurePipelinesCredential request headers (#37510)
Now we include the `X-TFS-FedAuthRedirect' header in OIDC requests to avoid redirect responses. Signed-off-by: Paul Van Eck <[email protected]>
1 parent e25c35d commit a62df9c

File tree

5 files changed

+64
-2
lines changed

5 files changed

+64
-2
lines changed

sdk/identity/azure-identity/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
### Bugs Fixed
1010

11+
- Fixed the request sent in `AzurePipelinesCredential` so it doesn't result in a redirect response when an invalid system access token is provided. ([#37510](https://github.com/Azure/azure-sdk-for-python/pull/37510))
12+
1113
### Other Changes
1214

1315
## 1.18.0 (2024-09-19)

sdk/identity/azure-identity/TROUBLESHOOTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ Get-AzAccessToken -ResourceUrl "https://management.core.windows.net"
295295
| --- | --- | --- |
296296
| AADSTS900023: Specified tenant identifier `<some tenant ID>` is neither a valid DNS name, nor a valid external domain. | The Microsoft Entra tenant ID passed to the credential is invalid. | Verify the tenant ID is valid. If the service connection federated identity credential (FIC) was configured via a user-assigned managed identity, the tenant is the one in which managed identity was registered. If the service connection FIC is configured via an app registration, the tenant should be the one in which the app registration is registered. |
297297
| No service connection found with identifier `<GUID>`. | The service connection ID provided is incorrect. | Verify the `service_connection_id` provided. For details on finding this value, check the [Find a service connection ID](#find-a-service-connection-id) section. |
298-
| ClientAuthenticationError: OIDC token not found in response. Response = Object moved to here. Status Code: 302. | The system access token seems to be malformed when passing in as a parameter to the credential. | `System.AccessToken` is a required system variable in the Azure Pipelines task and should be provided in the pipeline task, [as mentioned in the docs](https://learn.microsoft.com/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml#systemaccesstoken). Verify that the system access token value provided is the predefined variable in Azure Pipelines and isn't malformed. |
298+
| ClientAuthenticationError: 401 (Unauthorized) response from OIDC endpoint. | The system access token seems to be malformed/invalid when passing in as a parameter to the credential. | `System.AccessToken` is a required system variable in the Azure Pipelines task and should be provided in the pipeline task, [as mentioned in the docs](https://learn.microsoft.com/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml#systemaccesstoken). Verify that the system access token value provided is the predefined variable in Azure Pipelines and isn't malformed. |
299299
| ClientAuthenticationError: OIDC token not found in response. Response = {"$id":"1", "innerException":null, "message":"`<ACTUAL ERROR MESSAGE>`", "typeName":"Microsoft.VisualStudio.Services.WebApi.VssInvalidPreviewVersionException, Microsoft.VisualStudio.Services.WebApi", "typeKey":"VssInvalidPreviewVersionException", "errorCode":0} | When the OIDC token request fails, the OIDC token API throws an error. More details about the specific error are specified in the "message" field of the response. | Mitigation usually depends on the scenario based on what [error message](https://learn.microsoft.com/azure/devops/pipelines/release/troubleshoot-workload-identity?view=azure-devops#error-messages) is being thrown. Make sure you use the [recommended Azure Pipelines task](https://learn.microsoft.com/azure/devops/pipelines/release/troubleshoot-workload-identity?view=azure-devops#review-pipeline-tasks). |
300300
| CredentialUnavailableError: Missing value for the `SYSTEM_OIDCREQUESTURI` environment variable. | This code isn't running inside of an Azure Pipelines environment. You might be running this code locally or on some other environment. | This credential is only designed to run inside the Azure Pipelines environment for the federated identity to work. |
301301
| AuthenticationRequiredError: unauthorized_client: 700016 - AADSTS700016: Application with identifier `<client_id>` was not found in the directory 'Microsoft'. This error can happen if the application has not been installed by the administrator of the tenant or consented to by any user in the tenant. You may have sent your authentication request to the wrong tenant.| The `client_id` provided is invalid. | Verify the client ID argument is valid. If the service connection's federated identity was registered via a user-assigned managed identity, the client ID of the managed identity should be provided. If the service connection's federated identity is registered via an app registration, the application (client) ID from your app registration should be provided. |

sdk/identity/azure-identity/azure/identity/_credentials/azure_pipelines.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,12 @@
2424
def build_oidc_request(service_connection_id: str, access_token: str) -> HttpRequest:
2525
base_uri = os.environ[SYSTEM_OIDCREQUESTURI].rstrip("/")
2626
url = f"{base_uri}?api-version={OIDC_API_VERSION}&serviceConnectionId={service_connection_id}"
27-
headers = {"Content-Type": "application/json", "Authorization": f"Bearer {access_token}"}
27+
headers = {
28+
"Content-Type": "application/json",
29+
"Authorization": f"Bearer {access_token}",
30+
# Prevents the service from responding with a redirect HTTP status code (useful for automation).
31+
"X-TFS-FedAuthRedirect": "Suppress",
32+
}
2833
return HttpRequest("POST", url, headers=headers)
2934

3035

sdk/identity/azure-identity/tests/test_azure_pipelines_credential.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import pytest
1010
from azure.core.rest import HttpRequest
11+
from azure.core.exceptions import ClientAuthenticationError
1112
from azure.identity import (
1213
AzurePipelinesCredential,
1314
ChainedTokenCredential,
@@ -134,3 +135,29 @@ def test_azure_pipelines_credential_authentication(get_token_method):
134135
token = getattr(credential, get_token_method)(scope)
135136
assert token.token
136137
assert isinstance(token.expires_on, int)
138+
139+
140+
@pytest.mark.live_test_only("Requires Azure Pipelines environment with configured service connection")
141+
@pytest.mark.parametrize("get_token_method", GET_TOKEN_METHODS)
142+
def test_azure_pipelines_credential_authentication_invalid_token(get_token_method):
143+
system_access_token = "invalid"
144+
service_connection_id = os.environ.get("AZURE_SERVICE_CONNECTION_ID", "")
145+
tenant_id = os.environ.get("AZURE_SERVICE_CONNECTION_TENANT_ID", "")
146+
client_id = os.environ.get("AZURE_SERVICE_CONNECTION_CLIENT_ID", "")
147+
148+
scope = "https://vault.azure.net/.default"
149+
150+
if not all([service_connection_id, tenant_id, client_id]):
151+
pytest.skip("This test requires environment variables to be set")
152+
153+
credential = AzurePipelinesCredential(
154+
system_access_token=system_access_token,
155+
tenant_id=tenant_id,
156+
client_id=client_id,
157+
service_connection_id=service_connection_id,
158+
)
159+
160+
with pytest.raises(ClientAuthenticationError) as ex:
161+
getattr(credential, get_token_method)(scope)
162+
163+
assert ex.value.status_code == 401

sdk/identity/azure-identity/tests/test_azure_pipelines_credential_async.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from unittest.mock import AsyncMock, patch
88

99
import pytest
10+
from azure.core.exceptions import ClientAuthenticationError
1011
from azure.identity import CredentialUnavailableError
1112
from azure.identity._credentials.azure_pipelines import SYSTEM_OIDCREQUESTURI
1213
from azure.identity.aio import AzurePipelinesCredential, ChainedTokenCredential, ClientAssertionCredential
@@ -117,3 +118,30 @@ async def test_azure_pipelines_credential_authentication(get_token_method):
117118
token = await getattr(credential, get_token_method)(scope)
118119
assert token.token
119120
assert isinstance(token.expires_on, int)
121+
122+
123+
@pytest.mark.asyncio
124+
@pytest.mark.live_test_only("Requires Azure Pipelines environment with configured service connection")
125+
@pytest.mark.parametrize("get_token_method", GET_TOKEN_METHODS)
126+
async def test_azure_pipelines_credential_authentication_invalid_token(get_token_method):
127+
system_access_token = "invalid"
128+
service_connection_id = os.environ.get("AZURE_SERVICE_CONNECTION_ID", "")
129+
tenant_id = os.environ.get("AZURE_SERVICE_CONNECTION_TENANT_ID", "")
130+
client_id = os.environ.get("AZURE_SERVICE_CONNECTION_CLIENT_ID", "")
131+
132+
scope = "https://vault.azure.net/.default"
133+
134+
if not all([service_connection_id, tenant_id, client_id]):
135+
pytest.skip("This test requires environment variables to be set")
136+
137+
credential = AzurePipelinesCredential(
138+
system_access_token=system_access_token,
139+
tenant_id=tenant_id,
140+
client_id=client_id,
141+
service_connection_id=service_connection_id,
142+
)
143+
144+
with pytest.raises(ClientAuthenticationError) as ex:
145+
await getattr(credential, get_token_method)(scope)
146+
147+
assert ex.value.status_code == 401

0 commit comments

Comments
 (0)