mostly because code is being autogenerated by all the AI stuff using this prefix. it's also used in the stack trace.
163 lines
14 KiB
JavaScript
163 lines
14 KiB
JavaScript
/* global it:false */
|
|
/* global describe:false */
|
|
/* global before:false */
|
|
/* global after:false */
|
|
|
|
'use strict';
|
|
|
|
const BoxError = require('../boxerror.js'),
|
|
common = require('./common.js'),
|
|
domains = require('../domains.js'),
|
|
expect = require('expect.js'),
|
|
fs = require('node:fs'),
|
|
paths = require('../paths.js'),
|
|
reverseProxy = require('../reverseproxy.js'),
|
|
safe = require('safetydance');
|
|
|
|
describe('Reverse Proxy', function () {
|
|
const { setup, cleanup, domain, auditSource, app } = common;
|
|
const domainCopy = Object.assign({}, domain);
|
|
|
|
before(setup);
|
|
after(cleanup);
|
|
|
|
describe('validateCertificate', function () {
|
|
const foobarDomain = 'foobar.com';
|
|
const amazingDomain = 'amazing.com';
|
|
/*
|
|
Generate these with:
|
|
openssl genrsa -out server.key 512
|
|
openssl req -new -key server.key -out server.csr -subj "/C=DE/ST=Berlin/L=Berlin/O=Nebulon/OU=CTO/CN=baz.foobar.com"
|
|
openssl x509 -req -days 3650 -in server.csr -signkey server.key -out server.crt
|
|
*/
|
|
|
|
// foobar.com
|
|
const validCert0 = '-----BEGIN CERTIFICATE-----\nMIIBxTCCAW8CFBVWRFizZeUIdp94/l9Qx/+7UM4GMA0GCSqGSIb3DQEBCwUAMGQx\nCzAJBgNVBAYTAkRFMQ8wDQYDVQQIDAZCZXJsaW4xDzANBgNVBAcMBkJlcmxpbjEQ\nMA4GA1UECgwHTmVidWxvbjEMMAoGA1UECwwDQ1RPMRMwEQYDVQQDDApmb29iYXIu\nY29tMB4XDTIwMTEyMjAxNTI0M1oXDTMwMTEyMDAxNTI0M1owZDELMAkGA1UEBhMC\nREUxDzANBgNVBAgMBkJlcmxpbjEPMA0GA1UEBwwGQmVybGluMRAwDgYDVQQKDAdO\nZWJ1bG9uMQwwCgYDVQQLDANDVE8xEzARBgNVBAMMCmZvb2Jhci5jb20wXDANBgkq\nhkiG9w0BAQEFAANLADBIAkEA++BvW/oDsaM57d4Q4GQjkUzjB0/glKLj4P0Y8InS\nhLHOud9Uxz7dIcqHm9x9MOtqTRhtiHNoFLZLsU3a3upr2QIDAQABMA0GCSqGSIb3\nDQEBCwUAA0EAy9Acsgr/lH1rrE8DZov7dvvNjExkC+VO0kujO25aQIGBAtzLp9MG\nEblQ3ZXMBSX4b/nLMjOH8Xr4ZA0GUDgdew==\n-----END CERTIFICATE-----';
|
|
const validKey0 = '-----BEGIN RSA PRIVATE KEY-----\nMIIBPAIBAAJBAPvgb1v6A7GjOe3eEOBkI5FM4wdP4JSi4+D9GPCJ0oSxzrnfVMc+\n3SHKh5vcfTDrak0YbYhzaBS2S7FN2t7qa9kCAwEAAQJBALsBjWyKmcd/2vjCkWEo\nuEefAEhjg+iXb/2RrLyad1TQfgs35UfigcjpWbzT2ScpFZT61ng6hKmclt2OCT9F\nBKECIQD/bjRbGiPq762ikWkfvalgkAAhSoXo2AcD/MsrhWyyPQIhAPxwM7jZRNvO\nng3TJaAgISwwUC9vuaNJQ06Yt02pvoXNAiEAuQipTrGCAWe8vb5ei8rFzxihr3wf\nw0vy0RWoTA+sbPUCIHDFOwXf4bgEJG1unwdacxdHefrHAXold3D8Hh8OrnMdAiEA\nov6sW0C1+maNpoWC+moDGFdImZnej2SDIB5976akWVo=\n-----END RSA PRIVATE KEY-----';
|
|
|
|
// *.foobar.com
|
|
const validCert1 = '-----BEGIN CERTIFICATE-----\nMIIByTCCAXMCFEXpWxabfp9Nybi7akGuxKlXdQVsMA0GCSqGSIb3DQEBCwUAMGYx\nCzAJBgNVBAYTAkRFMQ8wDQYDVQQIDAZCZXJsaW4xDzANBgNVBAcMBkJlcmxpbjEQ\nMA4GA1UECgwHTmVidWxvbjEMMAoGA1UECwwDQ1RPMRUwEwYDVQQDDAwqLmZvb2Jh\nci5jb20wHhcNMjAxMTIyMDIxNjQzWhcNMzAxMTIwMDIxNjQzWjBmMQswCQYDVQQG\nEwJERTEPMA0GA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4xEDAOBgNVBAoM\nB05lYnVsb24xDDAKBgNVBAsMA0NUTzEVMBMGA1UEAwwMKi5mb29iYXIuY29tMFww\nDQYJKoZIhvcNAQEBBQADSwAwSAJBAKMUYf86EG+J6ughAvhKGbIIyOpB3XqnK6KV\nM+r2/DvFx2KGIew7KopkzM2+UThDWE2YTcgL5846QRbx+K5NAXECAwEAATANBgkq\nhkiG9w0BAQsFAANBAJrX5wdszGt0lhDx0w2saJtTM3A6AfYdI7F37rgnvQKwRA0u\nTlN9Ekp4HbZsRi36g3W9zl6nWa3/HWbnBiRNuXk=\n-----END CERTIFICATE-----\n';
|
|
const validKey1 = '-----BEGIN RSA PRIVATE KEY-----\nMIIBOQIBAAJBAKMUYf86EG+J6ughAvhKGbIIyOpB3XqnK6KVM+r2/DvFx2KGIew7\nKopkzM2+UThDWE2YTcgL5846QRbx+K5NAXECAwEAAQJAL/m/GqaqTyXzxXZwuTqT\ndJzA/qmBzqN/YsUiEO24Jp0AVuERlgiKBbxpu0xp8EpDsLTEt6TWWy1p0HIH6e0j\nAQIhANIZkHD6gVxvAMz0tquSprBnylqHngdT/PymDEHHNPv1AiEAxrUTvxV+vmii\n5CCLFTnYTQliKr+PC5qxn2WxV1rPng0CIGTiS55EW0t0LbE8rF40XAAGxn6z8ijY\npnj2jpojOojlAiBoaA6XEXFGFO651QufPISVfb+x3HMJ0t9PdHxo/NMoJQIgbVUh\naQKzUcrgIM2nbg4fLp3+VAh0ZkxNwaeKcsZz0cQ=\n-----END RSA PRIVATE KEY-----\n';
|
|
|
|
// baz.foobar.com
|
|
const validCert2 = '-----BEGIN CERTIFICATE-----\nMIIBzTCCAXcCFDnT77DcYIJ5EtAjyN7125yvCVtKMA0GCSqGSIb3DQEBCwUAMGgx\nCzAJBgNVBAYTAkRFMQ8wDQYDVQQIDAZCZXJsaW4xDzANBgNVBAcMBkJlcmxpbjEQ\nMA4GA1UECgwHTmVidWxvbjEMMAoGA1UECwwDQ1RPMRcwFQYDVQQDDA5iYXouZm9v\nYmFyLmNvbTAeFw0yNDEyMDQxNTMwMzRaFw0zNDEyMDIxNTMwMzRaMGgxCzAJBgNV\nBAYTAkRFMQ8wDQYDVQQIDAZCZXJsaW4xDzANBgNVBAcMBkJlcmxpbjEQMA4GA1UE\nCgwHTmVidWxvbjEMMAoGA1UECwwDQ1RPMRcwFQYDVQQDDA5iYXouZm9vYmFyLmNv\nbTBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQC3PwF9imDPxtYTEbg+Grz5GSgc6Rqk\nNsMOyeOLDmEF+KiXf1iHZ+6dlXNeE8ULCnTomQ8KQ3VGKT596fqDF+pzAgMBAAEw\nDQYJKoZIhvcNAQELBQADQQCuzLEHQIG5kiWL7gkLMHURCkNqLrxEQmhm3RVNkeGZ\n6GXhS3qKc4PEl6kxK6XZXI/OPimmMGF+CKpJNpx88xMm\n-----END CERTIFICATE-----\n';
|
|
const validKey2 = '-----BEGIN PRIVATE KEY-----\nMIIBVwIBADANBgkqhkiG9w0BAQEFAASCAUEwggE9AgEAAkEAtz8BfYpgz8bWExG4\nPhq8+RkoHOkapDbDDsnjiw5hBfiol39Yh2funZVzXhPFCwp06JkPCkN1Rik+fen6\ngxfqcwIDAQABAkEAgEXGkU6wfkG90RNyWWb2KZkGj1ZNo+4BlPSWJ90k5bv/ZGhN\n02H7ur2Lz8WOzrw/pQsK4g3xD5t+2I6RvzTywQIhANvdaeK13jI2822HHgMdY9Cp\nm3e69OxdPa2/t0+ExH7ZAiEA1VzlKtgI1j6gYQ70sa1N/dAsiFk8K2X7WuHKxeFh\n/CsCIQC7XQdD/OpabnupxfBshRovkqn7MWZRGvBZ5bvoVuNAmQIhAJoXW/6kRUWN\nt0BBj+EeO5xaEz9pyvXA0lZhiZN94ck/AiEAiLXiDwsUjOzcgGeX1wtn3fPlHZ1r\nbI3V4DXKe3AjUms=\n-----END PRIVATE KEY-----\n';
|
|
|
|
/*
|
|
Generate these with:
|
|
openssl ecparam -genkey -name prime256v1 -out server.key
|
|
openssl req -new -key server.key -out server.csr -subj "/C=DE/ST=Berlin/L=Berlin/O=Nebulon/OU=CTO/CN=*.foobar.com"
|
|
openssl req -x509 -sha256 -days 3650 -key server.key -in server.csr -out server.crt
|
|
*/
|
|
|
|
// *.foobar.com
|
|
const validCert4 = '-----BEGIN CERTIFICATE-----\nMIICITCCAcegAwIBAgIUThSKBnGJ3TzM3ACzYQinCB5KS0QwCgYIKoZIzj0EAwIw\nZjELMAkGA1UEBhMCREUxDzANBgNVBAgMBkJlcmxpbjEPMA0GA1UEBwwGQmVybGlu\nMRAwDgYDVQQKDAdOZWJ1bG9uMQwwCgYDVQQLDANDVE8xFTATBgNVBAMMDCouZm9v\nYmFyLmNvbTAeFw0yMDExMjIwMTU5MjhaFw0zMDExMjAwMTU5MjhaMGYxCzAJBgNV\nBAYTAkRFMQ8wDQYDVQQIDAZCZXJsaW4xDzANBgNVBAcMBkJlcmxpbjEQMA4GA1UE\nCgwHTmVidWxvbjEMMAoGA1UECwwDQ1RPMRUwEwYDVQQDDAwqLmZvb2Jhci5jb20w\nWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARXV7XqwL8dTTdKJ1sngAAgFXBmppsy\n5GLjm49GrDTB2ho6sjjwMUzKKP9jVCRrSlcKwmXNAy75/pPtLkL4A+s/o1MwUTAd\nBgNVHQ4EFgQUWajw1bCj16I+F8ZpjQEMnJb56XkwHwYDVR0jBBgwFoAUWajw1bCj\n16I+F8ZpjQEMnJb56XkwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNIADBF\nAiEA8eeVP+FvAfg4RVjH17DL/zPUBUIsmyTnPm9D7zIAdc0CICZYPU5qrAKA1h5U\n6+8vX4w+EuVQ8vjc8ATl7L/IKdmL\n-----END CERTIFICATE-----\n';
|
|
const validKey4 = '-----BEGIN EC PARAMETERS-----\nBggqhkjOPQMBBw==\n-----END EC PARAMETERS-----\n-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIAczzARUd1L4KN/2Fl4s5kc1to6QzP9XGPCVLfQdtwbSoAoGCCqGSM49\nAwEHoUQDQgAEV1e16sC/HU03SidbJ4AAIBVwZqabMuRi45uPRqw0wdoaOrI48DFM\nyij/Y1Qka0pXCsJlzQMu+f6T7S5C+APrPw==\n-----END EC PRIVATE KEY-----\n';
|
|
|
|
// cp /etc/ssl/openssl.cnf /tmp/openssl.cnf
|
|
// echo -e "[SAN]\nsubjectAltName=DNS:amazing.com,DNS:*.amazing.com\n" >> /tmp/openssl.cnf
|
|
// openssl req -x509 -newkey rsa:2048 -keyout amazing.key -out amazing.crt -days 3650 -subj /CN=*.amazing.com -nodes -extensions SAN -config /tmp/openssl.cnf
|
|
const validCert3 = '-----BEGIN CERTIFICATE-----\nMIIC3DCCAcSgAwIBAgIJALcStAD5sDWEMA0GCSqGSIb3DQEBCwUAMBgxFjAUBgNV\nBAMMDSouYW1hemluZy5jb20wHhcNMTgwMjA5MjIxMzM2WhcNMjgwMjA3MjIxMzM2\nWjAYMRYwFAYDVQQDDA0qLmFtYXppbmcuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOC\nAQ8AMIIBCgKCAQEAvp8dk13u4vmAfKfRNOO8+rVQ8q+vyR8scc9Euj0pTodLBflM\n2K6Zk0isirRzCL/jd4n1A6QrPeJ+r2J4xtHk2j+pavt8Sa2Go2MzpAe3OTuIqYJf\nUt7Im3f2Lb67itTPrpA2TR3A/dDFlazju+eBd3t3496Do8aBPpXAdOabfPsrv3nE\nx97vrr4tzeK3kG9u7GYuod5gyiwF2t5wSeMWbFk2oqkOCtHRXE77JDKVxIGiepnU\nTnkW9b7jIkiBQ1x0xHG4soewV2ymGHS2XrUHZ45FFMG7yVYpytKT9Iz9ty/z5VcL\nZ6NzgU/pKfQaIe8MpoDpVf5UNeB2DOAAEoJKKwIDAQABoykwJzAlBgNVHREEHjAc\nggthbWF6aW5nLmNvbYINKi5hbWF6aW5nLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEA\nMULk6B9XrVPAole8W66o3WUUOrC7NVjbwZjr+Kp5oQTSo84qacaZS2C3ox/j/TZY\nUuNvoE6gIOHi+inN+G4P76K7NEvm8+Y1CeAyaPq01H4Qy2lk9F5wFMtPqvBZnF9C\nx1MvV30FruHXe5pDfnG1npKECpn2SgE3k6FRHM55u8rTMEm/O4TtsDq+fPqUvyWa\nZuRjPv4qVGGkoPyxA6iffxclpOAXs3JUgLcYoM2vxKC0YSOjHEa0p4uffX063Jgg\nybuy3OKvm+8L6moycX7J+LZK81dDTFDtF7PwrnRbpS4re0i/LSk23jDQvDOLnrAa\nSawRR8+1QHTENBo7dnP+NA==\n-----END CERTIFICATE-----';
|
|
const validKey3 = '-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC+nx2TXe7i+YB8\np9E047z6tVDyr6/JHyxxz0S6PSlOh0sF+UzYrpmTSKyKtHMIv+N3ifUDpCs94n6v\nYnjG0eTaP6lq+3xJrYajYzOkB7c5O4ipgl9S3sibd/YtvruK1M+ukDZNHcD90MWV\nrOO754F3e3fj3oOjxoE+lcB05pt8+yu/ecTH3u+uvi3N4reQb27sZi6h3mDKLAXa\n3nBJ4xZsWTaiqQ4K0dFcTvskMpXEgaJ6mdROeRb1vuMiSIFDXHTEcbiyh7BXbKYY\ndLZetQdnjkUUwbvJVinK0pP0jP23L/PlVwtno3OBT+kp9Boh7wymgOlV/lQ14HYM\n4AASgkorAgMBAAECggEAdVSVLMcNqlGuv4vAHtDq2lpOaAKxrZbtkWPlxsisqzRl\nfljT7y+RQfHimkG16LXL+iFFWadsIlxOY/+1nZNGTPwQeNQwzVzs2ZbPC3DgW28E\nkGm56NVOHzu4oLGc2DhjWOxVMCRXTSN66sUPK/K0YunxgqXM2zrtBKvCWXI0VLlo\nN/UWAwHf4i0GWRl8u8PvxgMXlSW9p9l6gSsivWRMag9ADwRQ/NSKrRYkiOoRe3vz\nLxXARBvzeZXvOPVLGVRX4SIR7OmS8cC6Ol/rp1/ZFFID7aN+wdzphPSL1UNUriw4\nDv1mxz73SNakgeYSFBoWRS5BsJI01JoCoILsnhVCiQKBgQDyW+k5+j4K17fzwsmi\nyxZ0Nz/ncpkqxVrWYZM3pn7OVkb2NDArimEk53kmJ0hrT84kKJUYDx55R2TpnzpV\nMLmjxgs9TUrzZzsL/DP2ppkfE3OrPS+06OGa5GbURxD6KPvqDtOmU3oFyJ3f4YJR\nVK7RW+zO4sXEpHIxwdBXbYov1QKBgQDJWbt+W5M0sA2D5LrUBNMTvMdNnKH0syc2\nZlcIOdj6HuUIveYpBRq64Jn9VJpXMxQanwE+IUjCpPTa8wF0OA6MZPy6cfovqb8a\ni1/M/lvCoYVS3KHLcTOvTGD3xej0EUj13xWGNu8y3i7Z9/Bl21hEyjd0q0I5OqJx\no9Qa5TGR/wKBgBPfkYpdiMTe14i3ik09FgRFm4nhDcpCEKbPrYC8uF03Ge6KbQDF\nAh5ClN6aDggurRqt8Tvd0YPkZNP7aI8fxbk2PimystiuuFrNPX2WP6warjt2cvkE\nt6s522zAvxWkUrPor1ZONg1PXBLFrSf6J7OnNA3q7oina23FFM52fwRZAoGAZ7l7\nFffU2IKNI9HT0N7/YZ6RSVEUOXuFCsgjs5AhT5BUynERPTZs87I6gb9wltUwWRpq\nSHhbBDJ4FMa0jAtIq1hmvSF0EdOvJ9x+qJqr6JLOnMYd7zDMwFRna5yfigPRgx+9\n9dsc1CaTGiRYyg/5484MTWTgA51KC6Kq5IQHSj8CgYBr9rWgqM8hVCKSt1cMguQV\nTPaV97+u3kV2jFd/aVgDtCDIVvp5TPuqfskE1v3MsSjJ8hfHdYvyxZB8h8T4LlTD\n2HdxwCjVh2qirAvkar2b1mfA6R8msmVaIxBu4MqDcIPqR823klF7A8jSD3MGzYcU\nbnnxMdwgWQkmx0/6/90ZCg==\n-----END PRIVATE KEY-----\n';
|
|
|
|
it('does not allow empty string for cert', async function () {
|
|
const [error] = await safe(reverseProxy.validateCertificate('', foobarDomain, { cert: '', key: 'key' }));
|
|
expect(error.reason).to.be(BoxError.BAD_FIELD);
|
|
});
|
|
|
|
it('does not allow empty string for key', async function () {
|
|
const [error] = await safe(reverseProxy.validateCertificate('', foobarDomain, { cert: 'cert', key: '' }));
|
|
expect(error.reason).to.be(BoxError.BAD_FIELD);
|
|
});
|
|
|
|
it('does not allow invalid cert', async function () {
|
|
const [error] = await safe(reverseProxy.validateCertificate('', foobarDomain, { cert: 'someinvalidcert', key: validKey0 }));
|
|
expect(error.reason).to.be(BoxError.BAD_FIELD);
|
|
});
|
|
|
|
it('does not allow invalid key', async function () {
|
|
const [error] = await safe(reverseProxy.validateCertificate('', foobarDomain, { cert: validCert0, key: 'invalidkey' }));
|
|
expect(error.reason).to.be(BoxError.BAD_FIELD);
|
|
});
|
|
|
|
it('does not allow cert without matching domain', async function () {
|
|
const [error] = await safe(reverseProxy.validateCertificate('', 'cloudron.io', { cert: validCert0, key: validKey0 }));
|
|
expect(error.reason).to.be(BoxError.BAD_FIELD);
|
|
|
|
const [error2] = await safe(reverseProxy.validateCertificate('cloudron.io', foobarDomain, { cert: validCert0, key: validKey0 }));
|
|
expect(error2.reason).to.be(BoxError.BAD_FIELD);
|
|
});
|
|
|
|
it('allows valid cert with matching domain', async function () {
|
|
await reverseProxy.validateCertificate('', foobarDomain, { cert: validCert0, key: validKey0 });
|
|
});
|
|
|
|
it('allows valid cert with matching domain (wildcard)', async function () {
|
|
await reverseProxy.validateCertificate('abc', foobarDomain, { cert: validCert1, key: validKey1 });
|
|
});
|
|
|
|
it('does now allow cert without matching domain (wildcard)', async function () {
|
|
const [error] = await safe(reverseProxy.validateCertificate('', foobarDomain, { cert: validCert1, key: validKey1 }));
|
|
expect(error.reason).to.be(BoxError.BAD_FIELD);
|
|
|
|
const [error2] = await safe(reverseProxy.validateCertificate('bar.abc', foobarDomain, { cert: validCert1, key: validKey1 }));
|
|
expect(error2.reason).to.be(BoxError.BAD_FIELD);
|
|
});
|
|
|
|
it('allows valid cert with matching domain (subdomain)', async function () {
|
|
await reverseProxy.validateCertificate('baz', foobarDomain, { cert: validCert2, key: validKey2 });
|
|
});
|
|
|
|
it('does not allow cert without matching domain (subdomain)', async function () {
|
|
const [error] = await safe(reverseProxy.validateCertificate('baz', foobarDomain, { cert: validCert0, key: validKey0 }));
|
|
expect(error.reason).to.be(BoxError.BAD_FIELD);
|
|
});
|
|
|
|
it('does not allow invalid cert/key tuple', async function () {
|
|
//expect(reverseProxy.validateCertificate('', foobarDomain, { cert: validCert0, key: validKey1 })).to.be.an(Error);
|
|
});
|
|
|
|
it('picks certificate in SAN', async function () {
|
|
await reverseProxy.validateCertificate('', amazingDomain, { cert: validCert3, key: validKey3 });
|
|
await reverseProxy.validateCertificate('subdomain', amazingDomain, { cert: validCert3, key: validKey3 });
|
|
});
|
|
|
|
it('allows valid cert with matching domain (subdomain) - ecdsa', async function () {
|
|
reverseProxy.validateCertificate('baz', foobarDomain, { cert: validCert4, key: validKey4 });
|
|
});
|
|
});
|
|
|
|
describe('generateFallbackCertificate', function () {
|
|
const domain = 'cool.com';
|
|
let result;
|
|
|
|
it('can generate fallback certs', async function () {
|
|
result = await reverseProxy.generateFallbackCertificate(domain);
|
|
expect(result).to.be.ok();
|
|
});
|
|
|
|
it('can validate the certs', async function () {
|
|
await reverseProxy.validateCertificate('foo', domain, result);
|
|
await reverseProxy.validateCertificate('', domain, result);
|
|
});
|
|
});
|
|
|
|
describe('configureApp', function () {
|
|
before(async function () {
|
|
domainCopy.tlsConfig = { provider: 'fallback' };
|
|
|
|
await domains.setConfig(domainCopy.domain, domainCopy, auditSource);
|
|
});
|
|
|
|
it('configure nginx correctly', async function () {
|
|
await reverseProxy.configureApp(app, auditSource);
|
|
expect(fs.existsSync(paths.NGINX_APPCONFIG_DIR + '/' + app.id + '.conf'));
|
|
});
|
|
|
|
it('unconfigure nginx', async function () {
|
|
await reverseProxy.unconfigureApp(app);
|
|
expect(!fs.existsSync(paths.NGINX_APPCONFIG_DIR + '/' + app.id + '.conf'));
|
|
});
|
|
});
|
|
});
|