Skip to content

Commit

Permalink
[Identity] Testing service connections on pipeline (#29451)
Browse files Browse the repository at this point in the history
Co-authored-by: Maor Leger <maorleger@users.noreply.github.com>
  • Loading branch information
KarishmaGhiya and maorleger committed Apr 30, 2024
1 parent 8977450 commit a1e61aa
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 23 deletions.
22 changes: 12 additions & 10 deletions sdk/identity/identity/samples/AzureIdentityExamples.md
Expand Up @@ -119,11 +119,12 @@ To learn more, read [Application and service principal objects in Microsoft Entr

If your application is hosted in Azure, you can make use of [Managed Identity](https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview) for hassle free authentication in your production environments.

| Credential with example | Usage |
| ----------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [WorkloadIdentityCredential](#authenticating-in-azure-with-workload-identity) | Authenticate in Azure Kubernetes environment with [Microsoft Entra Workload ID](https://learn.microsoft.com/entra/workload-id/workload-identities-overview), which [integrates with the Kubernetes native capabilities](https://learn.microsoft.com/azure/aks/workload-identity-overview) to federate with any external identity providers. |
| [ManagedIdentityCredential](#authenticating-in-azure-with-managed-identity) | Authenticate in a virtual machine, App Service, Functions app, Cloud Shell, or AKS environment on Azure, with system-assigned managed identity, user-assigned managed identity, or app registration (when working with AKS pod identity). |
| [DefaultAzureCredential](#authenticating-with-defaultazurecredential) | Tries `EnvironmentCredential`, `ManagedIdentityCredential`, `AzureCliCredential`, `AzurePowerShellCredential`, and other credentials sequentially until one of them succeeds. Use this to have your application authenticate using developer tools, service principals or managed identity based on what is available in the current environment without changing your code. |
| Credential with example | Usage |
| --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| [WorkloadIdentityCredential](#authenticating-in-azure-with-workload-identity) | Authenticate in Azure Kubernetes environment with [Microsoft Entra Workload ID](https://learn.microsoft.com/entra/workload-id/workload-identities-overview), which [integrates with the Kubernetes native capabilities](https://learn.microsoft.com/azure/aks/workload-identity-overview) to federate with any external identity providers. |
| [AzurePipelinesServiceConnectionsCredential](#authenticating-in-azure-pipelines-with-service-connections) | Authenticate in Azure Devops environment with [Microsoft Entra Workload ID](https://learn.microsoft.com/entra/workload-id/workload-identities-overview), which [uses Azure Resource Manager service connections](https://learn.microsoft.com/azure/devops/pipelines/library/connect-to-azure?view=azure-devops#create-an-azure-resource-manager-service-connection-that-uses-workload-identity-federation) to federate with any external identity providers. |
| [ManagedIdentityCredential](#authenticating-in-azure-with-managed-identity) | Authenticate in a virtual machine, App Service, Functions app, Cloud Shell, or AKS environment on Azure, with system-assigned managed identity, user-assigned managed identity, or app registration (when working with AKS pod identity). |
| [DefaultAzureCredential](#authenticating-with-defaultazurecredential) | Tries `EnvironmentCredential`, `ManagedIdentityCredential`, `AzureCliCredential`, `AzurePowerShellCredential`, and other credentials sequentially until one of them succeeds. Use this to have your application authenticate using developer tools, service principals or managed identity based on what is available in the current environment without changing your code. |

### Examples

Expand Down Expand Up @@ -617,19 +618,20 @@ function withUserManagedIdentityCredential() {

This example demonstrates authenticating the `SecretClient` from the [@azure/keyvault-secrets][secrets_client_library] using the `AzurePipelinesServiceConnectionCredential` in an Azure Pipelines environment with service connections.

````ts
```ts
/**
* Authenticate with AzurePipelinesServiceConnection identity.
*/
function withAzurePipelinesServiceConnectionCredential() {
const clientId = "<YOUR_CLIENT_ID>"
const tenantId = "<YOUR_TENANT_ID>"
const serviceConnectionId = "<YOUR_SERVICE_CONNECTION_ID>"
const clientId = "<YOUR_CLIENT_ID>";
const tenantId = "<YOUR_TENANT_ID>";
const serviceConnectionId = "<YOUR_SERVICE_CONNECTION_ID>";
const credential = new AzurePipelinesServiceConnection(tenantId, clientId, serviceConnectionId);

const client = new SecretClient("https://key-vault-name.vault.azure.net", credential);
}
```

This credential is NOT part of `DefaultAzureCredential`.

## Chaining credentials
Expand All @@ -644,7 +646,7 @@ function withChainedTokenCredential() {
);
const client = new SecretClient("https://key-vault-name.vault.azure.net", credential);
}
````
```

## Authenticating With Azure Stack using Azure Identity

Expand Down
Expand Up @@ -14,16 +14,15 @@ import {
import { AzurePipelinesServiceConnectionCredentialOptions } from "./azurePipelinesServiceConnectionCredentialOptions";

const credentialName = "AzurePipelinesServiceConnectionCredential";
const OIDC_API_VERSION = "7.1";
const logger = credentialLogger(credentialName);
const OIDC_API_VERSION = "7.1-preview.1";

/**
* This credential is designed to be used in ADO Pipelines with service connections
* as a setup for workload identity federation.
*/
export class AzurePipelinesServiceConnectionCredential implements TokenCredential {
private clientAssertionCredential: ClientAssertionCredential | undefined;
private serviceConnectionId: string | undefined;

/**
* AzurePipelinesServiceConnectionCredential supports Federated Identity on Azure Pipelines through Service Connections.
Expand Down Expand Up @@ -51,7 +50,7 @@ export class AzurePipelinesServiceConnectionCredential implements TokenCredentia

if (clientId && tenantId && serviceConnectionId) {
this.ensurePipelinesSystemVars();
const oidcRequestUrl = `${process.env.SYSTEM_TEAMFOUNDATIONCOLLECTIONURI}${process.env.SYSTEM_TEAMPROJECTID}/_apis/distributedtask/hubs/build/plans/${process.env.SYSTEM_PLANID}/jobs/${process.env.SYSTEM_JOBID}/oidctoken?api-version=${OIDC_API_VERSION}&serviceConnectionId=${this.serviceConnectionId}`;
const oidcRequestUrl = `${process.env.SYSTEM_TEAMFOUNDATIONCOLLECTIONURI}${process.env.SYSTEM_TEAMPROJECTID}/_apis/distributedtask/hubs/build/plans/${process.env.SYSTEM_PLANID}/jobs/${process.env.SYSTEM_JOBID}/oidctoken?api-version=${OIDC_API_VERSION}&serviceConnectionId=${serviceConnectionId}`;
const systemAccessToken = `${process.env.SYSTEM_ACCESSTOKEN}`;
logger.info(
`Invoking ClientAssertionCredential with tenant ID: ${tenantId}, clientId: ${clientId} and service connection id: ${serviceConnectionId}`,
Expand All @@ -78,8 +77,7 @@ export class AzurePipelinesServiceConnectionCredential implements TokenCredentia
options?: GetTokenOptions,
): Promise<AccessToken> {
if (!this.clientAssertionCredential) {
const errorMessage = `${credentialName}: is unavailable. tenantId, clientId, and serviceConnectionId are required parameters.
To use Federation Identity in Azure Pipelines, these are required as inputs / env variables -
const errorMessage = `${credentialName}: is unavailable. To use Federation Identity in Azure Pipelines, these are required as input parameters / env variables -
tenantId,
clientId,
serviceConnectionId,
Expand Down Expand Up @@ -123,17 +121,27 @@ export class AzurePipelinesServiceConnectionCredential implements TokenCredentia
const response = await httpClient.sendRequest(request);
const text = response.bodyAsText;
if (!text) {
throw new AuthenticationError(
response.status,
`${credentialName}: Authenticated Failed. Received null token from OIDC request.`,
logger.error(
`${credentialName}: Authenticated Failed. Received null token from OIDC request. Response status- ${
response.status
}. Complete response - ${JSON.stringify(response)}`,
);
throw new CredentialUnavailableError(
`${credentialName}: Authenticated Failed. Received null token from OIDC request. Response status- ${
response.status
}. Complete response - ${JSON.stringify(response)}`,
);
}
const result = JSON.parse(text);
if (result?.oidcToken) {
return result.oidcToken;
} else {
throw new AuthenticationError(
response.status,
logger.error(
`${credentialName}: Authentication Failed. oidcToken field not detected in the response. Response = ${JSON.stringify(
result,
)}`,
);
throw new CredentialUnavailableError(
`${credentialName}: Authentication Failed. oidcToken field not detected in the response. Response = ${JSON.stringify(
result,
)}`,
Expand Down
Expand Up @@ -2,12 +2,11 @@
// Licensed under the MIT license.

import { AzurePipelinesServiceConnectionCredential } from "../../../src";
import { env, isLiveMode } from "@azure-tools/test-recorder";
import { isLiveMode } from "@azure-tools/test-recorder";
import { assert } from "@azure-tools/test-utils";

describe("AzurePipelinesServiceConnectionCredential", function () {
const scope = "https://vault.azure.net/.default";
const tenantId = env.IDENTITY_SP_TENANT_ID || env.AZURE_TENANT_ID!;
const tenantId = "72f988bf-86f1-41af-91ab-2d7cd011db47";
// const clientId = env.IDENTITY_SP_CLIENT_ID || env.AZURE_CLIENT_ID!;

it("authenticates with a valid service connection", async function () {
Expand Down

0 comments on commit a1e61aa

Please sign in to comment.