From 3e5b7064ec5a3222a12953658a2266018fb1189c Mon Sep 17 00:00:00 2001 From: Ahson Khan Date: Wed, 26 Jun 2024 11:44:01 -0700 Subject: [PATCH] Add live tests for AzurePipelinesCredential. (#5734) * Add AzurePipelinesCredential for authenticating an Azure Pipelines service connection with workload identity federation. * Add unit tests. * Add comment about not throwing in the ctor, but rather deferring it. * Order field in order of initialization and fix cspell. * Fix ambiguous call to EnvironmentOverride in tests. * Add a live test to AzurePipelinesCredential. * Add invalid test cases and output response. * Add access token env var in ci.yml. * Add identity yml files and EnvVars. * Fix merge conflicts and print out the oidc response. * Remove duplicate definition of ServiceDirectory and remove env. * Revert CI/infra changes. * Include engsys changes to add federated auth support. * Update environment variables used. * Sync recent engsys changes. * Add invalid tenant id test and re-order them. * Fail the live test pipeline if a test fails. * Update tests and revert source changes. * Debug failing TokenCredentialTest in new live test environment. * Dont fail test on missing env var. * Disable federated auth in ci.yml and add back client secret env var. * Remove test application secret. * Revert other changes related to infra. --- .../ut/azure_pipelines_credential_test.cpp | 184 ++++++++++++++++++ 1 file changed, 184 insertions(+) diff --git a/sdk/identity/azure-identity/test/ut/azure_pipelines_credential_test.cpp b/sdk/identity/azure-identity/test/ut/azure_pipelines_credential_test.cpp index fac85e039..02f110e33 100644 --- a/sdk/identity/azure-identity/test/ut/azure_pipelines_credential_test.cpp +++ b/sdk/identity/azure-identity/test/ut/azure_pipelines_credential_test.cpp @@ -9,6 +9,8 @@ #include +using Azure::Core::_internal::Environment; +using Azure::Core::Credentials::AccessToken; using Azure::Core::Credentials::AuthenticationException; using Azure::Core::Credentials::TokenRequestContext; using Azure::Core::Http::HttpMethod; @@ -492,3 +494,185 @@ TEST(AzurePipelinesCredential, InvalidOidcResponse) std::vector{"{\"oidcToken\":5}", ""}), AuthenticationException); } + +constexpr auto TenantIdEnvVar = "AZURESUBSCRIPTION_TENANT_ID"; +constexpr auto ClientIdEnvVar = "AZURESUBSCRIPTION_CLIENT_ID"; +constexpr auto ServiceConnectionIdEnvVar = "AZURESUBSCRIPTION_SERVICE_CONNECTION_ID"; +constexpr auto SystemAccessTokenEnv = "SYSTEM_ACCESSTOKEN"; + +static std::string GetSkipTestMessage( + std::string tenantId, + std::string clientId, + std::string serviceConnectionId, + std::string systemAccessToken) +{ + std::string message = "Set " + std::string(TenantIdEnvVar) + ", " + ClientIdEnvVar + ", " + + ServiceConnectionIdEnvVar + ", and " + SystemAccessTokenEnv + + " to run this AzurePipelinesCredential test. Tenant ID - '" + tenantId + "', Client ID - '" + + clientId + "', Service Connection ID - '" + serviceConnectionId + + "', and System Access Token size : " + std::to_string(systemAccessToken.size()) + "."; + return message; +} + +TEST(AzurePipelinesCredential, RegularLive_LIVEONLY_) +{ + std::string tenantId = Environment::GetVariable("AZURESUBSCRIPTION_TENANT_ID"); + std::string clientId = Environment::GetVariable("AZURESUBSCRIPTION_CLIENT_ID"); + std::string serviceConnectionId + = Environment::GetVariable("AZURESUBSCRIPTION_SERVICE_CONNECTION_ID"); + std::string systemAccessToken = Environment::GetVariable("SYSTEM_ACCESSTOKEN"); + + if (tenantId.empty() || clientId.empty() || serviceConnectionId.empty() + || systemAccessToken.empty()) + { + std::string message + = GetSkipTestMessage(tenantId, clientId, serviceConnectionId, systemAccessToken); + GTEST_SKIP_(message.c_str()); + } + + AzurePipelinesCredential const cred(tenantId, clientId, serviceConnectionId, systemAccessToken); + + TokenRequestContext trc; + trc.Scopes.push_back("https://vault.azure.net/.default"); + + AccessToken token = cred.GetToken(trc, {}); + EXPECT_NE(token.Token, "") << "GetToken returned an invalid token."; + + EXPECT_TRUE(token.ExpiresOn >= std::chrono::system_clock::now()) + << "GetToken returned an invalid expiration time."; + + AccessToken token2 = cred.GetToken(trc, {}); + EXPECT_TRUE(token.Token == token2.Token && token.ExpiresOn == token2.ExpiresOn) + << "Expected a cached token."; +} + +TEST(AzurePipelinesCredential, InvalidTenantId_LIVEONLY_) +{ + std::string clientId = Environment::GetVariable("AZURESUBSCRIPTION_CLIENT_ID"); + std::string serviceConnectionId + = Environment::GetVariable("AZURESUBSCRIPTION_SERVICE_CONNECTION_ID"); + std::string systemAccessToken = Environment::GetVariable("SYSTEM_ACCESSTOKEN"); + + const std::string tenantId = "invalidtenantId"; + + if (clientId.empty() || serviceConnectionId.empty() || systemAccessToken.empty()) + { + std::string message + = GetSkipTestMessage(tenantId, clientId, serviceConnectionId, systemAccessToken); + GTEST_SKIP_(message.c_str()); + } + + AzurePipelinesCredential const cred(tenantId, clientId, serviceConnectionId, systemAccessToken); + + TokenRequestContext trc; + trc.Scopes.push_back("https://vault.azure.net/.default"); + + try + { + AccessToken token = cred.GetToken(trc, {}); + GTEST_FAIL() << "GetToken should have thrown an exception due to an invalid tenant ID."; + } + catch (AuthenticationException const& ex) + { + EXPECT_TRUE(std::string(ex.what()).find("400 Bad Request") != std::string::npos) << ex.what(); + EXPECT_TRUE(std::string(ex.what()).find("AADSTS900023") != std::string::npos) << ex.what(); + } +} + +TEST(AzurePipelinesCredential, InvalidClientId_LIVEONLY_) +{ + std::string tenantId = Environment::GetVariable("AZURESUBSCRIPTION_TENANT_ID"); + std::string serviceConnectionId + = Environment::GetVariable("AZURESUBSCRIPTION_SERVICE_CONNECTION_ID"); + std::string systemAccessToken = Environment::GetVariable("SYSTEM_ACCESSTOKEN"); + + const std::string clientId = "invalidClientId"; + + if (tenantId.empty() || serviceConnectionId.empty() || systemAccessToken.empty()) + { + std::string message + = GetSkipTestMessage(tenantId, clientId, serviceConnectionId, systemAccessToken); + GTEST_SKIP_(message.c_str()); + } + + AzurePipelinesCredential const cred(tenantId, clientId, serviceConnectionId, systemAccessToken); + + TokenRequestContext trc; + trc.Scopes.push_back("https://vault.azure.net/.default"); + + try + { + AccessToken token = cred.GetToken(trc, {}); + GTEST_FAIL() << "GetToken should have thrown an exception due to an invalid client ID."; + } + catch (AuthenticationException const& ex) + { + EXPECT_TRUE(std::string(ex.what()).find("400 Bad Request") != std::string::npos) << ex.what(); + EXPECT_TRUE(std::string(ex.what()).find("AADSTS700016") != std::string::npos) << ex.what(); + } +} + +TEST(AzurePipelinesCredential, InvalidServiceConnectionId_LIVEONLY_) +{ + std::string tenantId = Environment::GetVariable("AZURESUBSCRIPTION_TENANT_ID"); + std::string clientId = Environment::GetVariable("AZURESUBSCRIPTION_CLIENT_ID"); + std::string systemAccessToken = Environment::GetVariable("SYSTEM_ACCESSTOKEN"); + + const std::string serviceConnectionId = "invalidServiceConnectionId"; + + if (tenantId.empty() || clientId.empty() || systemAccessToken.empty()) + { + std::string message + = GetSkipTestMessage(tenantId, clientId, serviceConnectionId, systemAccessToken); + GTEST_SKIP_(message.c_str()); + } + + AzurePipelinesCredential const cred(tenantId, clientId, serviceConnectionId, systemAccessToken); + + TokenRequestContext trc; + trc.Scopes.push_back("https://vault.azure.net/.default"); + + try + { + AccessToken token = cred.GetToken(trc, {}); + GTEST_FAIL() + << "GetToken should have thrown an exception due to an invalid service connection ID."; + } + catch (AuthenticationException const& ex) + { + EXPECT_TRUE(std::string(ex.what()).find("404 (Not Found)") != std::string::npos) << ex.what(); + } +} + +TEST(AzurePipelinesCredential, InvalidSystemAccessToken_LIVEONLY_) +{ + std::string tenantId = Environment::GetVariable("AZURESUBSCRIPTION_TENANT_ID"); + std::string clientId = Environment::GetVariable("AZURESUBSCRIPTION_CLIENT_ID"); + std::string serviceConnectionId + = Environment::GetVariable("AZURESUBSCRIPTION_SERVICE_CONNECTION_ID"); + + const std::string systemAccessToken = "invalidSystemAccessToken"; + + if (tenantId.empty() || clientId.empty() || serviceConnectionId.empty()) + { + std::string message + = GetSkipTestMessage(tenantId, clientId, serviceConnectionId, systemAccessToken); + GTEST_SKIP_(message.c_str()); + } + + AzurePipelinesCredential const cred(tenantId, clientId, serviceConnectionId, systemAccessToken); + + TokenRequestContext trc; + trc.Scopes.push_back("https://vault.azure.net/.default"); + + try + { + AccessToken token = cred.GetToken(trc, {}); + GTEST_FAIL() + << "GetToken should have thrown an exception due to an invalid system access token."; + } + catch (AuthenticationException const& ex) + { + EXPECT_TRUE(std::string(ex.what()).find("302 (Found)") != std::string::npos) << ex.what(); + } +}