2015-12-10 13:31:47 -08:00
'use strict' ;
2016-05-06 18:44:37 +02:00
exports = module . exports = {
2018-01-30 12:23:27 -08:00
ReverseProxyError : ReverseProxyError ,
2017-01-05 14:38:36 -08:00
2016-05-06 18:44:37 +02:00
setFallbackCertificate : setFallbackCertificate ,
2017-11-12 00:53:28 +01:00
getFallbackCertificate : getFallbackCertificate ,
2017-01-17 09:57:15 -08:00
2016-05-06 18:44:37 +02:00
validateCertificate : validateCertificate ,
2017-01-17 09:57:15 -08:00
2018-01-30 10:13:28 -08:00
getCertificate : getCertificate ,
2016-06-22 13:48:07 -05:00
2017-01-17 09:57:15 -08:00
renewAll : renewAll ,
2018-01-30 12:23:27 -08:00
configureDefaultServer : configureDefaultServer ,
2018-01-30 13:54:13 -08:00
2018-01-30 12:23:27 -08:00
configureAdmin : configureAdmin ,
configureApp : configureApp ,
unconfigureApp : unconfigureApp ,
2018-01-30 13:54:13 -08:00
2018-01-30 12:23:27 -08:00
reload : reload ,
removeAppConfigs : removeAppConfigs ,
2016-06-22 13:48:07 -05:00
// exported for testing
_getApi : getApi
2016-05-06 18:44:37 +02:00
} ;
2015-12-10 13:31:47 -08:00
var acme = require ( './cert/acme.js' ) ,
2016-03-17 12:20:02 -07:00
apps = require ( './apps.js' ) ,
2015-12-10 13:31:47 -08:00
assert = require ( 'assert' ) ,
2015-12-14 12:28:00 -08:00
async = require ( 'async' ) ,
2015-12-13 19:06:19 -08:00
caas = require ( './cert/caas.js' ) ,
2015-12-10 13:31:47 -08:00
config = require ( './config.js' ) ,
2015-12-11 13:52:21 -08:00
constants = require ( './constants.js' ) ,
2017-04-23 21:53:59 -07:00
debug = require ( 'debug' ) ( 'box:certificates' ) ,
2018-01-31 18:27:18 +01:00
domains = require ( './domains.js' ) ,
2018-01-30 12:23:27 -08:00
ejs = require ( 'ejs' ) ,
2016-04-30 22:27:33 -07:00
eventlog = require ( './eventlog.js' ) ,
2016-12-05 17:01:23 +01:00
fallback = require ( './cert/fallback.js' ) ,
2015-12-11 13:52:21 -08:00
fs = require ( 'fs' ) ,
2016-03-19 20:40:03 -07:00
mailer = require ( './mailer.js' ) ,
2015-12-11 13:52:21 -08:00
path = require ( 'path' ) ,
2015-12-10 13:31:47 -08:00
paths = require ( './paths.js' ) ,
2018-01-26 22:35:08 -08:00
platform = require ( './platform.js' ) ,
2015-12-10 20:31:38 -08:00
safe = require ( 'safetydance' ) ,
2018-01-30 12:23:27 -08:00
shell = require ( './shell.js' ) ,
2018-02-02 20:29:04 -08:00
tld = require ( 'tldjs' ) ,
2016-01-13 14:21:23 -08:00
user = require ( './user.js' ) ,
2017-02-24 19:21:53 -08:00
util = require ( 'util' ) ;
2015-12-10 13:31:47 -08:00
2018-01-30 12:23:27 -08:00
var NGINX _APPCONFIG _EJS = fs . readFileSync ( _ _dirname + '/../setup/start/nginx/appconfig.ejs' , { encoding : 'utf8' } ) ,
RELOAD _NGINX _CMD = path . join ( _ _dirname , 'scripts/reloadnginx.sh' ) ,
NOOP _CALLBACK = function ( error ) { if ( error ) debug ( error ) ; } ;
function ReverseProxyError ( reason , errorOrMessage ) {
2015-12-11 13:43:33 -08:00
assert . strictEqual ( typeof reason , 'string' ) ;
assert ( errorOrMessage instanceof Error || typeof errorOrMessage === 'string' || typeof errorOrMessage === 'undefined' ) ;
Error . call ( this ) ;
Error . captureStackTrace ( this , this . constructor ) ;
this . name = this . constructor . name ;
this . reason = reason ;
if ( typeof errorOrMessage === 'undefined' ) {
this . message = reason ;
} else if ( typeof errorOrMessage === 'string' ) {
this . message = errorOrMessage ;
} else {
this . message = 'Internal error' ;
this . nestedError = errorOrMessage ;
}
}
2018-01-30 12:23:27 -08:00
util . inherits ( ReverseProxyError , Error ) ;
ReverseProxyError . INTERNAL _ERROR = 'Internal Error' ;
ReverseProxyError . INVALID _CERT = 'Invalid certificate' ;
ReverseProxyError . NOT _FOUND = 'Not Found' ;
2015-12-11 13:43:33 -08:00
2016-04-19 08:21:23 -07:00
function getApi ( app , callback ) {
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2018-01-31 18:27:18 +01:00
domains . get ( app . domain , function ( error , domain ) {
2015-12-14 12:28:00 -08:00
if ( error ) return callback ( error ) ;
2018-01-31 18:27:18 +01:00
if ( domain . tlsConfig . provider === 'fallback' ) return callback ( null , fallback , { } ) ;
2016-12-05 17:01:23 +01:00
2018-02-07 20:54:43 +01:00
var api = domain . tlsConfig . provider === 'caas' ? caas : acme ;
2015-12-14 12:28:00 -08:00
2015-12-17 13:17:46 -08:00
var options = { } ;
2018-01-31 18:27:18 +01:00
if ( domain . tlsConfig . provider === 'caas' ) {
2018-02-07 20:54:43 +01:00
options . prod = true ;
2016-06-22 13:48:07 -05:00
} else { // acme
2018-01-31 18:37:05 -08:00
options . prod = domain . tlsConfig . provider . match ( /.*-prod/ ) !== null ; // matches 'le-prod' or 'letsencrypt-prod'
2016-06-22 13:48:07 -05:00
}
2016-01-13 14:21:23 -08:00
2016-01-13 12:15:27 -08:00
// registering user with an email requires A or MX record (https://github.com/letsencrypt/boulder/issues/1197)
// we cannot use admin@fqdn because the user might not have set it up.
2016-01-13 14:21:23 -08:00
// we simply update the account with the latest email we have each time when getting letsencrypt certs
// https://github.com/ietf-wg-acme/acme/issues/30
user . getOwner ( function ( error , owner ) {
2018-01-21 14:50:24 +01:00
options . email = error ? 'support@cloudron.io' : ( owner . fallbackEmail || owner . email ) ; // can error if not activated yet
2015-12-17 13:17:46 -08:00
2016-01-13 14:21:23 -08:00
callback ( null , api , options ) ;
} ) ;
2015-12-14 12:28:00 -08:00
} ) ;
}
2016-03-23 08:49:08 -07:00
function isExpiringSync ( certFilePath , hours ) {
assert . strictEqual ( typeof certFilePath , 'string' ) ;
2016-03-18 22:59:51 -07:00
assert . strictEqual ( typeof hours , 'number' ) ;
2016-03-19 12:50:31 -07:00
2016-03-23 08:49:08 -07:00
if ( ! fs . existsSync ( certFilePath ) ) return 2 ; // not found
2015-12-14 12:38:19 -08:00
2016-03-19 13:22:38 -07:00
var result = safe . child _process . spawnSync ( '/usr/bin/openssl' , [ 'x509' , '-checkend' , String ( 60 * 60 * hours ) , '-in' , certFilePath ] ) ;
2016-03-18 22:59:51 -07:00
2016-03-21 08:25:10 -07:00
debug ( 'isExpiringSync: %s %s %s' , certFilePath , result . stdout . toString ( 'utf8' ) . trim ( ) , result . status ) ;
2016-03-14 22:50:06 -07:00
return result . status === 1 ; // 1 - expired 0 - not expired
2015-12-14 12:38:19 -08:00
}
2015-12-10 20:31:38 -08:00
// note: https://tools.ietf.org/html/rfc4346#section-7.4.2 (certificate_list) requires that the
// servers certificate appears first (and not the intermediate cert)
2018-01-26 20:34:15 -08:00
function validateCertificate ( domain , cert , key ) {
2018-01-24 14:28:35 -08:00
assert . strictEqual ( typeof domain , 'string' ) ;
2018-01-26 19:31:06 -08:00
assert . strictEqual ( typeof cert , 'string' ) ;
assert . strictEqual ( typeof key , 'string' ) ;
2015-12-10 20:31:38 -08:00
2018-01-24 14:28:35 -08:00
function matchesDomain ( candidate ) {
if ( typeof candidate !== 'string' ) return false ;
if ( candidate === domain ) return true ;
if ( candidate . indexOf ( '*' ) === 0 && candidate . slice ( 2 ) === domain . slice ( domain . indexOf ( '.' ) + 1 ) ) return true ;
2017-05-11 21:55:25 +02:00
return false ;
}
2018-01-26 19:31:06 -08:00
// check for empty cert and key strings
2018-01-30 12:23:27 -08:00
if ( ! cert && key ) return new ReverseProxyError ( ReverseProxyError . INVALID _CERT , 'missing cert' ) ;
if ( cert && ! key ) return new ReverseProxyError ( ReverseProxyError . INVALID _CERT , 'missing key' ) ;
2015-12-10 20:31:38 -08:00
2018-01-24 14:28:35 -08:00
var result = safe . child _process . execSync ( 'openssl x509 -noout -checkhost "' + domain + '"' , { encoding : 'utf8' , input : cert } ) ;
2018-01-30 12:23:27 -08:00
if ( ! result ) return new ReverseProxyError ( ReverseProxyError . INVALID _CERT , 'Unable to get certificate subject.' ) ;
2017-05-11 21:55:25 +02:00
// if no match, check alt names
if ( result . indexOf ( 'does match certificate' ) === - 1 ) {
// https://github.com/drwetter/testssl.sh/pull/383
2017-11-27 10:39:42 -08:00
var cmd = ' openssl x509 - noout - text | grep - A3 "Subject Alternative Name" | \
2017-05-11 21:55:25 +02:00
grep "DNS:" | \
2017-11-27 10:39:42 -08:00
sed - e "s/DNS://g" - e "s/ //g" - e "s/,/ /g" - e "s/othername:<unsupported>//g" ' ;
2017-05-11 21:55:25 +02:00
result = safe . child _process . execSync ( cmd , { encoding : 'utf8' , input : cert } ) ;
var altNames = result ? [ ] : result . trim ( ) . split ( ' ' ) ; // might fail if cert has no SAN
debug ( 'validateCertificate: detected altNames as %j' , altNames ) ;
// check altNames
2018-01-30 12:23:27 -08:00
if ( ! altNames . some ( matchesDomain ) ) return new ReverseProxyError ( ReverseProxyError . INVALID _CERT , util . format ( 'Certificate is not valid for this domain. Expecting %s in %j' , domain , altNames ) ) ;
2017-05-11 21:55:25 +02:00
}
2015-12-10 20:31:38 -08:00
// http://httpd.apache.org/docs/2.0/ssl/ssl_faq.html#verify
var certModulus = safe . child _process . execSync ( 'openssl x509 -noout -modulus' , { encoding : 'utf8' , input : cert } ) ;
var keyModulus = safe . child _process . execSync ( 'openssl rsa -noout -modulus' , { encoding : 'utf8' , input : key } ) ;
2018-01-30 12:23:27 -08:00
if ( certModulus !== keyModulus ) return new ReverseProxyError ( ReverseProxyError . INVALID _CERT , 'Key does not match the certificate.' ) ;
2015-12-10 20:31:38 -08:00
2017-11-27 10:39:42 -08:00
// check expiration
2017-02-24 19:21:53 -08:00
result = safe . child _process . execSync ( 'openssl x509 -checkend 0' , { encoding : 'utf8' , input : cert } ) ;
2018-01-30 12:23:27 -08:00
if ( ! result ) return new ReverseProxyError ( ReverseProxyError . INVALID _CERT , 'Certificate has expired.' ) ;
2017-02-24 19:21:53 -08:00
2015-12-10 20:31:38 -08:00
return null ;
}
2015-12-11 13:52:21 -08:00
2018-01-30 16:14:05 -08:00
function reload ( callback ) {
if ( process . env . BOX _ENV === 'test' ) return callback ( ) ;
shell . sudo ( 'reload' , [ RELOAD _NGINX _CMD ] , callback ) ;
}
2018-01-26 20:30:37 -08:00
function setFallbackCertificate ( domain , fallback , callback ) {
2018-01-24 14:28:35 -08:00
assert . strictEqual ( typeof domain , 'string' ) ;
2018-01-26 20:30:37 -08:00
assert . strictEqual ( typeof fallback , 'object' ) ;
2015-12-11 13:52:21 -08:00
assert . strictEqual ( typeof callback , 'function' ) ;
2018-01-26 20:30:37 -08:00
const certFilePath = path . join ( paths . APP _CERTS _DIR , ` ${ domain } .host.cert ` ) ;
const keyFilePath = path . join ( paths . APP _CERTS _DIR , ` ${ domain } .host.key ` ) ;
2018-01-26 22:27:32 -08:00
if ( fallback ) {
2018-01-26 20:30:37 -08:00
// backup the cert
2018-01-30 12:23:27 -08:00
if ( ! safe . fs . writeFileSync ( path . join ( paths . APP _CERTS _DIR , ` ${ domain } .host.cert ` ) , fallback . cert ) ) return callback ( new ReverseProxyError ( ReverseProxyError . INTERNAL _ERROR , safe . error . message ) ) ;
if ( ! safe . fs . writeFileSync ( path . join ( paths . APP _CERTS _DIR , ` ${ domain } .host.key ` ) , fallback . key ) ) return callback ( new ReverseProxyError ( ReverseProxyError . INTERNAL _ERROR , safe . error . message ) ) ;
2018-01-26 22:27:32 -08:00
} else if ( ! fs . existsSync ( certFilePath ) || ! fs . existsSync ( keyFilePath ) ) { // generate it
var certCommand = util . format ( 'openssl req -x509 -newkey rsa:2048 -keyout %s -out %s -days 3650 -subj /CN=*.%s -nodes' , keyFilePath , certFilePath , domain ) ;
2018-01-30 12:23:27 -08:00
if ( ! safe . child _process . execSync ( certCommand ) ) return callback ( new ReverseProxyError ( ReverseProxyError . INTERNAL _ERROR , safe . error . message ) ) ;
2018-01-26 20:30:37 -08:00
}
2015-12-11 13:52:21 -08:00
2018-01-26 22:35:08 -08:00
platform . handleCertChanged ( '*.' + domain ) ;
2017-01-17 10:53:26 -08:00
2018-01-30 12:23:27 -08:00
reload ( function ( error ) {
if ( error ) return callback ( new ReverseProxyError ( ReverseProxyError . INTERNAL _ERROR , error ) ) ;
2015-12-11 13:52:21 -08:00
return callback ( null ) ;
} ) ;
}
2018-01-24 14:28:35 -08:00
function getFallbackCertificate ( domain , callback ) {
assert . strictEqual ( typeof domain , 'string' ) ;
2016-05-04 17:37:21 -07:00
assert . strictEqual ( typeof callback , 'function' ) ;
2018-01-31 18:20:29 -08:00
// check for any pre-provisioned (caas) certs. they get first priority
var certFilePath = path . join ( paths . NGINX _CERT _DIR , ` ${ domain } .host.cert ` ) ;
var keyFilePath = path . join ( paths . NGINX _CERT _DIR , ` ${ domain } .host.key ` ) ;
if ( fs . existsSync ( certFilePath ) && fs . existsSync ( keyFilePath ) ) return callback ( null , { certFilePath , keyFilePath } ) ;
// check for auto-generated or user set fallback certs
certFilePath = path . join ( paths . APP _CERTS _DIR , ` ${ domain } .host.cert ` ) ;
keyFilePath = path . join ( paths . APP _CERTS _DIR , ` ${ domain } .host.key ` ) ;
callback ( null , { certFilePath , keyFilePath } ) ;
2016-05-04 17:37:21 -07:00
}
2018-01-30 10:13:28 -08:00
function getCertificate ( app , callback ) {
assert . strictEqual ( typeof app , 'object' ) ;
2016-05-04 17:37:21 -07:00
assert . strictEqual ( typeof callback , 'function' ) ;
2018-02-08 15:07:49 +01:00
var certFilePath = path . join ( paths . APP _CERTS _DIR , ` ${ app . fqdn } .user.cert ` ) ;
var keyFilePath = path . join ( paths . APP _CERTS _DIR , ` ${ app . fqdn } .user.key ` ) ;
2017-01-17 09:58:55 -08:00
2018-01-31 18:20:29 -08:00
if ( fs . existsSync ( certFilePath ) && fs . existsSync ( keyFilePath ) ) return callback ( null , { certFilePath , keyFilePath } ) ;
2017-01-17 09:58:55 -08:00
2018-02-08 15:07:49 +01:00
certFilePath = path . join ( paths . APP _CERTS _DIR , ` ${ app . fqdn } .cert ` ) ;
keyFilePath = path . join ( paths . APP _CERTS _DIR , ` ${ app . fqdn } .key ` ) ;
2016-05-04 17:37:21 -07:00
2018-01-31 18:20:29 -08:00
if ( fs . existsSync ( certFilePath ) && fs . existsSync ( keyFilePath ) ) return callback ( null , { certFilePath , keyFilePath } ) ;
2016-05-04 17:37:21 -07:00
2018-01-31 18:20:29 -08:00
return getFallbackCertificate ( app . domain , callback ) ;
2017-01-17 10:21:42 -08:00
}
2018-01-30 15:16:34 -08:00
function ensureCertificate ( app , auditSource , callback ) {
2016-04-19 08:13:44 -07:00
assert . strictEqual ( typeof app , 'object' ) ;
2018-01-30 15:16:34 -08:00
assert . strictEqual ( typeof auditSource , 'object' ) ;
2015-12-11 14:15:23 -08:00
assert . strictEqual ( typeof callback , 'function' ) ;
2018-02-08 15:07:49 +01:00
var certFilePath = path . join ( paths . APP _CERTS _DIR , ` ${ app . fqdn } .user.cert ` ) ;
var keyFilePath = path . join ( paths . APP _CERTS _DIR , ` ${ app . fqdn } .user.key ` ) ;
2016-09-12 01:21:51 -07:00
if ( fs . existsSync ( certFilePath ) && fs . existsSync ( keyFilePath ) ) {
2018-02-08 15:07:49 +01:00
debug ( 'ensureCertificate: %s. user certificate already exists at %s' , app . fqdn , keyFilePath ) ;
2018-01-30 16:16:10 -08:00
return callback ( null , { certFilePath , keyFilePath , reason : 'user' } ) ;
2016-09-12 01:21:51 -07:00
}
2018-02-08 15:07:49 +01:00
certFilePath = path . join ( paths . APP _CERTS _DIR , ` ${ app . fqdn } .cert ` ) ;
keyFilePath = path . join ( paths . APP _CERTS _DIR , ` ${ app . fqdn } .key ` ) ;
2016-03-23 08:49:08 -07:00
2016-09-12 01:21:51 -07:00
if ( fs . existsSync ( certFilePath ) && fs . existsSync ( keyFilePath ) ) {
2018-02-08 15:07:49 +01:00
debug ( 'ensureCertificate: %s. certificate already exists at %s' , app . fqdn , keyFilePath ) ;
2016-03-19 13:22:38 -07:00
2018-01-30 16:16:10 -08:00
if ( ! isExpiringSync ( certFilePath , 24 * 30 ) ) return callback ( null , { certFilePath , keyFilePath , reason : 'existing-le' } ) ;
2018-02-08 15:07:49 +01:00
debug ( 'ensureCertificate: %s cert require renewal' , app . fqdn ) ;
2017-01-17 10:35:42 +01:00
} else {
2018-02-08 15:07:49 +01:00
debug ( 'ensureCertificate: %s cert does not exist' , app . fqdn ) ;
2015-12-14 12:38:19 -08:00
}
2016-04-19 08:21:23 -07:00
getApi ( app , function ( error , api , apiOptions ) {
2015-12-14 12:38:19 -08:00
if ( error ) return callback ( error ) ;
2015-12-11 22:25:22 -08:00
2018-01-30 14:18:25 -08:00
debug ( 'ensureCertificate: getting certificate for %s with options %j' , vhost , apiOptions ) ;
2015-12-11 14:15:23 -08:00
2018-01-30 14:18:25 -08:00
api . getCertificate ( vhost , apiOptions , function ( error , certFilePath , keyFilePath ) {
2018-02-02 21:21:51 -08:00
var errorMessage = error ? error . message : '' ;
2018-01-30 15:16:34 -08:00
if ( error ) {
debug ( 'ensureCertificate: could not get certificate. using fallback certs' , error ) ;
mailer . certificateRenewalError ( vhost , errorMessage ) ;
}
eventlog . add ( eventlog . ACTION _CERTIFICATE _RENEWAL , auditSource , { domain : vhost , errorMessage : errorMessage } ) ;
2017-11-12 00:53:28 +01:00
2018-01-30 14:18:25 -08:00
// if no cert was returned use fallback. the fallback/caas provider will not provide any for example
2018-02-07 20:54:43 +01:00
if ( ! certFilePath || ! keyFilePath ) return getFallbackCertificate ( app . domain , callback ) ;
2015-12-14 17:09:40 -08:00
2018-01-30 16:16:10 -08:00
callback ( null , { certFilePath , keyFilePath , reason : 'new-le' } ) ;
2015-12-14 17:09:40 -08:00
} ) ;
2015-12-11 14:15:23 -08:00
} ) ;
}
2018-01-30 12:23:27 -08:00
2018-01-30 16:16:10 -08:00
function configureAdminInternal ( bundle , configFileName , vhost , callback ) {
assert . strictEqual ( typeof bundle , 'object' ) ;
2018-01-30 12:23:27 -08:00
assert . strictEqual ( typeof configFileName , 'string' ) ;
assert . strictEqual ( typeof vhost , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
var data = {
sourceDir : path . resolve ( _ _dirname , '..' ) ,
adminOrigin : config . adminOrigin ( ) ,
vhost : vhost , // if vhost is empty it will become the default_server
hasIPv6 : config . hasIPv6 ( ) ,
endpoint : 'admin' ,
2018-01-30 16:16:10 -08:00
certFilePath : bundle . certFilePath ,
keyFilePath : bundle . keyFilePath ,
2018-01-30 12:23:27 -08:00
xFrameOptions : 'SAMEORIGIN' ,
robotsTxtQuoted : JSON . stringify ( 'User-agent: *\nDisallow: /\n' )
} ;
var nginxConf = ejs . render ( NGINX _APPCONFIG _EJS , data ) ;
var nginxConfigFilename = path . join ( paths . NGINX _APPCONFIG _DIR , configFileName ) ;
if ( ! safe . fs . writeFileSync ( nginxConfigFilename , nginxConf ) ) return callback ( safe . error ) ;
reload ( callback ) ;
}
2018-01-30 15:16:34 -08:00
function configureAdmin ( auditSource , callback ) {
2018-01-30 19:59:09 -08:00
assert . strictEqual ( typeof auditSource , 'object' ) ;
2018-01-30 12:23:27 -08:00
assert . strictEqual ( typeof callback , 'function' ) ;
2018-02-08 15:07:49 +01:00
var adminApp = { domain : config . adminDomain ( ) , fqdn : config . adminFqdn ( ) } ;
2018-01-30 19:59:09 -08:00
ensureCertificate ( adminApp , auditSource , function ( error , bundle ) {
2018-01-30 13:54:13 -08:00
if ( error ) return callback ( error ) ;
2018-01-30 12:23:27 -08:00
2018-01-30 16:16:10 -08:00
configureAdminInternal ( bundle , constants . NGINX _ADMIN _CONFIG _FILE _NAME , config . adminFqdn ( ) , callback ) ;
2018-01-30 13:54:13 -08:00
} ) ;
}
2018-01-30 12:23:27 -08:00
2018-01-30 16:16:10 -08:00
function configureAppInternal ( app , bundle , callback ) {
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof bundle , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
var sourceDir = path . resolve ( _ _dirname , '..' ) ;
var endpoint = 'app' ;
var data = {
sourceDir : sourceDir ,
adminOrigin : config . adminOrigin ( ) ,
2018-02-08 15:07:49 +01:00
vhost : app . fqdn ,
2018-01-30 16:16:10 -08:00
hasIPv6 : config . hasIPv6 ( ) ,
port : app . httpPort ,
endpoint : endpoint ,
certFilePath : bundle . certFilePath ,
keyFilePath : bundle . keyFilePath ,
robotsTxtQuoted : app . robotsTxt ? JSON . stringify ( app . robotsTxt ) : null ,
xFrameOptions : app . xFrameOptions || 'SAMEORIGIN' // once all apps have been updated/
} ;
var nginxConf = ejs . render ( NGINX _APPCONFIG _EJS , data ) ;
var nginxConfigFilename = path . join ( paths . NGINX _APPCONFIG _DIR , app . id + '.conf' ) ;
2018-02-08 15:07:49 +01:00
debug ( 'writing config for "%s" to %s with options %j' , app . fqdn , nginxConfigFilename , data ) ;
2018-01-30 16:16:10 -08:00
if ( ! safe . fs . writeFileSync ( nginxConfigFilename , nginxConf ) ) {
2018-02-08 15:07:49 +01:00
debug ( 'Error creating nginx config for "%s" : %s' , app . fqdn , safe . error . message ) ;
2018-01-30 16:16:10 -08:00
return callback ( safe . error ) ;
}
reload ( callback ) ;
}
2018-01-30 15:16:34 -08:00
function configureApp ( app , auditSource , callback ) {
2018-01-30 13:54:13 -08:00
assert . strictEqual ( typeof app , 'object' ) ;
2018-01-30 15:16:34 -08:00
assert . strictEqual ( typeof auditSource , 'object' ) ;
2018-01-30 13:54:13 -08:00
assert . strictEqual ( typeof callback , 'function' ) ;
2018-01-30 12:23:27 -08:00
2018-01-30 16:16:10 -08:00
ensureCertificate ( app , auditSource , function ( error , bundle ) {
2018-01-30 13:54:13 -08:00
if ( error ) return callback ( error ) ;
2018-01-30 12:23:27 -08:00
2018-01-30 16:16:10 -08:00
configureAppInternal ( app , bundle , callback ) ;
2018-01-30 13:54:13 -08:00
} ) ;
2018-01-30 12:23:27 -08:00
}
function unconfigureApp ( app , callback ) {
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
var nginxConfigFilename = path . join ( paths . NGINX _APPCONFIG _DIR , app . id + '.conf' ) ;
if ( ! safe . fs . unlinkSync ( nginxConfigFilename ) ) {
2018-02-08 15:07:49 +01:00
if ( safe . error . code !== 'ENOENT' ) debug ( 'Error removing nginx configuration of "%s": %s' , app . fqdn , safe . error . message ) ;
2018-01-30 12:23:27 -08:00
return callback ( null ) ;
}
reload ( callback ) ;
}
2018-01-30 16:16:10 -08:00
function renewAll ( auditSource , callback ) {
assert . strictEqual ( typeof auditSource , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
debug ( 'renewAll: Checking certificates for renewal' ) ;
apps . getAll ( function ( error , allApps ) {
if ( error ) return callback ( error ) ;
2018-02-08 15:07:49 +01:00
allApps . push ( { domain : config . adminDomain ( ) , fqdn : config . adminFqdn ( ) } ) ; // inject fake webadmin app
2018-01-30 16:16:10 -08:00
async . eachSeries ( allApps , function ( app , iteratorCallback ) {
ensureCertificate ( app , auditSource , function ( error , bundle ) {
if ( bundle . reason !== 'new-le' && bundle . reason !== 'fallback' ) return iteratorCallback ( ) ;
// reconfigure for the case where we got a renewed cert after fallback
2018-02-08 15:07:49 +01:00
var configureFunc = app . fqdn === config . adminFqdn ( ) ?
2018-01-30 16:16:10 -08:00
configureAdminInternal . bind ( null , bundle , constants . NGINX _ADMIN _CONFIG _FILE _NAME , config . adminFqdn ( ) )
: configureAppInternal . bind ( null , app , bundle ) ;
configureFunc ( function ( ignoredError ) {
if ( ignoredError ) debug ( 'fallbackExpiredCertificates: error reconfiguring app' , ignoredError ) ;
2018-02-08 15:07:49 +01:00
platform . handleCertChanged ( app . fqdn ) ;
2018-01-30 16:16:10 -08:00
iteratorCallback ( ) ; // move to next app
} ) ;
} ) ;
} ) ;
} ) ;
}
2018-01-30 12:23:27 -08:00
function removeAppConfigs ( ) {
for ( var appConfigFile of fs . readdirSync ( paths . NGINX _APPCONFIG _DIR ) ) {
fs . unlinkSync ( path . join ( paths . NGINX _APPCONFIG _DIR , appConfigFile ) ) ;
}
}
function configureDefaultServer ( callback ) {
callback = callback || NOOP _CALLBACK ;
var certFilePath = path . join ( paths . NGINX _CERT _DIR , 'default.cert' ) ;
var keyFilePath = path . join ( paths . NGINX _CERT _DIR , 'default.key' ) ;
if ( ! fs . existsSync ( certFilePath ) || ! fs . existsSync ( keyFilePath ) ) {
debug ( 'configureDefaultServer: create new cert' ) ;
var cn = 'cloudron-' + ( new Date ( ) ) . toISOString ( ) ; // randomize date a bit to keep firefox happy
var certCommand = util . format ( 'openssl req -x509 -newkey rsa:2048 -keyout %s -out %s -days 3650 -subj /CN=%s -nodes' , keyFilePath , certFilePath , cn ) ;
safe . child _process . execSync ( certCommand ) ;
}
2018-01-30 16:16:10 -08:00
configureAdminInternal ( { certFilePath , keyFilePath } , 'default.conf' , '' , function ( error ) {
2018-01-30 12:23:27 -08:00
if ( error ) return callback ( error ) ;
debug ( 'configureDefaultServer: done' ) ;
callback ( null ) ;
} ) ;
}