Skip to content

Commit 9b2c3b3

Browse files
authored
Add AzurePipelinesCredential for authenticating an Azure Pipelines service connection with workload identity federation. (#5733)
* 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. * Address PR feedback, suppress warning, move oidc fetch in token cache, and update exception message. * Address PR feedback, use ID and capitalize Azure Pipelines. * Revert back to the workaround for the warning, rather than suppressing it. * Address PR feedback, move getting an assertion to a helper, and add const.
1 parent ed933f5 commit 9b2c3b3

File tree

11 files changed

+898
-1
lines changed

11 files changed

+898
-1
lines changed

.vscode/cspell.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,10 @@
188188
"Oaep",
189189
"odata",
190190
"ofstad",
191+
"oidc",
192+
"Oidc",
191193
"OIDC",
194+
"OIDCREQUESTURI",
192195
"okhttp",
193196
"opentelemetry",
194197
"Osterman",

sdk/identity/azure-identity/CHANGELOG.md

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

55
### Features Added
66

7+
- Added `AzurePipelinesCredential` for authenticating an Azure Pipelines service connection with workload identity federation.
8+
79
### Breaking Changes
810

911
### Bugs Fixed

sdk/identity/azure-identity/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ set(
4848
AZURE_IDENTITY_HEADER
4949
inc/azure/identity.hpp
5050
inc/azure/identity/azure_cli_credential.hpp
51+
inc/azure/identity/azure_pipelines_credential.hpp
5152
inc/azure/identity/chained_token_credential.hpp
5253
inc/azure/identity/client_certificate_credential.hpp
5354
inc/azure/identity/client_secret_credential.hpp
@@ -64,6 +65,7 @@ set(
6465
set(
6566
AZURE_IDENTITY_SOURCE
6667
src/azure_cli_credential.cpp
68+
src/azure_pipelines_credential.cpp
6769
src/chained_token_credential.cpp
6870
src/client_certificate_credential.cpp
6971
src/client_credential_core.cpp

sdk/identity/azure-identity/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ Configuration is attempted in the above order. For example, if values for a clie
134134
### Authenticate service principals
135135
|Credential | Usage
136136
|-|-
137+
|`AzurePipelinesCredential`|Supports [Microsoft Entra Workload ID](https://learn.microsoft.com/azure/devops/pipelines/release/configure-workload-identity?view=azure-devops) on Azure Pipelines.
137138
|`ClientSecretCredential`|Authenticates a service principal [using a secret](https://learn.microsoft.com/entra/identity-platform/app-objects-and-service-principals).
138139
|`ClientCertificateCredential`|Authenticates a service principal [using a certificate](https://learn.microsoft.com/entra/identity-platform/app-objects-and-service-principals).
139140

sdk/identity/azure-identity/inc/azure/identity.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#pragma once
1010

1111
#include "azure/identity/azure_cli_credential.hpp"
12+
#include "azure/identity/azure_pipelines_credential.hpp"
1213
#include "azure/identity/chained_token_credential.hpp"
1314
#include "azure/identity/client_certificate_credential.hpp"
1415
#include "azure/identity/client_secret_credential.hpp"
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
/**
5+
* @file
6+
* @brief Azure Pipelines Credential and options.
7+
*/
8+
9+
#pragma once
10+
11+
#include "azure/identity/detail/client_credential_core.hpp"
12+
#include "azure/identity/detail/token_cache.hpp"
13+
14+
#include <azure/core/credentials/token_credential_options.hpp>
15+
#include <azure/core/http/http.hpp>
16+
#include <azure/core/internal/http/pipeline.hpp>
17+
18+
#include <string>
19+
#include <vector>
20+
21+
namespace Azure { namespace Identity {
22+
namespace _detail {
23+
class TokenCredentialImpl;
24+
} // namespace _detail
25+
26+
/**
27+
* @brief Options for Azure Pipelines credential.
28+
*
29+
*/
30+
struct AzurePipelinesCredentialOptions final : public Core::Credentials::TokenCredentialOptions
31+
{
32+
/**
33+
* @brief Authentication authority URL.
34+
* @note Defaults to the value of the environment variable 'AZURE_AUTHORITY_HOST'. If that's not
35+
* set, the default value is Microsoft Entra global authority
36+
* (https://login.microsoftonline.com/).
37+
*
38+
* @note Example of an authority host string: "https://login.microsoftonline.us/". See national
39+
* clouds' Microsoft Entra authentication endpoints:
40+
* https://learn.microsoft.com/entra/identity-platform/authentication-national-cloud.
41+
*/
42+
std::string AuthorityHost = _detail::DefaultOptionValues::GetAuthorityHost();
43+
44+
/**
45+
* @brief For multi-tenant applications, specifies additional tenants for which the credential
46+
* may acquire tokens. Add the wildcard value `"*"` to allow the credential to acquire tokens
47+
* for any tenant in which the application is installed.
48+
*/
49+
std::vector<std::string> AdditionallyAllowedTenants;
50+
};
51+
52+
/**
53+
* @brief Credential which authenticates using an Azure Pipelines service connection.
54+
*
55+
*/
56+
class AzurePipelinesCredential final : public Core::Credentials::TokenCredential {
57+
private:
58+
std::string m_serviceConnectionId;
59+
std::string m_systemAccessToken;
60+
_detail::ClientCredentialCore m_clientCredentialCore;
61+
Azure::Core::Http::_internal::HttpPipeline m_httpPipeline;
62+
std::string m_oidcRequestUrl;
63+
std::unique_ptr<_detail::TokenCredentialImpl> m_tokenCredentialImpl;
64+
std::string m_requestBody;
65+
_detail::TokenCache m_tokenCache;
66+
67+
std::string GetAssertion(Core::Context const& context) const;
68+
Azure::Core::Http::Request CreateOidcRequestMessage() const;
69+
std::string GetOidcTokenResponse(
70+
std::unique_ptr<Azure::Core::Http::RawResponse> const& response,
71+
std::string responseBody) const;
72+
73+
public:
74+
/**
75+
* @brief Constructs an Azure Pipelines Credential.
76+
*
77+
* @param tenantId The tenant ID for the service connection.
78+
* @param clientId The client ID for the service connection.
79+
* @param serviceConnectionId The service connection ID for the service connection associated
80+
* with the pipeline.
81+
* @param systemAccessToken The pipeline's System.AccessToken value. See
82+
* https://learn.microsoft.com/azure/devops/pipelines/build/variables?view=azure-devops%26tabs=yaml#systemaccesstoken
83+
* for more details.
84+
* @param options Options for token retrieval.
85+
*/
86+
explicit AzurePipelinesCredential(
87+
std::string tenantId,
88+
std::string clientId,
89+
std::string serviceConnectionId,
90+
std::string systemAccessToken,
91+
AzurePipelinesCredentialOptions const& options = {});
92+
93+
/**
94+
* @brief Destructs `%AzurePipelinesCredential`.
95+
*
96+
*/
97+
~AzurePipelinesCredential() override;
98+
99+
/**
100+
* @brief Gets an authentication token.
101+
*
102+
* @param tokenRequestContext A context to get the token in.
103+
* @param context A context to control the request lifetime.
104+
*
105+
* @throw Azure::Core::Credentials::AuthenticationException Authentication error occurred.
106+
*/
107+
Core::Credentials::AccessToken GetToken(
108+
Core::Credentials::TokenRequestContext const& tokenRequestContext,
109+
Core::Context const& context) const override;
110+
};
111+
112+
}} // namespace Azure::Identity

sdk/identity/azure-identity/inc/azure/identity/detail/client_credential_core.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ namespace Azure { namespace Identity { namespace _detail {
1717
constexpr auto AzureTenantIdEnvVarName = "AZURE_TENANT_ID";
1818
constexpr auto AzureClientIdEnvVarName = "AZURE_CLIENT_ID";
1919
constexpr auto AzureFederatedTokenFileEnvVarName = "AZURE_FEDERATED_TOKEN_FILE";
20+
const std::string OidcRequestUrlEnvVarName = "SYSTEM_OIDCREQUESTURI";
2021
const std::string AadGlobalAuthority = "https://login.microsoftonline.com/";
2122

2223
class DefaultOptionValues final {
@@ -46,6 +47,11 @@ namespace Azure { namespace Identity { namespace _detail {
4647
{
4748
return Core::_internal::Environment::GetVariable(AzureFederatedTokenFileEnvVarName);
4849
}
50+
51+
static std::string GetOidcRequestUrl()
52+
{
53+
return Core::_internal::Environment::GetVariable(OidcRequestUrlEnvVarName.c_str());
54+
}
4955
};
5056

5157
class ClientCredentialCore final {

0 commit comments

Comments
 (0)