diff --git a/src/host-config.ts b/src/host-config.ts index cde01a1e..b003d64c 100644 --- a/src/host-config.ts +++ b/src/host-config.ts @@ -3,7 +3,12 @@ import _ from 'lodash'; import path from 'path'; import * as applicationManager from './compose/application-manager'; -import { readHostname, setHostname } from './host-config/index'; +import { + readHostname, + setHostname, + readNoProxy, + setNoProxy, +} from './host-config/index'; import * as dbus from './lib/dbus'; import { isENOENT } from './lib/errors'; import { mkdirp, unlinkAll } from './lib/fs-utils'; @@ -30,7 +35,6 @@ const proxyFields = ['type', 'ip', 'port', 'login', 'password']; const proxyBasePath = pathOnBoot('system-proxy'); const redsocksConfPath = path.join(proxyBasePath, 'redsocks.conf'); -const noProxyPath = path.join(proxyBasePath, 'no_proxy'); interface ProxyConfig { [key: string]: string | string[] | number; @@ -83,18 +87,9 @@ async function readProxy(): Promise { } } - try { - const noProxy = await readFromBoot(noProxyPath, 'utf-8') - // Prevent empty newline from being reported as a noProxy address - .then((addrs) => addrs.split('\n').filter((addr) => addr !== '')); - - if (noProxy.length) { - conf.noProxy = noProxy; - } - } catch (e: unknown) { - if (!isENOENT(e)) { - throw e; - } + const noProxy = await readNoProxy(); + if (noProxy.length) { + conf.noProxy = noProxy; } // Convert port to number per API doc spec @@ -121,14 +116,15 @@ function generateRedsocksConfEntries(conf: ProxyConfig): string { async function setProxy(maybeConf: ProxyConfig | null): Promise { if (_.isEmpty(maybeConf)) { - await unlinkAll(redsocksConfPath, noProxyPath); + await unlinkAll(redsocksConfPath); + await setNoProxy([]); } else { // We know that maybeConf is not null due to the _.isEmpty check above, // but the compiler doesn't const conf = maybeConf as ProxyConfig; await mkdirp(proxyBasePath); if (Array.isArray(conf.noProxy)) { - await writeToBoot(noProxyPath, conf.noProxy.join('\n')); + await setNoProxy(conf.noProxy); } let currentConf: ProxyConfig | undefined; diff --git a/src/host-config/index.ts b/src/host-config/index.ts index 7c2c5ca8..19dde7e9 100644 --- a/src/host-config/index.ts +++ b/src/host-config/index.ts @@ -3,6 +3,8 @@ import { promises as fs } from 'fs'; import * as config from '../config'; import { pathOnRoot } from '../lib/host-utils'; +export * from './proxy'; + const hostnamePath = pathOnRoot('/etc/hostname'); export async function readHostname() { diff --git a/src/host-config/proxy.ts b/src/host-config/proxy.ts new file mode 100644 index 00000000..c1ced84c --- /dev/null +++ b/src/host-config/proxy.ts @@ -0,0 +1,36 @@ +import { promises as fs } from 'fs'; +import path from 'path'; + +import { pathOnBoot, readFromBoot } from '../lib/host-utils'; +import { unlinkAll } from '../lib/fs-utils'; +import { isENOENT } from '../lib/errors'; + +const proxyBasePath = pathOnBoot('system-proxy'); +const noProxyPath = path.join(proxyBasePath, 'no_proxy'); + +export async function readNoProxy(): Promise { + try { + const noProxy = await readFromBoot(noProxyPath, 'utf-8') + // Prevent empty newline from being reported as a noProxy address + .then((addrs) => addrs.split('\n').filter((addr) => addr !== '')); + + if (noProxy.length) { + return noProxy; + } else { + return []; + } + } catch (e: unknown) { + if (!isENOENT(e)) { + throw e; + } + return []; + } +} + +export async function setNoProxy(list: string[]) { + if (!list || !Array.isArray(list) || !list.length) { + await unlinkAll(noProxyPath); + } else { + await fs.writeFile(noProxyPath, list.join('\n')); + } +} diff --git a/test/integration/host-config.spec.ts b/test/integration/host-config.spec.ts index be717ea5..9cfd3979 100644 --- a/test/integration/host-config.spec.ts +++ b/test/integration/host-config.spec.ts @@ -98,6 +98,42 @@ describe('host-config', () => { }); }); + describe('noProxy', () => { + it('reads IPs to exclude from proxy', async () => { + expect(await hostConfig.readNoProxy()).to.deep.equal([ + '152.10.30.4', + '253.1.1.0/16', + ]); + }); + + it('sets IPs to exclude from proxy', async () => { + await hostConfig.setNoProxy(['balena.io', '1.1.1.1']); + expect(await fs.readFile(noProxy, 'utf-8')).to.equal( + 'balena.io\n1.1.1.1', + ); + }); + + it('removes no_proxy file if empty or invalid', async () => { + // Set initial no_proxy + await hostConfig.setNoProxy(['2.2.2.2']); + expect(await hostConfig.readNoProxy()).to.deep.equal(['2.2.2.2']); + + // Set to empty array + await hostConfig.setNoProxy([]); + expect(await hostConfig.readNoProxy()).to.deep.equal([]); + expect(await fs.readdir(proxyBase)).to.not.have.members(['no_proxy']); + + // Reset initial no_proxy + await hostConfig.setNoProxy(['2.2.2.2']); + expect(await hostConfig.readNoProxy()).to.deep.equal(['2.2.2.2']); + + // Set to invalid value + await hostConfig.setNoProxy(null as any); + expect(await hostConfig.readNoProxy()).to.deep.equal([]); + expect(await fs.readdir(proxyBase)).to.not.have.members(['no_proxy']); + }); + }); + it('reads proxy configs and hostname', async () => { const { network } = await get(); expect(network).to.have.property('hostname', 'deadbeef');