Skip to content

Commit 3e1c6f5

Browse files
Adding HTTP(S) over HTTP(S) proxy support to add accounts. (#26137) (#26162)
* Adjust axios proxy configuration * Add logging around axios get request logic * Minor clean up * Fix conditional check * Load environment proxy value * Fix compilation error * Review changes * Add additional logging to tunnel logic with axios * Remove unused import * Set proxy false when proxy endpoint found * Log when proxy endpoint is not found * Improve log wording * Prioritize workspace HTTP config before environment variables
1 parent 7d61ede commit 3e1c6f5

File tree

1 file changed

+131
-4
lines changed

1 file changed

+131
-4
lines changed

extensions/azurecore/src/account-provider/auths/azureAuth.ts

Lines changed: 131 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
import * as vscode from 'vscode';
77
import * as azdata from 'azdata';
88
import * as nls from 'vscode-nls';
9+
import * as tunnel from "tunnel";
10+
import * as http from "http";
11+
import * as https from "https";
912

1013
import {
1114
AzureAccount,
@@ -26,13 +29,19 @@ import { errorToPromptFailedResult } from './networkUtils';
2629
import { MsalCachePluginProvider } from '../utils/msalCachePlugin';
2730
import { isErrorResponseBodyWithError } from '../../azureResource/utils';
2831
import axios, { AxiosResponse, AxiosRequestConfig } from 'axios';
32+
import { getProxyAgentOptions } from '../../proxy';
2933
const localize = nls.loadMessageBundle();
3034

3135
export type GetTenantsResponseData = {
3236
value: TenantResponse[];
3337
error?: string;
3438
}
3539

40+
interface ProxyAgent {
41+
isHttps: boolean;
42+
agent: http.Agent | https.Agent;
43+
}
44+
3645
export abstract class AzureAuth implements vscode.Disposable {
3746
protected readonly memdb = new MemoryDatabase<string>();
3847
protected readonly loginEndpointUrl: string;
@@ -442,20 +451,138 @@ export abstract class AzureAuth implements vscode.Disposable {
442451

443452
//#region network functions
444453

445-
private async makeGetRequest<T>(url: string, token: string): Promise<AxiosResponse<T>> {
454+
private async makeGetRequest<T>(requestUrl: string, token: string): Promise<AxiosResponse<T>> {
446455
const config: AxiosRequestConfig = {
447456
headers: {
448457
'Content-Type': 'application/json',
449-
'Authorization': `Bearer ${token}`
458+
'Authorization': `Bearer ${token}`,
450459
},
451460
validateStatus: () => true // Never throw
452461
};
453462

454-
const response: AxiosResponse = await axios.get<T>(url, config);
455-
Logger.piiSanitized('GET request ', [{ name: 'response', objOrArray: response.data?.value as TenantResponse[] ?? response.data as GetTenantsResponseData }], [], url,);
463+
const httpConfig = vscode.workspace.getConfiguration("http");
464+
let proxy: string | undefined = httpConfig['proxy'] as string;
465+
if (!proxy) {
466+
Logger.verbose("Workspace HTTP config didn't contain a proxy endpoint. Checking environment variables.");
467+
468+
proxy = this.loadEnvironmentProxyValue();
469+
}
470+
471+
if (proxy) {
472+
Logger.verbose("Proxy endpoint found in environment variables or workspace configuration.");
473+
474+
// Turning off automatic proxy detection to avoid issues with tunneling agent by setting proxy to false.
475+
// https://github.com/axios/axios/blob/bad6d8b97b52c0c15311c92dd596fc0bff122651/lib/adapters/http.js#L85
476+
config.proxy = false;
477+
478+
const agent = this.createProxyAgent(requestUrl, proxy, httpConfig["proxyStrictSSL"]);
479+
if (agent.isHttps) {
480+
config.httpsAgent = agent.agent;
481+
}
482+
else {
483+
config.httpAgent = agent.agent;
484+
}
485+
486+
const HTTPS_PORT = 443;
487+
const HTTP_PORT = 80;
488+
const parsedRequestUrl = url.parse(requestUrl);
489+
const port = parsedRequestUrl.protocol?.startsWith("https") ? HTTPS_PORT : HTTP_PORT;
490+
491+
// Request URL will include HTTPS port 443 ('https://management.azure.com:443/tenants?api-version=2019-11-01'), so
492+
// that Axios doesn't try to reach this URL with HTTP port 80 on HTTP proxies, which result in an error. See https://github.com/axios/axios/issues/925
493+
const requestUrlWithPort = `${parsedRequestUrl.protocol}//${parsedRequestUrl.hostname}:${port}${parsedRequestUrl.path}`;
494+
const response: AxiosResponse = await axios.get<T>(requestUrlWithPort, config);
495+
Logger.piiSanitized('GET request ', [{ name: 'response', objOrArray: response.data?.value as TenantResponse[] ?? response.data as GetTenantsResponseData }], [], requestUrl,);
496+
return response;
497+
}
498+
499+
Logger.verbose("No proxy endpoint was found in the HTTP_PROXY, HTTPS_PROXY environment variables or in the workspace HTTP configuration.");
500+
const response: AxiosResponse = await axios.get<T>(requestUrl, config);
501+
Logger.piiSanitized('GET request ', [{ name: 'response', objOrArray: response.data?.value as TenantResponse[] ?? response.data as GetTenantsResponseData }], [], requestUrl,);
456502
return response;
457503
}
458504

505+
private loadEnvironmentProxyValue(): string | undefined {
506+
const HTTP_PROXY = "HTTP_PROXY";
507+
const HTTPS_PROXY = "HTTPS_PROXY";
508+
509+
if (!process) {
510+
Logger.verbose("No process object found, unable to read environment variables for proxy.");
511+
return undefined;
512+
}
513+
514+
if (process.env[HTTP_PROXY] || process.env[HTTP_PROXY.toLowerCase()]) {
515+
Logger.verbose("Loading proxy value from HTTP_PROXY environment variable.");
516+
517+
return process.env[HTTP_PROXY] || process.env[HTTP_PROXY.toLowerCase()];
518+
}
519+
else if (process.env[HTTPS_PROXY] || process.env[HTTPS_PROXY.toLowerCase()]) {
520+
Logger.verbose("Loading proxy value from HTTPS_PROXY environment variable.");
521+
522+
return process.env[HTTPS_PROXY] || process.env[HTTPS_PROXY.toLowerCase()];
523+
}
524+
525+
Logger.verbose("No proxy value found in either HTTPS_PROXY or HTTP_PROXY environment variables.");
526+
527+
return undefined;
528+
}
529+
530+
private createProxyAgent(requestUrl: string, proxy: string, proxyStrictSSL: boolean): ProxyAgent {
531+
const agentOptions = getProxyAgentOptions(url.parse(requestUrl), proxy, proxyStrictSSL);
532+
if (!agentOptions || !agentOptions.host || !agentOptions.port) {
533+
Logger.verbose("Unable to read proxy agent options to create proxy agent.");
534+
throw new Error("Unable to read proxy agent options to create proxy agent.");
535+
}
536+
537+
let tunnelOptions: tunnel.HttpsOverHttpsOptions = {};
538+
if (typeof agentOptions.auth === 'string' && agentOptions.auth) {
539+
tunnelOptions = {
540+
proxy: {
541+
proxyAuth: agentOptions.auth,
542+
host: agentOptions.host,
543+
port: Number(agentOptions.port),
544+
}
545+
};
546+
}
547+
else {
548+
tunnelOptions = {
549+
proxy: {
550+
host: agentOptions.host,
551+
port: Number(agentOptions.port),
552+
553+
}
554+
};
555+
}
556+
557+
const isHttpsRequest = requestUrl.startsWith("https");
558+
const isHttpsProxy = proxy.startsWith("https");
559+
const proxyAgent = {
560+
isHttps: isHttpsProxy,
561+
agent: this.createTunnelingAgent(isHttpsRequest, isHttpsProxy, tunnelOptions),
562+
} as ProxyAgent;
563+
564+
return proxyAgent;
565+
}
566+
567+
private createTunnelingAgent(isHttpsRequest: boolean, isHttpsProxy: boolean, tunnelOptions: tunnel.HttpsOverHttpsOptions): http.Agent | https.Agent {
568+
if (isHttpsRequest && isHttpsProxy) {
569+
Logger.verbose("Creating https request over https proxy tunneling agent");
570+
return tunnel.httpsOverHttps(tunnelOptions);
571+
}
572+
else if (isHttpsRequest && !isHttpsProxy) {
573+
Logger.verbose("Creating https request over http proxy tunneling agent");
574+
return tunnel.httpsOverHttp(tunnelOptions);
575+
}
576+
else if (!isHttpsRequest && isHttpsProxy) {
577+
Logger.verbose("Creating http request over https proxy tunneling agent");
578+
return tunnel.httpOverHttps(tunnelOptions);
579+
}
580+
else {
581+
Logger.verbose("Creating http request over http proxy tunneling agent");
582+
return tunnel.httpOverHttp(tunnelOptions);
583+
}
584+
}
585+
459586
//#endregion
460587

461588
//#region inconsequential

0 commit comments

Comments
 (0)