mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2025-02-20 17:52:51 +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:
parent
e724f60beb
commit
b775f8f14d
@ -3,7 +3,7 @@ import path from 'path';
|
||||
import { isRight } from 'fp-ts/lib/Either';
|
||||
import Reporter from 'io-ts-reporters';
|
||||
|
||||
import type { RedsocksConfig, HostProxyConfig } from './types';
|
||||
import type { RedsocksConfig, HostProxyConfig, DnsInput } from './types';
|
||||
import { ProxyConfig } from './types';
|
||||
import { pathOnBoot, readFromBoot, writeToBoot } from '../lib/host-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 DEFAULT_REMOTE_IP = '8.8.8.8';
|
||||
const DEFAULT_REMOTE_PORT = 53;
|
||||
|
||||
const isAuthField = (field: string): boolean =>
|
||||
['login', 'password'].includes(field);
|
||||
|
||||
@ -38,18 +41,45 @@ export class RedsocksConf {
|
||||
public static stringify(config: RedsocksConfig): string {
|
||||
const blocks: string[] = [];
|
||||
|
||||
if (config.redsocks && Object.keys(config.redsocks).length > 0) {
|
||||
blocks.push(RedsocksConf.stringifyBlock('base', baseBlock));
|
||||
blocks.push(
|
||||
RedsocksConf.stringifyBlock('redsocks', {
|
||||
...config.redsocks,
|
||||
local_ip: '127.0.0.1',
|
||||
local_port: 12345,
|
||||
}),
|
||||
);
|
||||
// If no redsocks config is provided or dns is the only config, return empty string.
|
||||
// A dns-only config is not valid as it depends on proxy being configured to function.
|
||||
if (
|
||||
!config.redsocks ||
|
||||
!Object.keys(config.redsocks).length ||
|
||||
(Object.keys(config.redsocks).length === 1 &&
|
||||
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 {
|
||||
@ -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> {
|
||||
// Get and parse redsocks.conf
|
||||
let rawConf: string | undefined;
|
||||
|
@ -1,5 +1,15 @@
|
||||
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([
|
||||
t.type({
|
||||
@ -12,10 +22,11 @@ export const ProxyConfig = t.intersection([
|
||||
ip: t.string,
|
||||
port: NumericIdentifier,
|
||||
}),
|
||||
// login & password are optional fields
|
||||
// login, password, and dns are optional fields
|
||||
t.partial({
|
||||
login: t.string,
|
||||
password: t.string,
|
||||
dns: DnsInput,
|
||||
}),
|
||||
]);
|
||||
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 shortStringWithRegex = (name: string, regex: RegExp, message: string) =>
|
||||
export const shortStringWithRegex = (
|
||||
name: string,
|
||||
regex: RegExp,
|
||||
message: string,
|
||||
) =>
|
||||
new t.Type<string, string>(
|
||||
name,
|
||||
(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 { RedsocksConf } from '~/src/host-config/proxy';
|
||||
import { DnsInput } from '~/src/host-config/types';
|
||||
import type { RedsocksConfig, ProxyConfig } from '~/src/host-config/types';
|
||||
import log from '~/lib/supervisor-console';
|
||||
|
||||
@ -124,6 +125,135 @@ describe('RedsocksConf', () => {
|
||||
const confStr = RedsocksConf.stringify(conf);
|
||||
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', () => {
|
||||
@ -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;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user