diff --git a/src/utils/validateEnvVars.js b/src/utils/validateEnvVars.js index bf25214f2c..3f5e62164d 100644 --- a/src/utils/validateEnvVars.js +++ b/src/utils/validateEnvVars.js @@ -50,15 +50,27 @@ const envVarsSchema = Joi.object({ CLUSTER_SHUTDOWN_TIMEOUT_MS: Joi.number().integer().min(1).optional(), REDIS_URL: Joi.string().pattern(/^rediss?:\/\//).optional(), - // TLS certs are required in production + // Mirrors config/index.js: any value other than 'false' enables TLS. + TLS_ENABLED: Joi.string().valid('true', 'false').optional(), + + // TLS certs are required in production unless TLS terminates at the platform + // edge (TLS_ENABLED=false), where startApplicationRuntime never loads them. TLS_KEY: Joi.string().when('NODE_ENV', { is: 'production', - then: Joi.required(), + then: Joi.when('TLS_ENABLED', { + is: 'false', + then: Joi.optional(), + otherwise: Joi.required(), + }), otherwise: Joi.optional(), }), TLS_CERT: Joi.string().when('NODE_ENV', { is: 'production', - then: Joi.required(), + then: Joi.when('TLS_ENABLED', { + is: 'false', + then: Joi.optional(), + otherwise: Joi.required(), + }), otherwise: Joi.optional(), }), OUTBOUND_CA_BUNDLE_FILE: Joi.string().optional(), diff --git a/tests/unit/utils/validateEnvVars.test.js b/tests/unit/utils/validateEnvVars.test.js index 6c10f405d0..6b2d831010 100644 --- a/tests/unit/utils/validateEnvVars.test.js +++ b/tests/unit/utils/validateEnvVars.test.js @@ -174,12 +174,92 @@ describe('Unit | Utils | Validate Env Vars', () => { NODE_ENV: 'production', REPLICATE_API_TOKEN: 'test-token', }, + remove: ['TLS_KEY', 'TLS_CERT', 'TLS_ENABLED'], + }); + + expect(() => validateEnvVars()).toThrow(/TLS_KEY/); + }); + + it('does not require production TLS credentials when TLS_ENABLED=false', () => { + const validateEnvVars = loadValidator({ + overrides: { + NODE_ENV: 'production', + TLS_ENABLED: 'false', + REPLICATE_API_TOKEN: 'test-token', + }, + remove: ['TLS_KEY', 'TLS_CERT'], + }); + + expect(() => validateEnvVars()).not.toThrow(); + }); + + it('requires production TLS credentials when TLS_ENABLED=true', () => { + const validateEnvVars = loadValidator({ + overrides: { + NODE_ENV: 'production', + TLS_ENABLED: 'true', + REPLICATE_API_TOKEN: 'test-token', + }, remove: ['TLS_KEY', 'TLS_CERT'], }); expect(() => validateEnvVars()).toThrow(/TLS_KEY/); }); + it('requires TLS_CERT in production when only TLS_KEY is supplied', () => { + const validateEnvVars = loadValidator({ + overrides: { + NODE_ENV: 'production', + REPLICATE_API_TOKEN: 'test-token', + TLS_KEY: 'tls-key', + }, + remove: ['TLS_CERT', 'TLS_ENABLED'], + }); + + expect(() => validateEnvVars()).toThrow(/TLS_CERT/); + }); + + it('accepts TLS_ENABLED=false in production alongside unused TLS credentials', () => { + const validateEnvVars = loadValidator({ + overrides: { + NODE_ENV: 'production', + TLS_ENABLED: 'false', + REPLICATE_API_TOKEN: 'test-token', + TLS_KEY: 'tls-key', + TLS_CERT: 'tls-cert', + }, + }); + + expect(() => validateEnvVars()).not.toThrow(); + }); + + it('rejects a TLS_ENABLED value other than true or false', () => { + const validateEnvVars = loadValidator({ + overrides: { + NODE_ENV: 'production', + TLS_ENABLED: 'disabled', + REPLICATE_API_TOKEN: 'test-token', + TLS_KEY: 'tls-key', + TLS_CERT: 'tls-cert', + }, + }); + + expect(() => validateEnvVars()).toThrow(/TLS_ENABLED/); + }); + + it('leaves TLS credentials optional outside production when TLS_ENABLED=true', () => { + const validateEnvVars = loadValidator({ + overrides: { + NODE_ENV: 'development', + TLS_ENABLED: 'true', + REPLICATE_API_TOKEN: 'test-token', + }, + remove: ['TLS_KEY', 'TLS_CERT'], + }); + + expect(() => validateEnvVars()).not.toThrow(); + }); + it('accepts a non-negative TRUST_PROXY_HOPS override', () => { const validateEnvVars = loadValidator({ overrides: {