@azure/identity
Version:
Provides credential implementations for Azure SDK libraries that can authenticate with Microsoft Entra ID
286 lines (278 loc) • 11.6 kB
JavaScript
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var azurePowerShellCredential_exports = {};
__export(azurePowerShellCredential_exports, {
AzurePowerShellCredential: () => AzurePowerShellCredential,
commandStack: () => commandStack,
createPowerShellEnvironment: () => createPowerShellEnvironment,
formatCommand: () => formatCommand,
parseJsonToken: () => parseJsonToken,
powerShellErrors: () => powerShellErrors,
powerShellPublicErrorMessages: () => powerShellPublicErrorMessages
});
module.exports = __toCommonJS(azurePowerShellCredential_exports);
var import_tenantIdUtils = require("../util/tenantIdUtils.js");
var import_logging = require("../util/logging.js");
var import_scopeUtils = require("../util/scopeUtils.js");
var import_errors = require("../errors.js");
var import_processUtils = require("../util/processUtils.js");
var import_tracing = require("../util/tracing.js");
const logger = (0, import_logging.credentialLogger)("AzurePowerShellCredential");
const isWindows = process.platform === "win32";
const powerShellResourceEnvironmentVariable = "AZURE_IDENTITY_POWERSHELL_RESOURCE";
const powerShellTenantEnvironmentVariable = "AZURE_IDENTITY_POWERSHELL_TENANT_ID";
const powerShellPrivateEnvironmentVariables = new Set(
[powerShellResourceEnvironmentVariable, powerShellTenantEnvironmentVariable].map(
(name) => name.toLowerCase()
)
);
function createPowerShellEnvironment(resource, tenantId, environment = process.env) {
const childEnvironment = Object.fromEntries(
Object.entries(environment).filter(
([name]) => !powerShellPrivateEnvironmentVariables.has(name.toLowerCase())
)
);
childEnvironment[powerShellResourceEnvironmentVariable] = resource;
childEnvironment[powerShellTenantEnvironmentVariable] = tenantId ?? "";
return childEnvironment;
}
function formatCommand(commandName) {
if (isWindows) {
return `${commandName}.exe`;
} else {
return commandName;
}
}
async function runCommands(commands, timeout, env) {
const results = [];
for (const command of commands) {
const [file, ...parameters] = command;
const result = await import_processUtils.processUtils.execFile(file, parameters, {
encoding: "utf8",
env,
timeout
});
results.push(result);
}
return results;
}
const powerShellErrors = {
login: "Run Connect-AzAccount to login",
installed: "The specified module 'Az.Accounts' with version '2.2.0' was not loaded because no valid module file was found in any module directory"
};
const powerShellPublicErrorMessages = {
login: "Please run 'Connect-AzAccount' from PowerShell to authenticate before using this credential.",
installed: `The 'Az.Account' module >= 2.2.0 is not installed. Install the Azure Az PowerShell module with: "Install-Module -Name Az -Scope CurrentUser -Repository PSGallery -Force".`,
claim: "This credential doesn't support claims challenges. To authenticate with the required claims, please run the following command:",
troubleshoot: `To troubleshoot, visit https://aka.ms/azsdk/js/identity/powershellcredential/troubleshoot.`
};
const isLoginError = (err) => err.message.match(`(.*)${powerShellErrors.login}(.*)`);
const isNotInstalledError = (err) => err.message.match(powerShellErrors.installed);
const commandStack = [formatCommand("pwsh")];
if (isWindows) {
commandStack.push(formatCommand("powershell"));
}
class AzurePowerShellCredential {
tenantId;
additionallyAllowedTenantIds;
timeout;
/**
* Creates an instance of the {@link AzurePowerShellCredential}.
*
* To use this credential:
* - Install the Azure Az PowerShell module with:
* `Install-Module -Name Az -Scope CurrentUser -Repository PSGallery -Force`.
* - You have already logged in to Azure PowerShell using the command
* `Connect-AzAccount` from the command line.
*
* @param options - Options, to optionally allow multi-tenant requests.
*/
constructor(options) {
if (options?.tenantId) {
(0, import_tenantIdUtils.checkTenantId)(logger, options?.tenantId);
this.tenantId = options?.tenantId;
}
this.additionallyAllowedTenantIds = (0, import_tenantIdUtils.resolveAdditionallyAllowedTenantIds)(
options?.additionallyAllowedTenants
);
this.timeout = options?.processTimeoutInMs;
}
/**
* Gets the access token from Azure PowerShell
* @param resource - The resource to use when getting the token
*/
async getAzurePowerShellAccessToken(resource, tenantId, timeout) {
for (const powerShellCommand of [...commandStack]) {
try {
await runCommands([[powerShellCommand, "/?"]], timeout);
} catch (e) {
commandStack.shift();
continue;
}
const results = await runCommands(
[
[
powerShellCommand,
"-NoProfile",
"-NonInteractive",
"-Command",
`
$tenantId = $env:${powerShellTenantEnvironmentVariable}
$resource = $env:${powerShellResourceEnvironmentVariable}
$m = Import-Module Az.Accounts -MinimumVersion 2.2.0 -PassThru
$useSecureString = $m.Version -ge [version]'2.17.0' -and $m.Version -lt [version]'5.0.0'
$params = @{
ResourceUrl = $resource
}
if ($tenantId.Length -gt 0) {
$params["TenantId"] = $tenantId
}
if ($useSecureString) {
$params["AsSecureString"] = $true
}
$token = Get-AzAccessToken @params
$result = New-Object -TypeName PSObject
$result | Add-Member -MemberType NoteProperty -Name ExpiresOn -Value $token.ExpiresOn
if ($token.Token -is [System.Security.SecureString]) {
if ($PSVersionTable.PSVersion.Major -lt 7) {
$ssPtr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($token.Token)
try {
$result | Add-Member -MemberType NoteProperty -Name Token -Value ([System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($ssPtr))
}
finally {
[System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($ssPtr)
}
}
else {
$result | Add-Member -MemberType NoteProperty -Name Token -Value ($token.Token | ConvertFrom-SecureString -AsPlainText)
}
}
else {
$result | Add-Member -MemberType NoteProperty -Name Token -Value $token.Token
}
Write-Output (ConvertTo-Json $result)
`
]
],
timeout,
createPowerShellEnvironment(resource, tenantId)
);
const result = results[0];
return parseJsonToken(result);
}
throw new Error(`Unable to execute PowerShell. Ensure that it is installed in your system`);
}
/**
* Authenticates with Microsoft Entra ID and returns an access token if successful.
* If the authentication cannot be performed through PowerShell, a {@link CredentialUnavailableError} will be thrown.
*
* @param scopes - The list of scopes for which the token will have access.
* @param options - The options used to configure any requests this TokenCredential implementation might make.
*/
async getToken(scopes, options = {}) {
return import_tracing.tracingClient.withSpan(`${this.constructor.name}.getToken`, options, async () => {
const scope = typeof scopes === "string" ? scopes : scopes[0];
const claimsValue = options.claims;
if (claimsValue && claimsValue.trim()) {
const encodedClaims = btoa(claimsValue);
let loginCmd = `Connect-AzAccount -ClaimsChallenge ${encodedClaims}`;
const tenantIdFromOptions = options.tenantId;
if (tenantIdFromOptions) {
loginCmd += ` -Tenant ${tenantIdFromOptions}`;
}
const error = new import_errors.CredentialUnavailableError(
`${powerShellPublicErrorMessages.claim} ${loginCmd}`
);
logger.getToken.info((0, import_logging.formatError)(scope, error));
throw error;
}
const tenantId = (0, import_tenantIdUtils.processMultiTenantRequest)(
this.tenantId,
options,
this.additionallyAllowedTenantIds
);
if (tenantId) {
(0, import_tenantIdUtils.checkTenantId)(logger, tenantId);
}
try {
(0, import_scopeUtils.ensureValidScopeForDevTimeCreds)(scope, logger);
logger.getToken.info(`Using the scope ${scope}`);
const resource = (0, import_scopeUtils.getScopeResource)(scope);
const response = await this.getAzurePowerShellAccessToken(resource, tenantId, this.timeout);
logger.getToken.info((0, import_logging.formatSuccess)(scopes));
return {
token: response.Token,
expiresOnTimestamp: new Date(response.ExpiresOn).getTime(),
tokenType: "Bearer"
};
} catch (err) {
if (isNotInstalledError(err)) {
const error2 = new import_errors.CredentialUnavailableError(powerShellPublicErrorMessages.installed);
logger.getToken.info((0, import_logging.formatError)(scope, error2));
throw error2;
} else if (isLoginError(err)) {
const error2 = new import_errors.CredentialUnavailableError(powerShellPublicErrorMessages.login);
logger.getToken.info((0, import_logging.formatError)(scope, error2));
throw error2;
}
const error = new import_errors.CredentialUnavailableError(
`${err}. ${powerShellPublicErrorMessages.troubleshoot}`
);
logger.getToken.info((0, import_logging.formatError)(scope, error));
throw error;
}
});
}
}
async function parseJsonToken(result) {
const jsonRegex = /{[^{}]*}/g;
const matches = result.match(jsonRegex);
let resultWithoutToken = result;
if (matches) {
try {
for (const item of matches) {
try {
const jsonContent = JSON.parse(item);
if (jsonContent?.Token) {
resultWithoutToken = resultWithoutToken.replace(item, "");
if (resultWithoutToken) {
logger.getToken.warning(resultWithoutToken);
}
return jsonContent;
}
} catch (e) {
continue;
}
}
} catch (e) {
throw new Error(`Unable to parse the output of PowerShell. Received output: ${result}`);
}
}
throw new Error(`No access token found in the output. Received output: ${result}`);
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
AzurePowerShellCredential,
commandStack,
createPowerShellEnvironment,
formatCommand,
parseJsonToken,
powerShellErrors,
powerShellPublicErrorMessages
});
//# sourceMappingURL=azurePowerShellCredential.js.map