Skip to content

Commit cc4ca0c

Browse files
committed
feat: managed identity integration
1 parent e2b00c2 commit cc4ca0c

File tree

5 files changed

+101
-56
lines changed

5 files changed

+101
-56
lines changed

src/handlers/handlerUtils.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1011,11 +1011,15 @@ export function constructConfigFromRequestHeaders(
10111011
resourceName: requestHeaders[`x-${POWERED_BY}-azure-resource-name`],
10121012
deploymentId: requestHeaders[`x-${POWERED_BY}-azure-deployment-id`],
10131013
apiVersion: requestHeaders[`x-${POWERED_BY}-azure-api-version`],
1014-
azureModelName: requestHeaders[`x-${POWERED_BY}-azure-model-name`],
1014+
azureAdToken: requestHeaders[`x-${POWERED_BY}-azure-ad-token`],
1015+
azureAuthMode: requestHeaders[`x-${POWERED_BY}-azure-auth-mode`],
1016+
azureManagedClientId:
1017+
requestHeaders[`x-${POWERED_BY}-azure-managed-client-id`],
10151018
azureEntraClientId: requestHeaders[`x-${POWERED_BY}-azure-entra-client-id`],
10161019
azureEntraClientSecret:
10171020
requestHeaders[`x-${POWERED_BY}-azure-entra-client-secret`],
10181021
azureEntraTenantId: requestHeaders[`x-${POWERED_BY}-azure-entra-tenant-id`],
1022+
azureModelName: requestHeaders[`x-${POWERED_BY}-azure-model-name`],
10191023
};
10201024

10211025
const stabilityAiConfig = {
@@ -1034,10 +1038,6 @@ export function constructConfigFromRequestHeaders(
10341038
requestHeaders[`x-${POWERED_BY}-azure-deployment-type`],
10351039
azureApiVersion: requestHeaders[`x-${POWERED_BY}-azure-api-version`],
10361040
azureEndpointName: requestHeaders[`x-${POWERED_BY}-azure-endpoint-name`],
1037-
azureEntraClientId: requestHeaders[`x-${POWERED_BY}-azure-entra-client-id`],
1038-
azureEntraClientSecret:
1039-
requestHeaders[`x-${POWERED_BY}-azure-entra-client-secret`],
1040-
azureEntraTenantId: requestHeaders[`x-${POWERED_BY}-azure-entra-tenant-id`],
10411041
};
10421042

10431043
const bedrockConfig = {

src/providers/azure-ai-inference/api.ts

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { GITHUB } from '../../globals';
2-
import { getAccessTokenFromEntraId } from '../azure-openai/utils';
32
import { ProviderAPIConfig } from '../types';
43

54
const AzureAIInferenceAPI: ProviderAPIConfig = {
@@ -21,30 +20,15 @@ const AzureAIInferenceAPI: ProviderAPIConfig = {
2120
return `https://${azureEndpointName}.${azureRegion}.inference.ml.azure.com/score`;
2221
},
2322
headers: async ({ providerOptions }) => {
24-
const {
25-
apiKey,
26-
azureDeploymentType,
27-
azureDeploymentName,
28-
azureEntraClientId,
29-
azureEntraClientSecret,
30-
azureEntraTenantId,
31-
} = providerOptions;
23+
const { apiKey, azureDeploymentType, azureDeploymentName } =
24+
providerOptions;
3225
const headers: Record<string, string> = {
3326
Authorization: `Bearer ${apiKey}`,
3427
'extra-parameters': 'ignore',
3528
};
3629
if (azureDeploymentType === 'managed' && azureDeploymentName) {
3730
headers['azureml-model-deployment'] = azureDeploymentName;
3831
}
39-
if (azureEntraClientId && azureEntraClientSecret && azureEntraTenantId) {
40-
const accessToken = await getAccessTokenFromEntraId(
41-
azureEntraTenantId,
42-
azureEntraClientId,
43-
azureEntraClientSecret,
44-
'https://cognitiveservices.azure.com/decision/.default'
45-
);
46-
headers['Authorization'] = `Bearer ${accessToken}`;
47-
}
4832
return headers;
4933
},
5034
getEndpoint: ({ providerOptions, fn }) => {

src/providers/azure-openai/api.ts

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,46 @@
11
import { ProviderAPIConfig } from '../types';
2-
import { getAccessTokenFromEntraId } from './utils';
2+
import { getAccessTokenFromEntraId, getAzureManagedIdentityToken } from './utils';
33

44
const AzureOpenAIAPIConfig: ProviderAPIConfig = {
55
getBaseURL: ({ providerOptions }) => {
66
const { resourceName, deploymentId } = providerOptions;
77
return `https://${resourceName}.openai.azure.com/openai/deployments/${deploymentId}`;
88
},
99
headers: async ({ providerOptions, fn }) => {
10+
const { apiKey, azureAuthMode } = providerOptions;
11+
12+
if (azureAuthMode === 'entra') {
13+
const { azureEntraTenantId, azureEntraClientId, azureEntraClientSecret } =
14+
providerOptions;
15+
if (azureEntraTenantId && azureEntraClientId && azureEntraClientSecret) {
16+
const scope = 'https://cognitiveservices.azure.com/.default';
17+
const accessToken = await getAccessTokenFromEntraId(
18+
azureEntraTenantId,
19+
azureEntraClientId,
20+
azureEntraClientSecret,
21+
scope
22+
);
23+
return {
24+
Authorization: `Bearer ${accessToken}`,
25+
};
26+
}
27+
}
28+
if (azureAuthMode === 'managed') {
29+
const { azureManagedClientId } = providerOptions;
30+
const resource = 'https://cognitiveservices.azure.com/';
31+
const accessToken = await getAzureManagedIdentityToken(
32+
resource,
33+
azureManagedClientId
34+
);
35+
return {
36+
Authorization: `Bearer ${accessToken}`,
37+
};
38+
}
1039
const headersObj: Record<string, string> = {
11-
'api-key': `${providerOptions.apiKey}`,
40+
'api-key': `${apiKey}`,
1241
};
1342
if (fn === 'createTranscription' || fn === 'createTranslation')
1443
headersObj['Content-Type'] = 'multipart/form-data';
15-
const { azureEntraClientId, azureEntraClientSecret, azureEntraTenantId } =
16-
providerOptions;
17-
if (azureEntraClientId && azureEntraClientSecret && azureEntraTenantId) {
18-
const accessToken = await getAccessTokenFromEntraId(
19-
azureEntraTenantId,
20-
azureEntraClientId,
21-
azureEntraClientSecret,
22-
'https://cognitiveservices.azure.com/decision/.default'
23-
);
24-
headersObj['Authorization'] = `Bearer ${accessToken}`;
25-
}
26-
2744
return headersObj;
2845
},
2946
getEndpoint: ({ providerOptions, fn }) => {

src/providers/azure-openai/utils.ts

Lines changed: 50 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,59 @@ export async function getAccessTokenFromEntraId(
22
tenantId: string,
33
clientId: string,
44
clientSecret: string,
5-
scope = 'https://openai.azure.com/.default'
5+
scope = 'https://cognitiveservices.azure.com/.default'
66
) {
7-
const url = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`;
7+
try {
8+
const url = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`;
9+
const params = new URLSearchParams({
10+
client_id: clientId,
11+
client_secret: clientSecret,
12+
scope: scope,
13+
grant_type: 'client_credentials',
14+
});
815

9-
const params = new URLSearchParams({
10-
client_id: clientId,
11-
client_secret: clientSecret,
12-
scope: scope,
13-
grant_type: 'client_credentials',
14-
});
16+
const response = await fetch(url, {
17+
method: 'POST',
18+
headers: {
19+
'Content-Type': 'application/x-www-form-urlencoded',
20+
},
21+
body: params,
22+
});
1523

16-
const response = await fetch(url, {
17-
method: 'POST',
18-
headers: {
19-
'Content-Type': 'application/x-www-form-urlencoded',
20-
},
21-
body: params,
22-
});
24+
if (!response.ok) {
25+
const errorMessage = await response.text();
26+
console.log({ message: `Error from Entra ${errorMessage}` });
27+
return undefined;
28+
}
29+
const data: { access_token: string } = await response.json();
30+
return data.access_token;
31+
} catch (error) {
32+
console.log(error);
33+
}
34+
}
2335

24-
if (!response.ok) {
25-
throw new Error(`Error fetching access token: ${response.statusText}`);
36+
export async function getAzureManagedIdentityToken(
37+
resource: string,
38+
clientId?: string
39+
) {
40+
try {
41+
const response = await fetch(
42+
`http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=${encodeURIComponent(resource)}${clientId ? `&client_id=${encodeURIComponent(clientId)}` : ''}`,
43+
{
44+
method: 'GET',
45+
headers: {
46+
Metadata: 'true',
47+
},
48+
}
49+
);
50+
if (!response.ok) {
51+
const errorMessage = await response.text();
52+
console.log({ message: `Error from Managed ${errorMessage}` });
53+
return undefined;
54+
}
55+
const data: { access_token: string } = await response.json();
56+
return data.access_token;
57+
} catch (error) {
58+
console.log({ error });
2659
}
27-
const data: { access_token: string } = await response.json();
28-
return data.access_token;
2960
}

src/types/requestBody.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ export interface Options {
6060
apiVersion?: string;
6161
adAuth?: string;
6262
azureModelName?: string;
63+
azureAuthMode?: string; // can be entra or managed
64+
azureManagedClientId?: string;
6365
azureEntraClientId?: string;
6466
azureEntraClientSecret?: string;
6567
azureEntraTenantId?: string;
@@ -143,6 +145,12 @@ export interface Targets {
143145
deploymentId?: string;
144146
apiVersion?: string;
145147
adAuth?: string;
148+
azureAuthMode?: string;
149+
azureManagedClientId?: string;
150+
azureEntraClientId?: string;
151+
azureEntraClientSecret?: string;
152+
azureEntraTenantId?: string;
153+
azureModelName?: string;
146154
/** provider option index picked based on weight in loadbalance mode */
147155
index?: number;
148156
cache?: CacheSettings | string;
@@ -353,6 +361,11 @@ export interface ShortConfig {
353361
azureModelName?: string;
354362
workersAiAccountId?: string;
355363
apiVersion?: string;
364+
azureAuthMode?: string;
365+
azureManagedClientId?: string;
366+
azureEntraClientId?: string;
367+
azureEntraClientSecret?: string;
368+
azureEntraTenantId?: string;
356369
customHost?: string;
357370
// Google Vertex AI specific
358371
vertexRegion?: string;

0 commit comments

Comments
 (0)