azureCliCredentials.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. "use strict";
  2. // Copyright (c) Microsoft Corporation. All rights reserved.
  3. // Licensed under the MIT License. See License.txt in the project root for license information.
  4. var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
  5. return new (P || (P = Promise))(function (resolve, reject) {
  6. function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
  7. function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
  8. function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
  9. step((generator = generator.apply(thisArg, _arguments || [])).next());
  10. });
  11. };
  12. Object.defineProperty(exports, "__esModule", { value: true });
  13. const ms_rest_js_1 = require("@azure/ms-rest-js");
  14. const login_1 = require("../login");
  15. /**
  16. * Describes the credentials by retrieving token via Azure CLI.
  17. */
  18. class AzureCliCredentials {
  19. constructor(subscriptionInfo, tokenInfo,
  20. // tslint:disable-next-line: no-inferrable-types
  21. resource = "https://management.azure.com") {
  22. /**
  23. * Azure resource endpoints.
  24. * - Defaults to Azure Resource Manager from environment: AzureCloud. "https://management.azure.com"
  25. * - For Azure KeyVault: "https://vault.azure.net"
  26. * - For Azure Batch: "https://batch.core.windows.net"
  27. * - For Azure Active Directory Graph: "https://graph.windows.net"
  28. *
  29. * To get the resource for other clouds:
  30. * - `az cloud list`
  31. */
  32. // tslint:disable-next-line: no-inferrable-types
  33. this.resource = "https://management.azure.com";
  34. /**
  35. * The number of seconds within which it is good to renew the token.
  36. * A constant set to 270 seconds (4.5 minutes).
  37. */
  38. this._tokenRenewalMarginInSeconds = 270;
  39. this.subscriptionInfo = subscriptionInfo;
  40. this.tokenInfo = tokenInfo;
  41. this.resource = resource;
  42. }
  43. /**
  44. * Tries to get the new token from Azure CLI, if the token has expired or the subscription has
  45. * changed else uses the cached accessToken.
  46. * @return The tokenResponse (tokenType and accessToken are the two important properties).
  47. */
  48. getToken() {
  49. return __awaiter(this, void 0, void 0, function* () {
  50. if (this._hasTokenExpired() || this._hasSubscriptionChanged() || this._hasResourceChanged()) {
  51. try {
  52. // refresh the access token
  53. this.tokenInfo = yield AzureCliCredentials.getAccessToken({
  54. subscriptionIdOrName: this.subscriptionInfo.id,
  55. resource: this.resource
  56. });
  57. }
  58. catch (err) {
  59. throw new Error(`An error occurred while refreshing the new access ` +
  60. `token:${err.stderr ? err.stderr : err.message}`);
  61. }
  62. }
  63. const result = {
  64. accessToken: this.tokenInfo.accessToken,
  65. tokenType: this.tokenInfo.tokenType,
  66. expiresOn: this.tokenInfo.expiresOn,
  67. tenantId: this.tokenInfo.tenant
  68. };
  69. return result;
  70. });
  71. }
  72. /**
  73. * Signs a request with the Authentication header.
  74. * @param The request to be signed.
  75. */
  76. signRequest(webResource) {
  77. return __awaiter(this, void 0, void 0, function* () {
  78. const tokenResponse = yield this.getToken();
  79. webResource.headers.set(ms_rest_js_1.Constants.HeaderConstants.AUTHORIZATION, `${tokenResponse.tokenType} ${tokenResponse.accessToken}`);
  80. return Promise.resolve(webResource);
  81. });
  82. }
  83. _hasTokenExpired() {
  84. let result = true;
  85. const now = Math.floor(Date.now() / 1000);
  86. if (this.tokenInfo.expiresOn &&
  87. this.tokenInfo.expiresOn instanceof Date &&
  88. Math.floor(this.tokenInfo.expiresOn.getTime() / 1000) - now > this._tokenRenewalMarginInSeconds) {
  89. result = false;
  90. }
  91. return result;
  92. }
  93. _hasSubscriptionChanged() {
  94. return this.subscriptionInfo.id !== this.tokenInfo.subscription;
  95. }
  96. _parseToken() {
  97. try {
  98. const base64Url = this.tokenInfo.accessToken.split(".")[1];
  99. const base64 = decodeURIComponent(Buffer.from(base64Url, "base64").toString("binary").split("").map((c) => {
  100. return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
  101. }).join(""));
  102. return JSON.parse(base64);
  103. }
  104. catch (err) {
  105. const msg = `An error occurred while parsing the access token: ${err.stack}`;
  106. throw new Error(msg);
  107. }
  108. }
  109. _isAzureResourceManagerEndpoint(newResource, currentResource) {
  110. if (newResource.endsWith("/"))
  111. newResource = newResource.slice(0, -1);
  112. if (currentResource.endsWith("/"))
  113. currentResource = currentResource.slice(0, -1);
  114. return (newResource === "https://management.core.windows.net" &&
  115. currentResource === "https://management.azure.com") ||
  116. (newResource === "https://management.azure.com" &&
  117. currentResource === "https://management.core.windows.net");
  118. }
  119. _hasResourceChanged() {
  120. const parsedToken = this._parseToken();
  121. // normalize the resource string, since it is possible to
  122. // provide a resource without a trailing slash
  123. const currentResource = parsedToken.aud && parsedToken.aud.endsWith("/")
  124. ? parsedToken.aud.slice(0, -1)
  125. : parsedToken.aud;
  126. const newResource = this.resource.endsWith("/")
  127. ? this.resource.slice(0, -1)
  128. : this.resource;
  129. const result = this._isAzureResourceManagerEndpoint(newResource, currentResource)
  130. ? false
  131. : currentResource !== newResource;
  132. return result;
  133. }
  134. /**
  135. * Gets the access token for the default or specified subscription.
  136. * @param options Optional parameters that can be provided to get the access token.
  137. */
  138. static getAccessToken(options = {}) {
  139. return __awaiter(this, void 0, void 0, function* () {
  140. try {
  141. let cmd = "account get-access-token";
  142. if (options.subscriptionIdOrName) {
  143. cmd += ` -s "${options.subscriptionIdOrName}"`;
  144. }
  145. if (options.resource) {
  146. cmd += ` --resource ${options.resource}`;
  147. }
  148. const result = yield login_1.execAz(cmd);
  149. result.expiresOn = new Date(result.expiresOn);
  150. return result;
  151. }
  152. catch (err) {
  153. const message = `An error occurred while getting credentials from ` +
  154. `Azure CLI: ${err.stack}`;
  155. throw new Error(message);
  156. }
  157. });
  158. }
  159. /**
  160. * Gets the subscription from Azure CLI.
  161. * @param subscriptionIdOrName - The name or id of the subscription for which the information is
  162. * required.
  163. */
  164. static getSubscription(subscriptionIdOrName) {
  165. return __awaiter(this, void 0, void 0, function* () {
  166. if (subscriptionIdOrName && (typeof subscriptionIdOrName !== "string" || !subscriptionIdOrName.length)) {
  167. throw new Error("'subscriptionIdOrName' must be a non-empty string.");
  168. }
  169. try {
  170. let cmd = "account show";
  171. if (subscriptionIdOrName) {
  172. cmd += ` -s "${subscriptionIdOrName}"`;
  173. }
  174. const result = yield login_1.execAz(cmd);
  175. return result;
  176. }
  177. catch (err) {
  178. const message = `An error occurred while getting information about the current subscription from ` +
  179. `Azure CLI: ${err.stack}`;
  180. throw new Error(message);
  181. }
  182. });
  183. }
  184. /**
  185. * Sets the specified subscription as the default subscription for Azure CLI.
  186. * @param subscriptionIdOrName The name or id of the subsciption that needs to be set as the
  187. * default subscription.
  188. */
  189. static setDefaultSubscription(subscriptionIdOrName) {
  190. return __awaiter(this, void 0, void 0, function* () {
  191. try {
  192. yield login_1.execAz(`account set -s ${subscriptionIdOrName}`);
  193. }
  194. catch (err) {
  195. const message = `An error occurred while setting the current subscription from ` +
  196. `Azure CLI: ${err.stack}`;
  197. throw new Error(message);
  198. }
  199. });
  200. }
  201. /**
  202. * Returns a list of all the subscriptions from Azure CLI.
  203. * @param options Optional parameters that can be provided while listing all the subcriptions.
  204. */
  205. static listAllSubscriptions(options = {}) {
  206. return __awaiter(this, void 0, void 0, function* () {
  207. let subscriptionList = [];
  208. try {
  209. let cmd = "account list";
  210. if (options.all) {
  211. cmd += " --all";
  212. }
  213. if (options.refresh) {
  214. cmd += "--refresh";
  215. }
  216. subscriptionList = yield login_1.execAz(cmd);
  217. if (subscriptionList && subscriptionList.length) {
  218. for (const sub of subscriptionList) {
  219. if (sub.cloudName) {
  220. sub.environmentName = sub.cloudName;
  221. delete sub.cloudName;
  222. }
  223. }
  224. }
  225. return subscriptionList;
  226. }
  227. catch (err) {
  228. const message = `An error occurred while getting a list of all the subscription from ` +
  229. `Azure CLI: ${err.stack}`;
  230. throw new Error(message);
  231. }
  232. });
  233. }
  234. /**
  235. * Provides credentials that can be used by the JS SDK to interact with Azure via azure cli.
  236. * **Pre-requisite**
  237. * - **install azure-cli** . For more information see
  238. * {@link https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest Install Azure CLI}
  239. * - **login via `az login`**
  240. * @param options - Optional parameters that can be provided while creating AzureCliCredentials.
  241. */
  242. static create(options = {}) {
  243. return __awaiter(this, void 0, void 0, function* () {
  244. const [subscriptinInfo, accessToken] = yield Promise.all([
  245. AzureCliCredentials.getSubscription(options.subscriptionIdOrName),
  246. AzureCliCredentials.getAccessToken(options)
  247. ]);
  248. return new AzureCliCredentials(subscriptinInfo, accessToken, options.resource);
  249. });
  250. }
  251. }
  252. exports.AzureCliCredentials = AzureCliCredentials;
  253. //# sourceMappingURL=azureCliCredentials.js.map