Add RedsocksConf.stringify method

`stringify` takes a RedsocksConfig, an internal object
representation of the redsocks.conf file, and transforms
it into a valid string that can be written to redsocks.conf.

Signed-off-by: Christina Ying Wang <christina@balena.io>
This commit is contained in:
Christina Ying Wang 2024-05-01 01:55:30 -07:00
parent 1e224be0cd
commit 9c6681bb23
3 changed files with 157 additions and 3 deletions

View File

@ -4,6 +4,7 @@ import * as config from '../config';
import { pathOnRoot } from '../lib/host-utils';
export * from './proxy';
export * from './types';
const hostnamePath = pathOnRoot('/etc/hostname');

View File

@ -24,10 +24,31 @@ const isAuthField = (field: string): boolean =>
const blockRegexFor = (blockLabel: string) =>
new RegExp(`${blockLabel}\\s?{([\\s\\S]+?)}(?=\\s|$)`);
const baseBlock = {
log_debug: 'off',
log_info: 'on',
log: 'stderr',
daemon: 'off',
redirector: 'iptables',
};
export class RedsocksConf {
// public static stringify(_config: RedsocksConfig): string {
// return 'TODO';
// }
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,
}),
);
}
return blocks.length ? blocks.join('\n') : '';
}
public static parse(rawConf: string): RedsocksConfig {
const conf: RedsocksConfig = {};
@ -63,6 +84,20 @@ export class RedsocksConf {
}
}
private static stringifyBlock(
label: string,
block: Record<string, any>,
): string {
const lines = Object.entries(block).map(([key, value]) => {
if (isAuthField(key)) {
// Add double quotes around login and password fields
value = `${value.startsWith('"') ? '' : '"'}${value}${value.endsWith('"') ? '' : '"'}`;
}
return `\t${key} = ${value};`;
});
return `${label} {\n${lines.join('\n')}\n}\n`;
}
/**
* Given the raw contents of a block redsocks.conf file,
* extract to a key-value object.

View File

@ -6,6 +6,124 @@ import * as hostConfig from '~/src/host-config/index';
import log from '~/lib/supervisor-console';
describe('RedsocksConf', () => {
describe('stringify', () => {
it('stringifies RedsocksConfig into config string', () => {
const conf: hostConfig.RedsocksConfig = {
redsocks: {
type: 'socks5',
ip: 'example.org',
port: 1080,
login: '"foo"',
password: '"bar"',
},
};
const confStr = hostConfig.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('adds double quotes to auth fields if not exists', () => {
const conf: hostConfig.RedsocksConfig = {
redsocks: {
type: 'socks5',
ip: 'example.org',
port: 1080,
login: 'foo',
password: 'bar',
},
};
const confStr = hostConfig.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('accepts port field of type string', () => {
const conf = {
redsocks: {
type: 'socks5',
ip: 'example.org',
port: '1080',
login: 'foo',
password: 'bar',
},
} as unknown as hostConfig.RedsocksConfig;
const confStr = hostConfig.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('stringifies to empty string when provided empty RedsocksConfig', () => {
const conf: hostConfig.RedsocksConfig = {};
const confStr = hostConfig.RedsocksConf.stringify(conf);
expect(confStr).to.equal('');
});
it('stringifies to empty string when provided empty redsocks block', () => {
const conf: hostConfig.RedsocksConfig = {
redsocks: {} as hostConfig.ProxyConfig,
};
const confStr = hostConfig.RedsocksConf.stringify(conf);
expect(confStr).to.equal('');
});
});
describe('parse', () => {
it('parses config string into RedsocksConfig', () => {
const redsocksConfStr = stripIndent`