Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add bindAddress command line option to allow local-ssl-proxy to play nice with WSL #124

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,16 @@ Start your web server on the target port (`9000` in the example) and navigate to

Using a dynamic DNS provider such as [noip](http://www.noip.com/personal/) or [DynDNS](http://dyn.com/dns/) or a static IP (if you have one) you can open a port in your firewall to allow external sites to call into your web server. This is great for developing applications using [OAuth](http://oauth.net/) without having to deploy externally.

## Specific bind address

To pass a specific bind address to listen to, run:

```sh
local-ssl-proxy --source 9001 --target 9000 --bindAddress '0.0.0.0'
```

This is often necessary when running local-ssl-proxy under WSL, if you are then using a browser on Windows as opposed to one installed directly to WSL.

## Advanced

You can also pass a configuration file, this helps share setups with team members. These can contain multiple proxies that `local-ssl-proxy` will open concurrently.
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@
},
"devDependencies": {
"@tsconfig/node-lts-strictest": "^18.12.1",
"@types/http-proxy": "^1.17.10",
"@types/http-proxy": "1.17.15",
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

v1.17.10 didn't include the hostname parameter on the .listen method, hence the update.

"@types/node": "^18.15.0",
"typescript": "^4.9.5",
"vitest": "^0.29.2"
},
"packageManager": "[email protected]"
}
}
16 changes: 9 additions & 7 deletions src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,22 @@ const program = createCommand(name)
resolve(__dirname, '..', 'resources', 'localhost.pem')
)
.option('-k, --key <key>', 'path to SSL key', exists, resolve(__dirname, '..', 'resources', 'localhost-key.pem'))
.option('-o, --config <config>', 'path to configuration file', (path) => require(absolutePath(path)));
.option('-o, --config <config>', 'path to configuration file', (path) => require(absolutePath(path)))
.option('-b, --bindAddress <bindAddress>', 'bind address for the server');

type Proxy = {
export type Proxy = {
hostname: string;
source: number;
target: number;
cert: string;
key: string;
};

type Config = { config: Record<string, Proxy> };
type ParsedArguments = Proxy | Config;
export type Config = { config: Record<string, Proxy> };
type BindAddress = { bindAddress: string };
export type ParsedArguments = (Proxy | Config) & BindAddress;

function isConfig(args: unknown): args is Config {
export function isConfig(args: unknown): args is Config {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exported so it can be used in unit-tests

return Boolean(args && typeof args === 'object' && 'config' in args);
}

Expand All @@ -58,9 +60,9 @@ export function isProxy(input: unknown): input is Proxy {
);
}

export function parse(args?: string[]): Proxy | Record<string, Proxy> {
export function parse(args?: string[]): ParsedArguments {
const proxy: ParsedArguments =
args === undefined ? program.parse().opts() : program.parse(args, { from: 'user' }).opts();

return isConfig(proxy) ? proxy.config : proxy;
return isConfig(proxy) ? {config: proxy.config, bindAddress: proxy.bindAddress} : {...proxy, bindAddress: proxy.bindAddress};
}
5 changes: 3 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ import { isProxy, parse } from './lib';

const parsed = parse();

const config = isProxy(parsed) ? { proxy: parsed } : parsed;
const config = isProxy(parsed) ? { proxy: parsed } : parsed.config;

for (const name of Object.keys(config)) {
const { hostname, target, key, cert, source } = config[name]!;
const bindAddress = parsed.bindAddress || undefined;

proxy
.createServer({
Expand All @@ -28,7 +29,7 @@ for (const name of Object.keys(config)) {
.on('error', (e: any) => {
console.error(red('Request failed to ' + name + ': ' + bold(e.code)));
})
.listen(source);
.listen(source, bindAddress);

console.log(
green(
Expand Down
62 changes: 49 additions & 13 deletions test/lib.test.ts
Original file line number Diff line number Diff line change
@@ -1,59 +1,95 @@
import { test, expect } from 'vitest';
import { parse } from '../src/lib';
import { isProxy, isConfig, parse, ParsedArguments, Proxy, Config } from '../src/lib';


function expectProxy(result: ParsedArguments): Proxy {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type guard functions to keep TypeScript happy on the tests.
After changing ParsedArguments to export type ParsedArguments = (Proxy | Config) & BindAddress; to include bindAddress, TypeScript was unable to infer the correct type from the return of the call to parse

if (isProxy(result)) {
return result;
}

throw new Error('Returned object is not of type Proxy');
}

function expectConfig(result: ParsedArguments): Config {
if (isConfig(result)) {
return result;
}

throw new Error('Returned object is not of type Proxy');
}

test('cert (default)', () => {
const { cert } = parse([]);
const result = parse([]);
const { cert } = expectProxy(result);
expect(cert).toEqual(expect.any(String));
expect(result.bindAddress).toBeUndefined();
});

test('cert', () => {
const { cert } = parse(['--cert', require.resolve('../resources/localhost.pem')]);
const result = parse(['--cert', require.resolve('../resources/localhost.pem')]);
const { cert } = expectProxy(result);
expect(cert).toEqual(expect.any(String));
});

test('key (default)', () => {
const { key } = parse([]);
const result = parse([]);
const { key } = expectProxy(result);
expect(key).toEqual(expect.any(String));
});

test('key', () => {
const { key } = parse(['--key', require.resolve('../resources/localhost-key.pem')]);
const result = parse(['--key', require.resolve('../resources/localhost-key.pem')]);
const { key } = expectProxy(result);
expect(key).toEqual(expect.any(String));
});

test('hostname (default)', () => {
const { hostname } = parse([]);
const result = parse([]);
const { hostname } = expectProxy(result);
expect(hostname).toBe('localhost');
});

test('hostname', () => {
const { hostname } = parse(['--hostname', '127.0.0.1']);
const result = parse(['--hostname', '127.0.0.1']);
const { hostname } = expectProxy(result);
expect(hostname).toBe('127.0.0.1');
});

test('source (default)', () => {
const { source } = parse([]);
const result = parse([]);
const { source } = expectProxy(result);
expect(source).toBe(9001);
});

test('source', () => {
const { source } = parse(['--source', '5001']);
const result = parse(['--source', '5001']);
const { source } = expectProxy(result);
expect(source).toBe(5001);
});

test('target (default)', () => {
const { target } = parse([]);
const result = parse([]);
const { target } = expectProxy(result);
expect(target).toBe(9000);
});

test('target', () => {
const { target } = parse(['--target', '5000']);
const result = parse(['--target', '5000']);
const { target } = expectProxy(result);
expect(target).toBe(5000);
});

test('bindAddress', () => {
const { bindAddress } = parse(['--bindAddress', '0.0.0.0']);
expect(bindAddress).toBe('0.0.0.0');
});

test('config', () => {
const config = parse(['--config', require.resolve('./test-config.json')]);
expect(config).toMatchInlineSnapshot(`
const result = parse(['--config', require.resolve('./test-config.json'),'--bindAddress', '0.0.0.0']);
expect(result.bindAddress).toBe('0.0.0.0');

const parsed = expectConfig(result);
expect(parsed.config).toMatchInlineSnapshot(`
{
"Proxy 1": {
"cert": "/etc/apache2/server.pem",
Expand Down
10 changes: 5 additions & 5 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -216,12 +216,12 @@ __metadata:
languageName: node
linkType: hard

"@types/http-proxy@npm:^1.17.10":
version: 1.17.10
resolution: "@types/http-proxy@npm:1.17.10"
"@types/http-proxy@npm:latest":
version: 1.17.15
resolution: "@types/http-proxy@npm:1.17.15"
dependencies:
"@types/node": "*"
checksum: 8fabee5d01715e338f426715325121d6c4b7a9694dee716ab61c874e0aaccee9a0fff7ccc3c9d7e37a8feeaab7c783c17aaa9943efbc8849c5e79ecd7eaf02ab
checksum: d96eaf4e22232b587b46256b89c20525c453216684481015cf50fb385b0b319b883749ccb77dee9af57d107e8440cdacd56f4234f65176d317e9777077ff5bf3
languageName: node
linkType: hard

Expand Down Expand Up @@ -976,7 +976,7 @@ __metadata:
resolution: "local-ssl-proxy@workspace:."
dependencies:
"@tsconfig/node-lts-strictest": ^18.12.1
"@types/http-proxy": ^1.17.10
"@types/http-proxy": latest
"@types/node": ^18.15.0
ansi-colors: ^4.1.3
commander: ^10.0.0
Expand Down