Skip to content

Add AzurePipelinesCredential for authenticating an Azure Pipelines service connection with workload identity federation. #5733

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jun 21, 2024
Merged
3 changes: 3 additions & 0 deletions .vscode/cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,10 @@
"Oaep",
"odata",
"ofstad",
"oidc",
"Oidc",
"OIDC",
"OIDCREQUESTURI",
"okhttp",
"opentelemetry",
"Osterman",
Expand Down
2 changes: 2 additions & 0 deletions sdk/identity/azure-identity/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

### Features Added

- Added `AzurePipelinesCredential` for authenticating an Azure Pipelines service connection with workload identity federation.

### Breaking Changes

### Bugs Fixed
Expand Down
2 changes: 2 additions & 0 deletions sdk/identity/azure-identity/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ set(
AZURE_IDENTITY_HEADER
inc/azure/identity.hpp
inc/azure/identity/azure_cli_credential.hpp
inc/azure/identity/azure_pipelines_credential.hpp
inc/azure/identity/chained_token_credential.hpp
inc/azure/identity/client_certificate_credential.hpp
inc/azure/identity/client_secret_credential.hpp
Expand All @@ -64,6 +65,7 @@ set(
set(
AZURE_IDENTITY_SOURCE
src/azure_cli_credential.cpp
src/azure_pipelines_credential.cpp
src/chained_token_credential.cpp
src/client_certificate_credential.cpp
src/client_credential_core.cpp
Expand Down
1 change: 1 addition & 0 deletions sdk/identity/azure-identity/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ Configuration is attempted in the above order. For example, if values for a clie
### Authenticate service principals
|Credential | Usage
|-|-
|`AzurePipelinesCredential`|Supports [Microsoft Entra Workload ID](https://learn.microsoft.com/azure/devops/pipelines/release/configure-workload-identity?view=azure-devops) on Azure Pipelines.
|`ClientSecretCredential`|Authenticates a service principal [using a secret](https://learn.microsoft.com/entra/identity-platform/app-objects-and-service-principals).
|`ClientCertificateCredential`|Authenticates a service principal [using a certificate](https://learn.microsoft.com/entra/identity-platform/app-objects-and-service-principals).

Expand Down
1 change: 1 addition & 0 deletions sdk/identity/azure-identity/inc/azure/identity.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#pragma once

#include "azure/identity/azure_cli_credential.hpp"
#include "azure/identity/azure_pipelines_credential.hpp"
#include "azure/identity/chained_token_credential.hpp"
#include "azure/identity/client_certificate_credential.hpp"
#include "azure/identity/client_secret_credential.hpp"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

/**
* @file
* @brief Azure Pipelines Credential and options.
*/

#pragma once

#include "azure/identity/detail/client_credential_core.hpp"
#include "azure/identity/detail/token_cache.hpp"

#include <azure/core/credentials/token_credential_options.hpp>
#include <azure/core/http/http.hpp>
#include <azure/core/internal/http/pipeline.hpp>

#include <string>
#include <vector>

namespace Azure { namespace Identity {
namespace _detail {
class TokenCredentialImpl;
} // namespace _detail

/**
* @brief Options for Azure Pipelines credential.
*
*/
struct AzurePipelinesCredentialOptions final : public Core::Credentials::TokenCredentialOptions
{
/**
* @brief Authentication authority URL.
* @note Defaults to the value of the environment variable 'AZURE_AUTHORITY_HOST'. If that's not
* set, the default value is Microsoft Entra global authority
* (https://login.microsoftonline.com/).
*
* @note Example of an authority host string: "https://login.microsoftonline.us/". See national
* clouds' Microsoft Entra authentication endpoints:
* https://learn.microsoft.com/entra/identity-platform/authentication-national-cloud.
*/
std::string AuthorityHost = _detail::DefaultOptionValues::GetAuthorityHost();

/**
* @brief For multi-tenant applications, specifies additional tenants for which the credential
* may acquire tokens. Add the wildcard value `"*"` to allow the credential to acquire tokens
* for any tenant in which the application is installed.
*/
std::vector<std::string> AdditionallyAllowedTenants;
};

/**
* @brief Credential which authenticates using an Azure Pipelines service connection.
*
*/
class AzurePipelinesCredential final : public Core::Credentials::TokenCredential {
private:
std::string m_serviceConnectionId;
std::string m_systemAccessToken;
_detail::ClientCredentialCore m_clientCredentialCore;
Azure::Core::Http::_internal::HttpPipeline m_httpPipeline;
std::string m_oidcRequestUrl;
std::unique_ptr<_detail::TokenCredentialImpl> m_tokenCredentialImpl;
std::string m_requestBody;
_detail::TokenCache m_tokenCache;

Azure::Core::Http::Request CreateOidcRequestMessage() const;
std::string GetOidcTokenResponse(
std::unique_ptr<Azure::Core::Http::RawResponse> const& response,
std::string responseBody) const;

public:
/**
* @brief Constructs an Azure Pipelines Credential.
*
* @param tenantId The tenant ID for the service connection.
* @param clientId The client ID for the service connection.
* @param serviceConnectionId The service connection ID for the service connection associated
* with the pipeline.
* @param systemAccessToken The pipeline's System.AccessToken value. See
* https://learn.microsoft.com/azure/devops/pipelines/build/variables?view=azure-devops%26tabs=yaml#systemaccesstoken
* for more details.
* @param options Options for token retrieval.
*/
explicit AzurePipelinesCredential(
std::string tenantId,
std::string clientId,
std::string serviceConnectionId,
std::string systemAccessToken,
AzurePipelinesCredentialOptions const& options = {});

/**
* @brief Destructs `%AzurePipelinesCredential`.
*
*/
~AzurePipelinesCredential() override;

/**
* @brief Gets an authentication token.
*
* @param tokenRequestContext A context to get the token in.
* @param context A context to control the request lifetime.
*
* @throw Azure::Core::Credentials::AuthenticationException Authentication error occurred.
*/
Core::Credentials::AccessToken GetToken(
Core::Credentials::TokenRequestContext const& tokenRequestContext,
Core::Context const& context) const override;
};

}} // namespace Azure::Identity
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ namespace Azure { namespace Identity { namespace _detail {
constexpr auto AzureTenantIdEnvVarName = "AZURE_TENANT_ID";
constexpr auto AzureClientIdEnvVarName = "AZURE_CLIENT_ID";
constexpr auto AzureFederatedTokenFileEnvVarName = "AZURE_FEDERATED_TOKEN_FILE";
const std::string OidcRequestUrlEnvVarName = "SYSTEM_OIDCREQUESTURI";
const std::string AadGlobalAuthority = "https://login.microsoftonline.com/";

class DefaultOptionValues final {
Expand Down Expand Up @@ -46,6 +47,11 @@ namespace Azure { namespace Identity { namespace _detail {
{
return Core::_internal::Environment::GetVariable(AzureFederatedTokenFileEnvVarName);
}

static std::string GetOidcRequestUrl()
{
return Core::_internal::Environment::GetVariable(OidcRequestUrlEnvVarName.c_str());
}
};

class ClientCredentialCore final {
Expand Down
Loading