mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-01-08 06:22:53 +00:00
268 lines
9.2 KiB
TypeScript
268 lines
9.2 KiB
TypeScript
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
/*
|
|
* Copyright (C) 2019 StApps
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as
|
|
* published by the Free Software Foundation, either version 3 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
import {Logger} from '@openstapps/logger';
|
|
import chai from 'chai';
|
|
import {expect} from 'chai';
|
|
import {ContainerInfo} from 'dockerode';
|
|
import {
|
|
sslHardeningParameters,
|
|
protocolHardeningParameters,
|
|
SSLFilePaths,
|
|
configFile,
|
|
} from './../src/common.js';
|
|
import {
|
|
containerMatchesRegex,
|
|
generateUpstreamMap,
|
|
getGatewayOfStAppsBackend,
|
|
getTemplateView,
|
|
generateListener,
|
|
generateMetricsServer,
|
|
getContainers,
|
|
} from '../src/main.js';
|
|
import path from 'path';
|
|
import {mkdirSync, writeFileSync, unlinkSync, rmdirSync} from 'fs';
|
|
import {anyContainerWithExposedPorts} from './containers/any-with-exposed-ports.js';
|
|
import {backendContainerWithExposedPorts} from './containers/backend-with-exposed-ports.js';
|
|
import {swarmBackendContainerWithExposedPorts} from './containers/swarm-backend-with-exposed-ports.js';
|
|
import {fileURLToPath} from 'url';
|
|
import sinon from 'sinon';
|
|
import sinonChai from 'sinon-chai';
|
|
import portScannerModule from '../src/port-scanner.js';
|
|
|
|
process.on('unhandledRejection', async error => {
|
|
await Logger.error(error);
|
|
|
|
process.exit(1);
|
|
});
|
|
|
|
chai.should();
|
|
chai.use(sinonChai);
|
|
|
|
console.log(configFile);
|
|
|
|
describe('main', function () {
|
|
this.timeout(1000);
|
|
this.slow(500);
|
|
|
|
const sandbox = sinon.createSandbox();
|
|
|
|
beforeEach(function () {
|
|
sandbox.restore();
|
|
});
|
|
|
|
it('should check if container does not match any container', function () {
|
|
expect(containerMatchesRegex('anyName', new RegExp('d+'), anyContainerWithExposedPorts)).to.be.equal(
|
|
false,
|
|
);
|
|
});
|
|
|
|
it('should check if container does not match if version is incorrect', function () {
|
|
expect(
|
|
containerMatchesRegex('backend', new RegExp('1\\.4\\.\\d+'), backendContainerWithExposedPorts),
|
|
).to.be.equal(false);
|
|
});
|
|
|
|
it('should check if container matches', function () {
|
|
expect(
|
|
containerMatchesRegex('backend', new RegExp('1\\.0\\.\\d+'), backendContainerWithExposedPorts),
|
|
).to.be.equal(true);
|
|
expect(
|
|
containerMatchesRegex('backend', new RegExp('1\\.0\\.\\d+'), swarmBackendContainerWithExposedPorts),
|
|
).to.be.equal(true);
|
|
});
|
|
|
|
it('should get gateway of any container with exposed ports', async function () {
|
|
expect(await getGatewayOfStAppsBackend(anyContainerWithExposedPorts)).to.be.equal('0.0.0.0:80');
|
|
});
|
|
|
|
it('should get gateway of backend container', async function () {
|
|
const spy = sandbox.stub(console, 'error');
|
|
|
|
const containerWithoutPorts: Partial<ContainerInfo> = {
|
|
Id: 'Foo',
|
|
Ports: [],
|
|
Names: ['/container_name_1'],
|
|
};
|
|
|
|
expect(await getGatewayOfStAppsBackend(containerWithoutPorts as ContainerInfo)).to.be.equal('');
|
|
expect(spy).to.have.been.calledWithMatch('Container /container_name_1 does not advertise any port.');
|
|
});
|
|
|
|
it('should get gateway of backend container without ports', async function () {
|
|
expect(await getGatewayOfStAppsBackend(backendContainerWithExposedPorts)).to.be.equal('127.0.0.1:3000');
|
|
});
|
|
|
|
it('should get gateway of backend container within docker swarm', async function () {
|
|
const backendContainer = swarmBackendContainerWithExposedPorts as any;
|
|
delete backendContainer.Ports[0].IP;
|
|
|
|
const spy = sandbox.stub(portScannerModule, 'isPortFree').resolves(true);
|
|
expect(await getGatewayOfStAppsBackend(backendContainer)).to.be.equal('172.18.0.3:3000');
|
|
expect(spy).to.have.been.called;
|
|
});
|
|
|
|
it('should fail to get gateway of backend container if unreachable', async function () {
|
|
const backendContainer = swarmBackendContainerWithExposedPorts as any;
|
|
delete backendContainer.Ports[0].IP;
|
|
|
|
const spy = sandbox.stub(console, 'error');
|
|
|
|
const scanner = sandbox.stub(portScannerModule, 'isPortFree').resolves(false);
|
|
|
|
expect(await getGatewayOfStAppsBackend(swarmBackendContainerWithExposedPorts)).to.be.equal('');
|
|
expect(scanner.calledOnce).to.be.true;
|
|
expect(spy).to.have.been.calledWithMatch(
|
|
"It's possible your current Docker network setup isn't supported yet.",
|
|
);
|
|
});
|
|
|
|
it('should fail to get gateway of backend container network mode is unsupported', async function () {
|
|
const backendContainer = swarmBackendContainerWithExposedPorts as any;
|
|
delete backendContainer.Ports[0].IP;
|
|
delete backendContainer.Ports[0].PublicPort;
|
|
delete backendContainer.Ports[0].PrivatePort;
|
|
|
|
const spy = sandbox.stub(console, 'error');
|
|
|
|
expect(await getGatewayOfStAppsBackend(swarmBackendContainerWithExposedPorts)).to.be.equal('');
|
|
expect(spy).to.have.been.calledWithMatch(
|
|
"It's possible your current Docker network setup isn't supported yet.",
|
|
);
|
|
});
|
|
|
|
it('should upstream map calls logger error when no matching container is found', async function () {
|
|
const spy = sandbox.stub(console, 'warn');
|
|
|
|
expect(await generateUpstreamMap(['0\\.8\\.\\d+'], ['1\\.1\\.\\d+'], [backendContainerWithExposedPorts]))
|
|
.to.be.equal(`map $http_x_stapps_version $proxyurl {
|
|
default unsupported;
|
|
"~0\\.8\\.\\d+" unavailable;
|
|
"~1\\.1\\.\\d+" outdated;
|
|
}
|
|
`);
|
|
|
|
expect(spy).to.have.been.calledWithMatch('No backend for version');
|
|
});
|
|
|
|
it('should upstream map with one active version and no outdated ones', async function () {
|
|
expect(await generateUpstreamMap(['1\\.0\\.\\d+'], ['0\\.8\\.\\d+'], [backendContainerWithExposedPorts]))
|
|
.to.be.equal(`map $http_x_stapps_version $proxyurl {
|
|
default unsupported;
|
|
"~1\\.0\\.\\d+" 1__0___d_;
|
|
"~0\\.8\\.\\d+" outdated;
|
|
}
|
|
upstream 1__0___d_ {
|
|
server 127.0.0.1:3000;
|
|
}
|
|
`);
|
|
});
|
|
|
|
it('should get containers', async function () {
|
|
try {
|
|
await getContainers();
|
|
return false;
|
|
} catch (error) {
|
|
if ((error as Error).message.startsWith('No')) {
|
|
// Result, if docker is installed
|
|
expect((error as Error).message).to.equal(`No running docker containers found.
|
|
|
|
Please check if docker is running and Node.js can access the docker socket (/var/run/docker.sock)`);
|
|
} else {
|
|
// Result, if docker is not installed
|
|
expect([
|
|
new Error(`connect ENOENT /var/run/docker.sock`).message,
|
|
new Error('connect EACCES /var/run/docker.sock').message,
|
|
]).to.include((error as Error).message);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
});
|
|
|
|
it('should get template view', async function () {
|
|
try {
|
|
const containersWithSameVersion = [backendContainerWithExposedPorts, backendContainerWithExposedPorts];
|
|
await getTemplateView(containersWithSameVersion);
|
|
return false;
|
|
} catch (error) {
|
|
expect((error as Error).message).to.equal(`Multiple backends for one version found.`);
|
|
}
|
|
|
|
return true;
|
|
});
|
|
|
|
it('should include metrics config', async function () {
|
|
expect(await generateMetricsServer('test', true)).length.to.be.greaterThan(1);
|
|
});
|
|
|
|
it('should omit metrics config', async function () {
|
|
expect(await generateMetricsServer('test', false)).to.equal('');
|
|
});
|
|
|
|
it('should create listener faulty config', async function () {
|
|
expect(
|
|
generateListener({
|
|
certificate: 'faultyTest',
|
|
certificateChain: 'faultyTest',
|
|
certificateKey: 'faultyTest',
|
|
dhparam: 'faultyTest',
|
|
}),
|
|
).to.equal(`listen 80 default_server;
|
|
|
|
${protocolHardeningParameters}
|
|
`);
|
|
});
|
|
|
|
it('should create listener correct config', async function () {
|
|
const testCertDirectory = path.resolve(path.dirname(fileURLToPath(import.meta.url)), 'certs');
|
|
mkdirSync(testCertDirectory);
|
|
|
|
const certificateFile = path.resolve(testCertDirectory, 'ssl.crt');
|
|
const certificateKeyFile = path.resolve(testCertDirectory, 'ssl.key');
|
|
const certificateChainFile = path.resolve(testCertDirectory, 'chain.crt');
|
|
const dhparamFile = path.resolve(testCertDirectory, 'dhparam.pem');
|
|
|
|
writeFileSync(certificateFile, 'Test');
|
|
writeFileSync(certificateKeyFile, 'Test');
|
|
writeFileSync(certificateChainFile, 'Test');
|
|
writeFileSync(dhparamFile, 'Test');
|
|
|
|
const sslFilePaths: SSLFilePaths = {
|
|
certificate: certificateFile,
|
|
certificateKey: certificateKeyFile,
|
|
certificateChain: certificateChainFile,
|
|
dhparam: dhparamFile,
|
|
};
|
|
|
|
expect(generateListener(sslFilePaths)).to.equal(` listen 443 ssl default_server;
|
|
ssl_certificate ${sslFilePaths.certificate};
|
|
ssl_certificate_key ${sslFilePaths.certificateKey};
|
|
ssl_trusted_certificate ${sslFilePaths.certificateChain};
|
|
ssl_dhparam ${sslFilePaths.dhparam};
|
|
${sslHardeningParameters}
|
|
|
|
${protocolHardeningParameters}
|
|
`);
|
|
|
|
unlinkSync(certificateFile);
|
|
unlinkSync(certificateKeyFile);
|
|
unlinkSync(certificateChainFile);
|
|
unlinkSync(dhparamFile);
|
|
rmdirSync(testCertDirectory);
|
|
});
|
|
});
|