mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2025-06-21 08:40:05 +00:00
Stringify dns subsection of redsocks input config to dnsu2t
Signed-off-by: Christina Ying Wang <christina@balena.io>
This commit is contained in:
@ -3,7 +3,7 @@ import path from 'path';
|
|||||||
import { isRight } from 'fp-ts/lib/Either';
|
import { isRight } from 'fp-ts/lib/Either';
|
||||||
import Reporter from 'io-ts-reporters';
|
import Reporter from 'io-ts-reporters';
|
||||||
|
|
||||||
import type { RedsocksConfig, HostProxyConfig } from './types';
|
import type { RedsocksConfig, HostProxyConfig, DnsInput } from './types';
|
||||||
import { ProxyConfig } from './types';
|
import { ProxyConfig } from './types';
|
||||||
import { pathOnBoot, readFromBoot, writeToBoot } from '../lib/host-utils';
|
import { pathOnBoot, readFromBoot, writeToBoot } from '../lib/host-utils';
|
||||||
import { unlinkAll, mkdirp } from '../lib/fs-utils';
|
import { unlinkAll, mkdirp } from '../lib/fs-utils';
|
||||||
@ -17,6 +17,9 @@ const redsocksConfPath = path.join(proxyBasePath, 'redsocks.conf');
|
|||||||
|
|
||||||
const disallowedProxyFields = ['local_ip', 'local_port'];
|
const disallowedProxyFields = ['local_ip', 'local_port'];
|
||||||
|
|
||||||
|
const DEFAULT_REMOTE_IP = '8.8.8.8';
|
||||||
|
const DEFAULT_REMOTE_PORT = 53;
|
||||||
|
|
||||||
const isAuthField = (field: string): boolean =>
|
const isAuthField = (field: string): boolean =>
|
||||||
['login', 'password'].includes(field);
|
['login', 'password'].includes(field);
|
||||||
|
|
||||||
@ -38,18 +41,45 @@ export class RedsocksConf {
|
|||||||
public static stringify(config: RedsocksConfig): string {
|
public static stringify(config: RedsocksConfig): string {
|
||||||
const blocks: string[] = [];
|
const blocks: string[] = [];
|
||||||
|
|
||||||
if (config.redsocks && Object.keys(config.redsocks).length > 0) {
|
// If no redsocks config is provided or dns is the only config, return empty string.
|
||||||
blocks.push(RedsocksConf.stringifyBlock('base', baseBlock));
|
// A dns-only config is not valid as it depends on proxy being configured to function.
|
||||||
blocks.push(
|
if (
|
||||||
RedsocksConf.stringifyBlock('redsocks', {
|
!config.redsocks ||
|
||||||
...config.redsocks,
|
!Object.keys(config.redsocks).length ||
|
||||||
local_ip: '127.0.0.1',
|
(Object.keys(config.redsocks).length === 1 &&
|
||||||
local_port: 12345,
|
Object.hasOwn(config.redsocks, 'dns'))
|
||||||
}),
|
) {
|
||||||
);
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
return blocks.length ? blocks.join('\n') : '';
|
// Add base block
|
||||||
|
blocks.push(RedsocksConf.stringifyBlock('base', baseBlock));
|
||||||
|
|
||||||
|
const { dns, ...redsocks } = config.redsocks;
|
||||||
|
// Add redsocks block
|
||||||
|
blocks.push(
|
||||||
|
RedsocksConf.stringifyBlock('redsocks', {
|
||||||
|
...redsocks,
|
||||||
|
local_ip: '127.0.0.1',
|
||||||
|
local_port: 12345,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add optional dnsu2t block if input dns config is true or a string
|
||||||
|
if (dns != null) {
|
||||||
|
const dnsu2t = dnsToDnsu2t(dns);
|
||||||
|
if (dnsu2t) {
|
||||||
|
blocks.push(
|
||||||
|
RedsocksConf.stringifyBlock('dnsu2t', {
|
||||||
|
...dnsu2t,
|
||||||
|
local_ip: '127.0.0.1',
|
||||||
|
local_port: 53,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return blocks.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
public static parse(rawConf: string): RedsocksConfig {
|
public static parse(rawConf: string): RedsocksConfig {
|
||||||
@ -138,6 +168,25 @@ export class RedsocksConf {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function dnsToDnsu2t(
|
||||||
|
dns: DnsInput,
|
||||||
|
): { remote_ip: string; remote_port: number } | null {
|
||||||
|
const dnsu2t = {
|
||||||
|
remote_ip: DEFAULT_REMOTE_IP,
|
||||||
|
remote_port: DEFAULT_REMOTE_PORT,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (typeof dns === 'boolean') {
|
||||||
|
return dns ? dnsu2t : null;
|
||||||
|
} else {
|
||||||
|
// Convert dns string to config object
|
||||||
|
const [ip, port] = dns.split(':');
|
||||||
|
dnsu2t.remote_ip = ip;
|
||||||
|
dnsu2t.remote_port = parseInt(port, 10);
|
||||||
|
return dnsu2t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function readProxy(): Promise<HostProxyConfig | undefined> {
|
export async function readProxy(): Promise<HostProxyConfig | undefined> {
|
||||||
// Get and parse redsocks.conf
|
// Get and parse redsocks.conf
|
||||||
let rawConf: string | undefined;
|
let rawConf: string | undefined;
|
||||||
|
@ -1,5 +1,15 @@
|
|||||||
import * as t from 'io-ts';
|
import * as t from 'io-ts';
|
||||||
import { NumericIdentifier } from '../types';
|
import { NumericIdentifier, shortStringWithRegex } from '../types';
|
||||||
|
|
||||||
|
const AddressString = shortStringWithRegex(
|
||||||
|
'AddressString',
|
||||||
|
/^.+:[0-9]+$/,
|
||||||
|
"must be a string in the format 'ADDRESS:PORT'",
|
||||||
|
);
|
||||||
|
type AddressString = t.TypeOf<typeof AddressString>;
|
||||||
|
|
||||||
|
export const DnsInput = t.union([AddressString, t.boolean]);
|
||||||
|
export type DnsInput = t.TypeOf<typeof DnsInput>;
|
||||||
|
|
||||||
export const ProxyConfig = t.intersection([
|
export const ProxyConfig = t.intersection([
|
||||||
t.type({
|
t.type({
|
||||||
@ -12,10 +22,11 @@ export const ProxyConfig = t.intersection([
|
|||||||
ip: t.string,
|
ip: t.string,
|
||||||
port: NumericIdentifier,
|
port: NumericIdentifier,
|
||||||
}),
|
}),
|
||||||
// login & password are optional fields
|
// login, password, and dns are optional fields
|
||||||
t.partial({
|
t.partial({
|
||||||
login: t.string,
|
login: t.string,
|
||||||
password: t.string,
|
password: t.string,
|
||||||
|
dns: DnsInput,
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
export type ProxyConfig = t.TypeOf<typeof ProxyConfig>;
|
export type ProxyConfig = t.TypeOf<typeof ProxyConfig>;
|
||||||
|
@ -93,7 +93,11 @@ const VAR_NAME_REGEX = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|||||||
*/
|
*/
|
||||||
const CONFIG_VAR_NAME_REGEX = /^[a-zA-Z_][a-zA-Z0-9_:]*$/;
|
const CONFIG_VAR_NAME_REGEX = /^[a-zA-Z_][a-zA-Z0-9_:]*$/;
|
||||||
|
|
||||||
const shortStringWithRegex = (name: string, regex: RegExp, message: string) =>
|
export const shortStringWithRegex = (
|
||||||
|
name: string,
|
||||||
|
regex: RegExp,
|
||||||
|
message: string,
|
||||||
|
) =>
|
||||||
new t.Type<string, string>(
|
new t.Type<string, string>(
|
||||||
name,
|
name,
|
||||||
(s: unknown): s is string => ShortString.is(s) && regex.test(s),
|
(s: unknown): s is string => ShortString.is(s) && regex.test(s),
|
||||||
|
@ -4,6 +4,7 @@ import type { SinonStub } from 'sinon';
|
|||||||
|
|
||||||
import * as hostConfig from '~/src/host-config';
|
import * as hostConfig from '~/src/host-config';
|
||||||
import { RedsocksConf } from '~/src/host-config/proxy';
|
import { RedsocksConf } from '~/src/host-config/proxy';
|
||||||
|
import { DnsInput } from '~/src/host-config/types';
|
||||||
import type { RedsocksConfig, ProxyConfig } from '~/src/host-config/types';
|
import type { RedsocksConfig, ProxyConfig } from '~/src/host-config/types';
|
||||||
import log from '~/lib/supervisor-console';
|
import log from '~/lib/supervisor-console';
|
||||||
|
|
||||||
@ -124,6 +125,135 @@ describe('RedsocksConf', () => {
|
|||||||
const confStr = RedsocksConf.stringify(conf);
|
const confStr = RedsocksConf.stringify(conf);
|
||||||
expect(confStr).to.equal('');
|
expect(confStr).to.equal('');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('stringifies dns to separate dnsu2t block', () => {
|
||||||
|
const conf: RedsocksConfig = {
|
||||||
|
redsocks: {
|
||||||
|
type: 'socks5',
|
||||||
|
ip: 'example.org',
|
||||||
|
port: 1080,
|
||||||
|
login: '"foo"',
|
||||||
|
password: '"bar"',
|
||||||
|
dns: '1.1.1.1:54',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const confStr = RedsocksConf.stringify(conf);
|
||||||
|
expect(confStr).to.equal(
|
||||||
|
stripIndent`
|
||||||
|
base {
|
||||||
|
log_debug = off;
|
||||||
|
log_info = on;
|
||||||
|
log = stderr;
|
||||||
|
daemon = off;
|
||||||
|
redirector = iptables;
|
||||||
|
}
|
||||||
|
|
||||||
|
redsocks {
|
||||||
|
type = socks5;
|
||||||
|
ip = example.org;
|
||||||
|
port = 1080;
|
||||||
|
login = "foo";
|
||||||
|
password = "bar";
|
||||||
|
local_ip = 127.0.0.1;
|
||||||
|
local_port = 12345;
|
||||||
|
}
|
||||||
|
|
||||||
|
dnsu2t {
|
||||||
|
remote_ip = 1.1.1.1;
|
||||||
|
remote_port = 54;
|
||||||
|
local_ip = 127.0.0.1;
|
||||||
|
local_port = 53;
|
||||||
|
}
|
||||||
|
` + '\n',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('stringifies dns: true to default dnsu2t config', () => {
|
||||||
|
const conf: RedsocksConfig = {
|
||||||
|
redsocks: {
|
||||||
|
type: 'socks5',
|
||||||
|
ip: 'example.org',
|
||||||
|
port: 1080,
|
||||||
|
login: '"foo"',
|
||||||
|
password: '"bar"',
|
||||||
|
dns: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const confStr = RedsocksConf.stringify(conf);
|
||||||
|
expect(confStr).to.equal(
|
||||||
|
stripIndent`
|
||||||
|
base {
|
||||||
|
log_debug = off;
|
||||||
|
log_info = on;
|
||||||
|
log = stderr;
|
||||||
|
daemon = off;
|
||||||
|
redirector = iptables;
|
||||||
|
}
|
||||||
|
|
||||||
|
redsocks {
|
||||||
|
type = socks5;
|
||||||
|
ip = example.org;
|
||||||
|
port = 1080;
|
||||||
|
login = "foo";
|
||||||
|
password = "bar";
|
||||||
|
local_ip = 127.0.0.1;
|
||||||
|
local_port = 12345;
|
||||||
|
}
|
||||||
|
|
||||||
|
dnsu2t {
|
||||||
|
remote_ip = 8.8.8.8;
|
||||||
|
remote_port = 53;
|
||||||
|
local_ip = 127.0.0.1;
|
||||||
|
local_port = 53;
|
||||||
|
}
|
||||||
|
` + '\n',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not include dnsu2t config if dns: false', () => {
|
||||||
|
const conf: RedsocksConfig = {
|
||||||
|
redsocks: {
|
||||||
|
type: 'socks5',
|
||||||
|
ip: 'example.org',
|
||||||
|
port: 1080,
|
||||||
|
login: '"foo"',
|
||||||
|
password: '"bar"',
|
||||||
|
dns: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const confStr = RedsocksConf.stringify(conf);
|
||||||
|
expect(confStr).to.equal(
|
||||||
|
stripIndent`
|
||||||
|
base {
|
||||||
|
log_debug = off;
|
||||||
|
log_info = on;
|
||||||
|
log = stderr;
|
||||||
|
daemon = off;
|
||||||
|
redirector = iptables;
|
||||||
|
}
|
||||||
|
|
||||||
|
redsocks {
|
||||||
|
type = socks5;
|
||||||
|
ip = example.org;
|
||||||
|
port = 1080;
|
||||||
|
login = "foo";
|
||||||
|
password = "bar";
|
||||||
|
local_ip = 127.0.0.1;
|
||||||
|
local_port = 12345;
|
||||||
|
}
|
||||||
|
` + '\n',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not stringify dnsu2t if no other fields in redsocks config', () => {
|
||||||
|
const conf = {
|
||||||
|
redsocks: {
|
||||||
|
dns: '3.3.3.3:52',
|
||||||
|
},
|
||||||
|
} as RedsocksConfig;
|
||||||
|
const confStr = RedsocksConf.stringify(conf);
|
||||||
|
expect(confStr).to.equal('');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('parse', () => {
|
describe('parse', () => {
|
||||||
@ -573,4 +703,28 @@ describe('src/host-config', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('types', () => {
|
||||||
|
describe('DnsInput', () => {
|
||||||
|
it('decodes to DnsInput boolean', () => {
|
||||||
|
expect(DnsInput.is(true)).to.be.true;
|
||||||
|
expect(DnsInput.is(false)).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('decodes to DnsInput from string in the format "ADDRESS:PORT"', () => {
|
||||||
|
expect(DnsInput.is('1.2.3.4:53')).to.be.true;
|
||||||
|
expect(DnsInput.is('example.com:53')).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not decode to DnsInput from invalid string', () => {
|
||||||
|
expect(DnsInput.is('')).to.be.false;
|
||||||
|
expect(DnsInput.is(':')).to.be.false;
|
||||||
|
expect(DnsInput.is('1.2.3.4:')).to.be.false;
|
||||||
|
expect(DnsInput.is(':53')).to.be.false;
|
||||||
|
expect(DnsInput.is('example.com:')).to.be.false;
|
||||||
|
expect(DnsInput.is('1.2.3.4')).to.be.false;
|
||||||
|
expect(DnsInput.is('example.com')).to.be.false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
Reference in New Issue
Block a user