Skip to content

Commit

Permalink
(fix: TT-270) Improve proxy resiliency (#16)
Browse files Browse the repository at this point in the history
* use proxy auto configuration settings
* adapt daemon to use pac file
* specify proxy pac file content type
* update version to 0.0.3-alpha
  • Loading branch information
keplervital authored Jun 26, 2023
1 parent 2bb92a4 commit 0a6c066
Show file tree
Hide file tree
Showing 27 changed files with 230 additions and 157 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[![GitHub license](https://img.shields.io/badge/license-Apache%202.0-blue.svg?style=for-the-badge)](LICENSE)
[![GitHub license](https://img.shields.io/badge/install-MacOSX-blue.svg?style=for-the-badge&logo=apple)](https://github.com/dfinity/http-proxy/releases/download/0.0.2-alpha/ic-http-proxy-mac-universal-0.0.2-alpha.dmg)
[![GitHub license](https://img.shields.io/badge/install-Windows-blue.svg?style=for-the-badge&logo=windows)](https://github.com/dfinity/http-proxy/releases/download/0.0.2-alpha/ic-http-proxy-win-x64-0.0.2-alpha.exe)
[![GitHub license](https://img.shields.io/badge/install-MacOSX-blue.svg?style=for-the-badge&logo=apple)](https://github.com/dfinity/http-proxy/releases/download/0.0.3-alpha/ic-http-proxy-mac-universal-0.0.3-alpha.dmg)
[![GitHub license](https://img.shields.io/badge/install-Windows-blue.svg?style=for-the-badge&logo=windows)](https://github.com/dfinity/http-proxy/releases/download/0.0.3-alpha/ic-http-proxy-win-x64-0.0.3-alpha.exe)

# IC HTTP Proxy
> This application is currently only a proof of concept implementation and should be used at your own risk.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@dfinity/http-proxy",
"version": "0.0.2-alpha",
"version": "0.0.3-alpha",
"description": "HTTP Proxy to enable trustless access to the Internet Computer.",
"author": "Kepler Vital <[email protected]>",
"license": "Apache-2.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@dfinity/http-proxy-core",
"version": "0.0.2-alpha",
"version": "0.0.3-alpha",
"description": "Gateway server to enable trustless access to the Internet Computer.",
"main": "built/main.js",
"types": "built/main.d.ts",
Expand Down
4 changes: 2 additions & 2 deletions packages/daemon/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@dfinity/http-proxy-daemon",
"version": "0.0.2-alpha",
"version": "0.0.3-alpha",
"description": "Daemon process to enable trustless access to the Internet Computer.",
"main": "built/main.js",
"types": "built/main.d.ts",
Expand Down Expand Up @@ -59,7 +59,7 @@
"typescript": "^4.9.5"
},
"dependencies": {
"@dfinity/http-proxy-core": "0.0.2-alpha",
"@dfinity/http-proxy-core": "0.0.3-alpha",
"http-proxy": "^1.18.1",
"node-cache": "^5.1.2",
"node-forge": "^1.3.1",
Expand Down
1 change: 1 addition & 0 deletions packages/daemon/src/daemon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export class Daemon {
host: message.host,
port: message.port,
},
pac: message.pac,
});

await this.platform.attach();
Expand Down
2 changes: 2 additions & 0 deletions packages/daemon/src/platforms/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ export class PlatformFactory {
return new MacPlatform({
ca: configs.ca,
proxy: configs.proxy,
pac: configs.pac,
});
case SupportedPlatforms.Windows:
return new WindowsPlatform({
ca: configs.ca,
proxy: configs.proxy,
pac: configs.pac,
});
default:
throw new UnsupportedPlatformError('unknown');
Expand Down
59 changes: 17 additions & 42 deletions packages/daemon/src/platforms/mac/mac.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,11 @@ import { execAsync, getFile, logger, saveFile } from '@dfinity/http-proxy-core';
import { exec } from 'child_process';
import { resolve } from 'path';
import { Platform } from '../typings';
import {
PlatformConfigs,
PlatformProxyInfo,
WebProxyConfiguration,
} from './typings';
import { PlatformConfigs, PlatformProxyInfo } from './typings';
import {
CURL_RC_FILE,
SHELL_SCRIPT_SEPARATOR,
resolveNetworkInfo,
getActiveNetworkService,
} from './utils';

export class MacPlatform implements Platform {
Expand Down Expand Up @@ -114,9 +110,8 @@ export class MacPlatform implements Platform {
encoding: 'utf-8',
});

// configure proxy in all network interfaces
const networkInfo = await resolveNetworkInfo({ host, port });
await this.tooggleNetworkWebProxy(networkInfo, enable);
// configure proxy to the active network interface
await this.tooggleNetworkWebProxy(enable);

ok();
} catch (e) {
Expand All @@ -126,48 +121,28 @@ export class MacPlatform implements Platform {
});
}

private async tooggleNetworkWebProxy(
networkPorts: Map<string, WebProxyConfiguration>,
enable: boolean
): Promise<void> {
const hasIncorrectStatus = Array.from(networkPorts.values()).some(
(proxy) => proxy.http.enabled !== enable || proxy.https.enabled !== enable
);
private async tooggleNetworkWebProxy(enable: boolean): Promise<void> {
const networkService = getActiveNetworkService();

if (!hasIncorrectStatus) {
return;
if (!networkService) {
throw new Error('no active network service found');
}

const status = enable ? 'on' : 'off';
const commands: string[] = [];
// enable admin privileges
commands.push(
`security authorizationdb write com.apple.trust-settings.admin allow`
);
// set proxy host configuration
for (const [port, proxyStatus] of networkPorts.entries()) {
if (!proxyStatus.http.enabled) {
commands.push(
`networksetup -setwebproxy "${port}" ${this.configs.proxy.host} ${this.configs.proxy.port}`
);
}
if (!proxyStatus.https.enabled) {
commands.push(
`networksetup -setsecurewebproxy "${port}" ${this.configs.proxy.host} ${this.configs.proxy.port}`
);
}
}
const status = enable ? 'on' : 'off';
// toggle web proxy for all network interfaces
for (const [port, proxyStatus] of networkPorts.entries()) {
if (proxyStatus.http.enabled !== enable) {
commands.push(`networksetup -setwebproxystate "${port}" ${status}`);
}
if (proxyStatus.https.enabled !== enable) {
commands.push(
`networksetup -setsecurewebproxystate "${port}" ${status}`
);
}
// toggle web proxy for the active network interface
if (enable) {
commands.push(
`networksetup -setautoproxyurl "${networkService}" "http://${this.configs.pac.host}:${this.configs.pac.port}/proxy.pac"`
);
}
commands.push(
`networksetup -setautoproxystate "${networkService}" ${status}`
);
// remove admin privileges
commands.push(
`security authorizationdb remove com.apple.trust-settings.admin`
Expand Down
3 changes: 2 additions & 1 deletion packages/daemon/src/platforms/mac/typings.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { PlatformRootCA } from '../typings';
import { PlatformPacInfo, PlatformRootCA } from '../typings';

export interface PlatformProxyInfo {
host: string;
Expand All @@ -8,6 +8,7 @@ export interface PlatformProxyInfo {
export interface PlatformConfigs {
ca: PlatformRootCA;
proxy: PlatformProxyInfo;
pac: PlatformPacInfo;
}

export interface SystemWebProxyInfo {
Expand Down
101 changes: 17 additions & 84 deletions packages/daemon/src/platforms/mac/utils.ts
Original file line number Diff line number Diff line change
@@ -1,92 +1,25 @@
import { exec } from 'child_process';
import { WebProxyConfiguration } from './typings';
import { PlatformProxyInfo } from '../typings';
import { execSync } from 'child_process';

export const SHELL_SCRIPT_SEPARATOR = ' ; ';
export const CURL_RC_FILE = '.curlrc';
export const PROXY_GET_SEPARATOR = ':ic-separator:';

export const resolveNetworkInfo = async (
proxy: PlatformProxyInfo
): Promise<Map<string, WebProxyConfiguration>> => {
const ports = await fetchHardwarePorts();
const networkInfo = new Map<string, WebProxyConfiguration>();
for (const port of ports) {
const webProxyState = await fetchNetworkWebProxy(port, proxy);
networkInfo.set(port, webProxyState);
export const getActiveNetworkService = (): string | null => {
const networkServices = execSync(
`networksetup -listallnetworkservices | tail -n +2`
)
.toString()
.split('\n');
for (const networkService of networkServices) {
const assignedIpAddress = execSync(
`networksetup -getinfo "${networkService}" | awk '/^IP address:/{print $3}'`
)
.toString()
.trim();
if (assignedIpAddress.length > 0) {
return networkService;
}
}

return networkInfo;
};

export const fetchHardwarePorts = async (): Promise<string[]> => {
return new Promise<string[]>((ok, err) => {
exec(
`networksetup -listnetworkserviceorder | grep 'Hardware Port'`,
(error, stdout) => {
if (error) {
return err(error);
}

const ports = stdout
.split('\n')
.map((line) => {
const [, port] =
line.match(new RegExp(/Hardware Port:\s(.*),.*/)) ?? [];

return port;
})
.filter((port) => !!port) as string[];

ok(ports);
}
);
});
};

export const fetchNetworkWebProxy = async (
networkHardwarePort = 'wi-fi',
proxy: PlatformProxyInfo
): Promise<WebProxyConfiguration> => {
return new Promise<WebProxyConfiguration>(async (ok, err) => {
const shellScript =
`networksetup -getwebproxy "${networkHardwarePort}"` +
SHELL_SCRIPT_SEPARATOR +
`echo "${PROXY_GET_SEPARATOR}"` +
SHELL_SCRIPT_SEPARATOR +
`networksetup -getsecurewebproxy "${networkHardwarePort}"`;

exec(`${shellScript}`, (error, stdout) => {
if (error) {
return err(error);
}

const [rawHttpProxy, rawHttpsProxy] = stdout.split(PROXY_GET_SEPARATOR);

const isEnabled = (parts: string[]): boolean => {
const sameProxyHost = parts.some((part) => {
const [, host] =
part.trim().match(new RegExp(/^Server:\s+(.*)/)) ?? [];
return host ? host === proxy.host : false;
});
const sameProxyPort = parts.some((part) => {
const [, port] = part.trim().match(new RegExp(/^Port:\s+(.*)/)) ?? [];
return port ? Number(port) === proxy.port : false;
});
const isEnabled = parts.some((part) => {
const [, enabled] =
part.trim().match(new RegExp(/^Enabled:\s+(.*)/)) ?? [];

return enabled ? enabled.toLowerCase() === 'yes' : false;
});

return sameProxyHost && sameProxyPort && isEnabled;
};

ok({
http: { enabled: isEnabled(rawHttpProxy.split('\n')) },
https: { enabled: isEnabled(rawHttpsProxy.split('\n')) },
});
});
});
return null;
};
3 changes: 3 additions & 0 deletions packages/daemon/src/platforms/typings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ export interface PlatformProxyInfo {
port: number;
}

export type PlatformPacInfo = PlatformProxyInfo;

export interface PlatformBuildConfigs {
platform: string;
ca: PlatformRootCA;
proxy: PlatformProxyInfo;
pac: PlatformPacInfo;
}
14 changes: 14 additions & 0 deletions packages/daemon/src/platforms/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export const PAC_FILE_NAME = 'ic-proxy.pac';

export const getProxyAutoConfiguration = (
proxyHost: string,
proxyPort: number
): string => {
return `function FindProxyForURL(url, host) {
if (url.startsWith("https:") || url.startsWith("http:")) {
return "PROXY ${proxyHost}:${proxyPort}; DIRECT";
}
return "DIRECT";
}`;
};
3 changes: 2 additions & 1 deletion packages/daemon/src/platforms/windows/typings.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { PlatformRootCA } from '../typings';
import { PlatformPacInfo, PlatformRootCA } from '../typings';

export interface PlatformProxyInfo {
host: string;
Expand All @@ -8,6 +8,7 @@ export interface PlatformProxyInfo {
export interface PlatformConfigs {
ca: PlatformRootCA;
proxy: PlatformProxyInfo;
pac: PlatformPacInfo;
}

export interface SystemWebProxyInfo {
Expand Down
12 changes: 6 additions & 6 deletions packages/daemon/src/platforms/windows/windows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ export class WindowsPlatform implements Platform {
this.configs.ca.commonName
);
await this.configureWebProxy(true, {
host: this.configs.proxy.host,
port: this.configs.proxy.port,
host: this.configs.pac.host,
port: this.configs.pac.port,
});
}

Expand All @@ -39,8 +39,8 @@ export class WindowsPlatform implements Platform {
this.configs.ca.commonName
);
await this.configureWebProxy(false, {
host: this.configs.proxy.host,
port: this.configs.proxy.port,
host: this.configs.pac.host,
port: this.configs.pac.port,
});
}

Expand Down Expand Up @@ -74,8 +74,8 @@ export class WindowsPlatform implements Platform {
return new Promise<void>(async (ok, err) => {
try {
const updateInternetSettingsProxy = enable
? `powershell -command "Set-ItemProperty -Path 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings' -name ProxyServer -Value 'http://${host}:${port}'"`
: `powershell -command "Set-ItemProperty -Path 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings' -name ProxyServer -Value ''"`;
? `powershell -command "Set-ItemProperty -Path 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings' -name AutoConfigURL -Value 'http://${host}:${port}/proxy.pac'"`
: `powershell -command "Set-ItemProperty -Path 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings' -name AutoConfigURL -Value ''"`;

const updateInternetSettingsEnabled = enable
? `powershell -command "Set-ItemProperty -Path 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings' ProxyEnable -value 1"`
Expand Down
4 changes: 4 additions & 0 deletions packages/daemon/src/typings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ export interface EnableProxyMessage {
type: MessageType.EnableProxy;
host: string;
port: number;
pac: {
host: string;
port: number;
};
certificatePath: string;
commonName: string;
}
Expand Down
6 changes: 3 additions & 3 deletions packages/server/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@dfinity/http-proxy-server",
"version": "0.0.2-alpha",
"version": "0.0.3-alpha",
"description": "Gateway server to enable trustless access to the Internet Computer.",
"main": "built/main.js",
"types": "built/main.d.ts",
Expand Down Expand Up @@ -51,8 +51,8 @@
"dependencies": {
"@dfinity/agent": "^0.15.6",
"@dfinity/candid": "^0.15.6",
"@dfinity/http-proxy-core": "0.0.2-alpha",
"@dfinity/http-proxy-daemon": "0.0.2-alpha",
"@dfinity/http-proxy-core": "0.0.3-alpha",
"@dfinity/http-proxy-daemon": "0.0.3-alpha",
"@dfinity/principal": "^0.15.6",
"@dfinity/response-verification": "^0.2.1",
"http-proxy": "^1.18.1",
Expand Down
Loading

0 comments on commit 0a6c066

Please sign in to comment.