2015-12-10 13:31:47 -08:00
'use strict' ;
2016-05-06 18:44:37 +02:00
exports = module . exports = {
2020-08-13 14:00:55 -07:00
setFallbackCertificate ,
getFallbackCertificate ,
2017-01-17 09:57:15 -08:00
2020-08-13 14:00:55 -07:00
generateFallbackCertificateSync ,
setAppCertificateSync ,
2018-11-05 19:09:58 -08:00
2020-08-13 14:00:55 -07:00
validateCertificate ,
2017-01-17 09:57:15 -08:00
2020-08-13 14:00:55 -07:00
getCertificate ,
ensureCertificate ,
2016-06-22 13:48:07 -05:00
2020-08-13 14:00:55 -07:00
renewCerts ,
2017-01-17 09:57:15 -08:00
2019-09-30 15:25:53 -07:00
// the 'configure' ensure a certificate and generate nginx config
2020-08-13 14:00:55 -07:00
configureAdmin ,
configureApp ,
unconfigureApp ,
2018-01-30 13:54:13 -08:00
2019-09-30 15:25:53 -07:00
// these only generate nginx config
2020-08-13 14:00:55 -07:00
writeDefaultConfig ,
2020-09-23 15:45:04 -07:00
writeDashboardConfig ,
2020-08-13 14:00:55 -07:00
writeAppConfig ,
removeAppConfigs ,
2018-01-30 12:23:27 -08:00
2016-06-22 13:48:07 -05:00
// exported for testing
2020-08-07 22:59:57 -07:00
_getAcmeApi : getAcmeApi
2016-05-06 18:44:37 +02:00
} ;
2018-09-10 15:19:10 -07:00
var acme2 = require ( './cert/acme2.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' ) ,
2019-10-22 16:46:24 -07:00
BoxError = require ( './boxerror.js' ) ,
2015-12-11 13:52:21 -08:00
constants = require ( './constants.js' ) ,
2018-02-09 13:44:29 -08:00
crypto = require ( 'crypto' ) ,
2018-10-31 15:41:02 -07:00
debug = require ( 'debug' ) ( 'box:reverseproxy' ) ,
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' ) ,
2015-12-11 13:52:21 -08:00
fs = require ( 'fs' ) ,
2019-03-04 15:20:58 -08:00
mail = require ( './mail.js' ) ,
2018-02-09 13:44:29 -08:00
os = require ( 'os' ) ,
2015-12-11 13:52:21 -08:00
path = require ( 'path' ) ,
2015-12-10 13:31:47 -08:00
paths = require ( './paths.js' ) ,
2018-06-29 23:01:22 +02:00
rimraf = require ( 'rimraf' ) ,
2015-12-10 20:31:38 -08:00
safe = require ( 'safetydance' ) ,
2019-07-26 10:49:29 -07:00
settings = require ( './settings.js' ) ,
2018-01-30 12:23:27 -08:00
shell = require ( './shell.js' ) ,
2019-07-25 11:26:53 -07:00
sysinfo = require ( './sysinfo.js' ) ,
2018-04-29 10:58:45 -07:00
users = require ( './users.js' ) ,
2017-02-24 19:21:53 -08:00
util = require ( 'util' ) ;
2015-12-10 13:31:47 -08:00
2019-10-13 17:08:33 -07:00
var NGINX _APPCONFIG _EJS = fs . readFileSync ( _ _dirname + '/nginxconfig.ejs' , { encoding : 'utf8' } ) ,
2018-11-10 18:21:15 -08:00
RELOAD _NGINX _CMD = path . join ( _ _dirname , 'scripts/reloadnginx.sh' ) ;
2018-01-30 12:23:27 -08:00
2021-01-08 14:10:11 -08:00
function nginxLocation ( s ) {
if ( ! s . startsWith ( '!' ) ) return s ;
let re = s . replace ( /[\^$\\.*+?()[\]{}|]/g , '\\$&' ) ; // https://github.com/es-shims/regexp.escape/blob/master/implementation.js
return ` ~ ^(?!( ${ re . slice ( 1 ) } )) ` ; // negative regex assertion - https://stackoverflow.com/questions/16302897/nginx-location-not-equal-to-regex
}
2020-08-07 22:59:57 -07:00
function getAcmeApi ( domainObject , callback ) {
2018-11-14 19:36:12 -08:00
assert . strictEqual ( typeof domainObject , 'object' ) ;
2016-04-19 08:21:23 -07:00
assert . strictEqual ( typeof callback , 'function' ) ;
2020-08-07 22:59:57 -07:00
const api = acme2 ;
2016-12-05 17:01:23 +01:00
2020-08-07 22:59:57 -07:00
let options = { prod : false , performHttpAuthorization : false , wildcard : false , email : '' } ;
2020-08-07 11:49:34 -07:00
options . prod = domainObject . tlsConfig . provider . match ( /.*-prod/ ) !== null ; // matches 'le-prod' or 'letsencrypt-prod'
options . performHttpAuthorization = domainObject . provider . match ( /noop|manual|wildcard/ ) !== null ;
options . wildcard = ! ! domainObject . tlsConfig . wildcard ;
2016-01-13 14:21:23 -08:00
2018-11-14 19:36:12 -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.
// 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
users . getOwner ( function ( error , owner ) {
2020-05-03 11:02:18 +02:00
options . email = error ? 'webmaster@cloudron.io' : owner . email ; // can error if not activated yet
2015-12-17 13:17:46 -08:00
2018-11-14 19:36:12 -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
2018-11-23 11:26:18 -08:00
if ( ! result ) return 3 ; // some error
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
}
2018-11-14 19:46:38 -08:00
// checks if the certificate matches the options provided by user (like wildcard, le-staging etc)
function providerMatchesSync ( domainObject , certFilePath , apiOptions ) {
assert . strictEqual ( typeof domainObject , 'object' ) ;
2018-10-24 19:52:07 -07:00
assert . strictEqual ( typeof certFilePath , 'string' ) ;
assert . strictEqual ( typeof apiOptions , 'object' ) ;
if ( ! fs . existsSync ( certFilePath ) ) return false ; // not found
const subjectAndIssuer = safe . child _process . execSync ( ` /usr/bin/openssl x509 -noout -subject -issuer -in " ${ certFilePath } " ` , { encoding : 'utf8' } ) ;
2018-11-23 11:39:00 -08:00
if ( ! subjectAndIssuer ) return false ; // something bad happenned
2018-10-24 19:52:07 -07:00
2018-11-15 14:18:34 +01:00
const subject = subjectAndIssuer . match ( /^subject=(.*)$/m ) [ 1 ] ;
2018-11-15 10:45:27 -08:00
const domain = subject . substr ( subject . indexOf ( '=' ) + 1 ) . trim ( ) ; // subject can be /CN=, CN=, CN = and other forms
2018-11-14 19:46:38 -08:00
const issuer = subjectAndIssuer . match ( /^issuer=(.*)$/m ) [ 1 ] ;
2018-11-15 10:45:27 -08:00
const isWildcardCert = domain . includes ( '*' ) ;
2020-12-04 11:48:23 -08:00
const isLetsEncryptProd = issuer . includes ( 'Let\'s Encrypt' ) ;
2018-11-14 19:46:38 -08:00
const issuerMismatch = ( apiOptions . prod && ! isLetsEncryptProd ) || ( ! apiOptions . prod && isLetsEncryptProd ) ;
// bare domain is not part of wildcard SAN
2018-11-15 10:45:27 -08:00
const wildcardMismatch = ( domain !== domainObject . domain ) && ( apiOptions . wildcard && ! isWildcardCert ) || ( ! apiOptions . wildcard && isWildcardCert ) ;
2018-10-24 19:52:07 -07:00
2018-11-14 19:46:38 -08:00
const mismatch = issuerMismatch || wildcardMismatch ;
2018-10-24 19:52:07 -07:00
2020-12-04 11:47:19 -08:00
debug ( ` providerMatchesSync: ${ certFilePath } subject= ${ subject } domain= ${ domain } issuer= ${ issuer } `
+ ` wildcard= ${ isWildcardCert } / ${ apiOptions . wildcard } prod= ${ isLetsEncryptProd } / ${ apiOptions . prod } `
+ ` issuerMismatch= ${ issuerMismatch } wildcardMismatch= ${ wildcardMismatch } match= ${ ! mismatch } ` ) ;
2018-10-24 19:52:07 -07:00
return ! mismatch ;
}
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-11-05 22:36:16 -08:00
function validateCertificate ( location , domainObject , certificate ) {
2018-11-05 19:09:58 -08:00
assert . strictEqual ( typeof location , 'string' ) ;
2018-11-05 21:26:53 -08:00
assert . strictEqual ( typeof domainObject , 'object' ) ;
2018-11-05 22:36:16 -08:00
assert ( certificate && typeof certificate , 'object' ) ;
const cert = certificate . cert , key = certificate . key ;
2015-12-10 20:31:38 -08:00
2018-01-26 19:31:06 -08:00
// check for empty cert and key strings
2019-10-22 16:46:24 -07:00
if ( ! cert && key ) return new BoxError ( BoxError . BAD _FIELD , 'missing cert' , { field : 'cert' } ) ;
if ( cert && ! key ) return new BoxError ( BoxError . BAD _FIELD , 'missing key' , { field : 'key' } ) ;
2015-12-10 20:31:38 -08:00
2018-02-09 14:05:01 -08:00
// -checkhost checks for SAN or CN exclusively. SAN takes precedence and if present, ignores the CN.
2019-01-04 18:44:54 -08:00
const fqdn = domains . fqdn ( location , domainObject ) ;
2018-11-05 19:09:58 -08:00
2020-03-24 20:56:49 -07:00
let result = safe . child _process . execSync ( ` openssl x509 -noout -checkhost " ${ fqdn } " ` , { encoding : 'utf8' , input : cert } ) ;
2019-10-22 16:46:24 -07:00
if ( result === null ) return new BoxError ( BoxError . BAD _FIELD , 'Unable to get certificate subject:' + safe . error . message , { field : 'cert' } ) ;
2017-05-11 21:55:25 +02:00
2019-10-22 16:46:24 -07:00
if ( result . indexOf ( 'does match certificate' ) === - 1 ) return new BoxError ( BoxError . BAD _FIELD , ` Certificate is not valid for this domain. Expecting ${ fqdn } ` , { field : 'cert' } ) ;
2015-12-10 20:31:38 -08:00
2020-03-24 20:56:49 -07:00
// check if public key in the cert and private key matches. pkey below works for RSA and ECDSA keys
const pubKeyFromCert = safe . child _process . execSync ( 'openssl x509 -noout -pubkey' , { encoding : 'utf8' , input : cert } ) ;
if ( pubKeyFromCert === null ) return new BoxError ( BoxError . BAD _FIELD , ` Unable to get public key from cert: ${ safe . error . message } ` , { field : 'cert' } ) ;
2018-11-23 11:39:00 -08:00
2020-03-24 20:56:49 -07:00
const pubKeyFromKey = safe . child _process . execSync ( 'openssl pkey -pubout' , { encoding : 'utf8' , input : key } ) ;
if ( pubKeyFromKey === null ) return new BoxError ( BoxError . BAD _FIELD , ` Unable to get public key from private key: ${ safe . error . message } ` , { field : 'cert' } ) ;
2018-11-23 11:39:00 -08:00
2020-03-24 20:56:49 -07:00
if ( pubKeyFromCert !== pubKeyFromKey ) return new BoxError ( BoxError . BAD _FIELD , 'Public key does not match the certificate.' , { field : 'cert' } ) ;
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 } ) ;
2019-10-22 16:46:24 -07:00
if ( ! result ) return new BoxError ( BoxError . BAD _FIELD , 'Certificate has expired.' , { field : 'cert' } ) ;
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 ) {
2020-11-18 23:24:34 -08:00
if ( constants . TEST ) return callback ( ) ;
2018-01-30 16:14:05 -08:00
2019-10-24 10:28:38 -07:00
shell . sudo ( 'reload' , [ RELOAD _NGINX _CMD ] , { } , function ( error ) {
2019-12-04 13:17:58 -08:00
if ( error ) return callback ( new BoxError ( BoxError . NGINX _ERROR , ` Error reloading nginx: ${ error . message } ` ) ) ;
2019-10-24 10:28:38 -07:00
callback ( ) ;
} ) ;
2018-01-30 16:14:05 -08:00
}
2018-11-05 19:09:58 -08:00
function generateFallbackCertificateSync ( domainObject ) {
assert . strictEqual ( typeof domainObject , 'object' ) ;
const domain = domainObject . domain ;
const certFilePath = path . join ( os . tmpdir ( ) , ` ${ domain } - ${ crypto . randomBytes ( 4 ) . readUInt32LE ( 0 ) } .cert ` ) ;
const keyFilePath = path . join ( os . tmpdir ( ) , ` ${ domain } - ${ crypto . randomBytes ( 4 ) . readUInt32LE ( 0 ) } .key ` ) ;
let opensslConf = safe . fs . readFileSync ( '/etc/ssl/openssl.cnf' , 'utf8' ) ;
// SAN must contain all the domains since CN check is based on implementation if SAN is found. -checkhost also checks only SAN if present!
let opensslConfWithSan ;
2020-08-15 18:40:59 -07:00
let cn = domain ;
2018-11-05 20:36:58 -08:00
2020-08-15 18:40:59 -07:00
debug ( ` generateFallbackCertificateSync: domain= ${ domainObject . domain } cn= ${ cn } ` ) ;
2018-11-05 20:36:58 -08:00
opensslConfWithSan = ` ${ opensslConf } \n [SAN] \n subjectAltName=DNS: ${ domain } ,DNS:*. ${ cn } \n ` ;
2018-11-05 19:09:58 -08:00
let configFile = path . join ( os . tmpdir ( ) , 'openssl-' + crypto . randomBytes ( 4 ) . readUInt32LE ( 0 ) + '.conf' ) ;
safe . fs . writeFileSync ( configFile , opensslConfWithSan , 'utf8' ) ;
2020-10-08 14:38:52 -07:00
// the days field is chosen to be less than 825 days per apple requirement (https://support.apple.com/en-us/HT210176)
let certCommand = util . format ( ` openssl req -x509 -newkey rsa:2048 -keyout ${ keyFilePath } -out ${ certFilePath } -days 800 -subj /CN=*. ${ cn } -extensions SAN -config ${ configFile } -nodes ` ) ;
2019-10-22 16:46:24 -07:00
if ( ! safe . child _process . execSync ( certCommand ) ) return { error : new BoxError ( BoxError . OPENSSL _ERROR , safe . error . message ) } ;
2018-11-05 19:09:58 -08:00
safe . fs . unlinkSync ( configFile ) ;
const cert = safe . fs . readFileSync ( certFilePath , 'utf8' ) ;
2019-10-23 10:02:04 -07:00
if ( ! cert ) return { error : new BoxError ( BoxError . FS _ERROR , safe . error . message ) } ;
2018-11-05 19:09:58 -08:00
safe . fs . unlinkSync ( certFilePath ) ;
const key = safe . fs . readFileSync ( keyFilePath , 'utf8' ) ;
2019-10-23 10:02:04 -07:00
if ( ! key ) return { error : new BoxError ( BoxError . FS _ERROR , safe . error . message ) } ;
2018-11-05 19:09:58 -08:00
safe . fs . unlinkSync ( keyFilePath ) ;
return { cert : cert , key : key , error : null } ;
}
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-11-05 19:09:58 -08:00
assert ( fallback && typeof fallback === 'object' ) ;
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' ) ;
2020-08-07 11:41:15 -07:00
debug ( ` setFallbackCertificate: setting certs for domain ${ domain } ` ) ;
if ( ! safe . fs . writeFileSync ( path . join ( paths . APP _CERTS _DIR , ` ${ domain } .host.cert ` ) , fallback . cert ) ) return callback ( new BoxError ( BoxError . FS _ERROR , safe . error . message ) ) ;
if ( ! safe . fs . writeFileSync ( path . join ( paths . APP _CERTS _DIR , ` ${ domain } .host.key ` ) , fallback . key ) ) return callback ( new BoxError ( BoxError . FS _ERROR , safe . error . message ) ) ;
2015-12-11 13:52:21 -08:00
2019-03-04 19:35:22 -08:00
// TODO: maybe the cert is being used by the mail container
reload ( function ( error ) {
2019-10-22 16:46:24 -07:00
if ( error ) return callback ( new BoxError ( BoxError . NGINX _ERROR , error ) ) ;
2015-12-11 13:52:21 -08:00
2019-03-04 19:35:22 -08:00
return callback ( null ) ;
2015-12-11 13:52:21 -08:00
} ) ;
}
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' ) ;
2020-08-07 11:49:34 -07: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-31 18:20:29 -08:00
2019-10-01 11:17:12 -07:00
callback ( null , { certFilePath , keyFilePath } ) ;
2016-05-04 17:37:21 -07:00
}
2018-11-05 22:36:16 -08:00
function setAppCertificateSync ( location , domainObject , certificate ) {
2018-11-05 19:09:58 -08:00
assert . strictEqual ( typeof location , 'string' ) ;
assert . strictEqual ( typeof domainObject , 'object' ) ;
2018-11-05 22:36:16 -08:00
assert . strictEqual ( typeof certificate , 'object' ) ;
2018-11-05 19:09:58 -08:00
2019-01-04 18:44:54 -08:00
let fqdn = domains . fqdn ( location , domainObject ) ;
2018-11-05 22:36:16 -08:00
if ( certificate . cert && certificate . key ) {
if ( ! safe . fs . writeFileSync ( path . join ( paths . APP _CERTS _DIR , ` ${ fqdn } .user.cert ` ) , certificate . cert ) ) return safe . error ;
if ( ! safe . fs . writeFileSync ( path . join ( paths . APP _CERTS _DIR , ` ${ fqdn } .user.key ` ) , certificate . key ) ) return safe . error ;
2018-11-05 19:09:58 -08:00
} else { // remove existing cert/key
if ( ! safe . fs . unlinkSync ( path . join ( paths . APP _CERTS _DIR , ` ${ fqdn } .user.cert ` ) ) ) debug ( 'Error removing cert: ' + safe . error . message ) ;
if ( ! safe . fs . unlinkSync ( path . join ( paths . APP _CERTS _DIR , ` ${ fqdn } .user.key ` ) ) ) debug ( 'Error removing key: ' + safe . error . message ) ;
}
return null ;
}
2021-01-19 13:47:11 -08:00
function getAcmeCertificate ( vhost , domainObject , callback ) {
assert . strictEqual ( typeof vhost , 'string' ) ; // this can contain wildcard domain (for alias domains)
2018-11-14 19:36:12 -08:00
assert . strictEqual ( typeof domainObject , 'object' ) ;
2018-09-12 14:19:25 -07:00
assert . strictEqual ( typeof callback , 'function' ) ;
2016-05-04 17:37:21 -07:00
2020-08-07 22:59:57 -07:00
let certFilePath , keyFilePath ;
2017-01-17 09:58:55 -08:00
2021-01-19 13:47:11 -08:00
if ( vhost !== domainObject . domain && domainObject . tlsConfig . wildcard ) { // bare domain is not part of wildcard SAN
let certName = domains . makeWildcard ( vhost ) . replace ( '*.' , '_.' ) ;
2018-11-14 19:36:12 -08:00
certFilePath = path . join ( paths . APP _CERTS _DIR , ` ${ certName } .cert ` ) ;
keyFilePath = path . join ( paths . APP _CERTS _DIR , ` ${ certName } .key ` ) ;
2016-05-04 17:37:21 -07:00
2018-11-14 19:36:12 -08:00
if ( fs . existsSync ( certFilePath ) && fs . existsSync ( keyFilePath ) ) return callback ( null , { certFilePath , keyFilePath } ) ;
} else {
2021-01-19 13:47:11 -08:00
certFilePath = path . join ( paths . APP _CERTS _DIR , ` ${ vhost } .cert ` ) ;
keyFilePath = path . join ( paths . APP _CERTS _DIR , ` ${ vhost } .key ` ) ;
2018-09-11 22:46:17 -07:00
2018-11-14 19:36:12 -08:00
if ( fs . existsSync ( certFilePath ) && fs . existsSync ( keyFilePath ) ) return callback ( null , { certFilePath , keyFilePath } ) ;
}
2018-09-11 23:47:23 -07:00
2018-11-14 19:36:12 -08:00
callback ( null ) ;
2018-09-11 23:47:23 -07:00
}
2018-12-19 14:20:48 -08:00
function getCertificate ( fqdn , domain , callback ) {
assert . strictEqual ( typeof fqdn , 'string' ) ;
assert . strictEqual ( typeof domain , 'string' ) ;
2018-09-11 23:47:23 -07:00
assert . strictEqual ( typeof callback , 'function' ) ;
2020-08-07 22:59:57 -07:00
// 1. user cert always wins
// 2. if using fallback provider, return that cert
// 3. look for LE certs
2018-12-19 14:20:48 -08:00
domains . get ( domain , function ( error , domainObject ) {
2018-11-14 19:36:12 -08:00
if ( error ) return callback ( error ) ;
2020-08-07 22:59:57 -07:00
// user cert always wins
let certFilePath = path . join ( paths . APP _CERTS _DIR , ` ${ fqdn } .user.cert ` ) ;
let keyFilePath = path . join ( paths . APP _CERTS _DIR , ` ${ fqdn } .user.key ` ) ;
if ( fs . existsSync ( certFilePath ) && fs . existsSync ( keyFilePath ) ) return callback ( null , { certFilePath , keyFilePath } ) ;
if ( domainObject . tlsConfig . provider === 'fallback' ) return getFallbackCertificate ( domain , callback ) ;
getAcmeCertificate ( fqdn , domainObject , function ( error , result ) {
2018-11-14 19:36:12 -08:00
if ( error || result ) return callback ( error , result ) ;
2018-09-11 22:46:17 -07:00
2018-12-19 14:20:48 -08:00
return getFallbackCertificate ( domain , callback ) ;
2018-11-14 19:36:12 -08:00
} ) ;
2018-09-12 14:19:25 -07:00
} ) ;
2017-01-17 10:21:42 -08:00
}
2018-09-12 12:50:04 -07:00
function ensureCertificate ( vhost , domain , auditSource , callback ) {
assert . strictEqual ( typeof vhost , 'string' ) ;
assert . strictEqual ( typeof domain , 'string' ) ;
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-11-14 19:36:12 -08:00
domains . get ( domain , function ( error , domainObject ) {
2018-10-24 19:52:07 -07:00
if ( error ) return callback ( error ) ;
2016-09-12 01:21:51 -07:00
2020-08-07 22:59:57 -07:00
// user cert always wins
let certFilePath = path . join ( paths . APP _CERTS _DIR , ` ${ vhost } .user.cert ` ) ;
let keyFilePath = path . join ( paths . APP _CERTS _DIR , ` ${ vhost } .user.key ` ) ;
2020-08-10 14:54:37 -07:00
if ( fs . existsSync ( certFilePath ) && fs . existsSync ( keyFilePath ) ) {
debug ( ` ensureCertificate: ${ vhost } will use custom app certs ` ) ;
return callback ( null , { certFilePath , keyFilePath } , { renewed : false } ) ;
}
2020-08-07 22:59:57 -07:00
if ( domainObject . tlsConfig . provider === 'fallback' ) {
2020-08-10 14:54:37 -07:00
debug ( ` ensureCertificate: ${ vhost } will use fallback certs ` ) ;
2020-08-07 22:59:57 -07:00
return getFallbackCertificate ( domain , function ( error , bundle ) {
if ( error ) return callback ( error ) ;
callback ( null , bundle , { renewed : false } ) ;
} ) ;
}
getAcmeApi ( domainObject , function ( error , acmeApi , apiOptions ) {
2018-11-14 19:36:12 -08:00
if ( error ) return callback ( error ) ;
2015-12-14 12:38:19 -08:00
2020-08-07 22:59:57 -07:00
getAcmeCertificate ( vhost , domainObject , function ( _error , currentBundle ) {
2018-11-14 20:38:49 -08:00
if ( currentBundle ) {
debug ( ` ensureCertificate: ${ vhost } certificate already exists at ${ currentBundle . keyFilePath } ` ) ;
2015-12-11 22:25:22 -08:00
2019-10-01 11:25:17 -07:00
if ( ! isExpiringSync ( currentBundle . certFilePath , 24 * 30 ) && providerMatchesSync ( domainObject , currentBundle . certFilePath , apiOptions ) ) return callback ( null , currentBundle , { renewed : false } ) ;
2020-12-04 11:47:19 -08:00
debug ( ` ensureCertificate: ${ vhost } cert requires renewal ` ) ;
2018-11-14 19:36:12 -08:00
} else {
debug ( ` ensureCertificate: ${ vhost } cert does not exist ` ) ;
}
2015-12-11 14:15:23 -08:00
2018-11-14 19:36:12 -08:00
debug ( 'ensureCertificate: getting certificate for %s with options %j' , vhost , apiOptions ) ;
2018-02-02 21:21:51 -08:00
2020-08-07 22:59:57 -07:00
acmeApi . getCertificate ( vhost , domain , apiOptions , function ( error , certFilePath , keyFilePath ) {
2019-12-08 18:23:12 -08:00
debug ( ` ensureCertificate: error: ${ error ? error . message : 'null' } cert: ${ certFilePath || 'null' } ` ) ;
2019-10-03 10:36:57 -07:00
2019-03-04 14:50:56 -08:00
eventlog . add ( currentBundle ? eventlog . ACTION _CERTIFICATE _RENEWAL : eventlog . ACTION _CERTIFICATE _NEW , auditSource , { domain : vhost , errorMessage : error ? error . message : '' } ) ;
2015-12-14 17:09:40 -08:00
2019-10-03 10:46:03 -07:00
if ( error && currentBundle && ! isExpiringSync ( currentBundle . certFilePath , 0 ) ) {
debug ( 'ensureCertificate: continue using existing bundle since renewal failed' ) ;
return callback ( null , currentBundle , { renewed : false } ) ;
}
2020-08-15 23:17:47 -07:00
if ( certFilePath && keyFilePath ) return callback ( null , { certFilePath , keyFilePath } , { renewed : true } ) ;
2019-10-01 14:04:39 -07:00
2020-08-15 23:17:47 -07:00
debug ( ` ensureCertificate: renewal of ${ vhost } failed. using fallback certificates for ${ domain } ` ) ;
2019-10-03 10:36:57 -07:00
2020-08-15 23:17:47 -07:00
getFallbackCertificate ( domain , function ( error , bundle ) {
if ( error ) return callback ( error ) ;
2018-11-14 19:36:12 -08:00
2020-08-15 23:17:47 -07:00
callback ( null , bundle , { renewed : false } ) ;
2019-03-04 15:20:58 -08:00
} ) ;
2018-11-14 19:36:12 -08:00
} ) ;
2018-09-12 14:19:25 -07:00
} ) ;
2015-12-14 17:09:40 -08:00
} ) ;
2015-12-11 14:15:23 -08:00
} ) ;
}
2018-01-30 12:23:27 -08:00
2020-09-23 15:45:04 -07:00
function writeDashboardNginxConfig ( bundle , configFileName , vhost , callback ) {
2018-01-30 16:16:10 -08:00
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 , '..' ) ,
2019-07-26 10:49:29 -07:00
adminOrigin : settings . adminOrigin ( ) ,
2020-09-23 22:13:02 -07:00
vhost : vhost ,
2019-07-25 11:26:53 -07:00
hasIPv6 : sysinfo . hasIPv6 ( ) ,
2018-01-30 12:23:27 -08:00
endpoint : 'admin' ,
2018-01-30 16:16:10 -08:00
certFilePath : bundle . certFilePath ,
keyFilePath : bundle . keyFilePath ,
2020-11-09 20:34:48 -08:00
robotsTxtQuoted : JSON . stringify ( 'User-agent: *\nDisallow: /\n' ) ,
2021-01-08 14:10:11 -08:00
proxyAuth : { enabled : false , id : null , location : nginxLocation ( '/' ) }
2018-01-30 12:23:27 -08:00
} ;
var nginxConf = ejs . render ( NGINX _APPCONFIG _EJS , data ) ;
var nginxConfigFilename = path . join ( paths . NGINX _APPCONFIG _DIR , configFileName ) ;
2019-10-24 10:28:38 -07:00
if ( ! safe . fs . writeFileSync ( nginxConfigFilename , nginxConf ) ) return callback ( new BoxError ( BoxError . FS _ERROR , safe . error ) ) ;
2018-01-30 12:23:27 -08:00
reload ( callback ) ;
}
2018-12-13 21:42:47 -08:00
function configureAdmin ( domain , auditSource , callback ) {
assert . strictEqual ( typeof domain , 'string' ) ;
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-12-13 21:42:47 -08:00
domains . get ( domain , function ( error , domainObject ) {
2018-01-30 13:54:13 -08:00
if ( error ) return callback ( error ) ;
2018-01-30 12:23:27 -08:00
2019-01-04 18:44:54 -08:00
const adminFqdn = domains . fqdn ( constants . ADMIN _LOCATION , domainObject ) ;
2018-12-13 21:42:47 -08:00
ensureCertificate ( adminFqdn , domainObject . domain , auditSource , function ( error , bundle ) {
if ( error ) return callback ( error ) ;
2020-09-23 15:45:04 -07:00
writeDashboardNginxConfig ( bundle , ` ${ adminFqdn } .conf ` , adminFqdn , callback ) ;
2018-12-13 22:15:08 -08:00
} ) ;
} ) ;
}
2020-09-23 15:45:04 -07:00
function writeDashboardConfig ( domain , callback ) {
2018-12-13 22:15:08 -08:00
assert . strictEqual ( typeof domain , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2020-09-23 15:45:04 -07:00
debug ( ` writeDashboardConfig: writing admin config for ${ domain } ` ) ;
2019-09-30 11:52:23 -07:00
2018-12-13 22:15:08 -08:00
domains . get ( domain , function ( error , domainObject ) {
if ( error ) return callback ( error ) ;
2019-01-04 18:44:54 -08:00
const adminFqdn = domains . fqdn ( constants . ADMIN _LOCATION , domainObject ) ;
2018-12-13 22:15:08 -08:00
2018-12-19 14:20:48 -08:00
getCertificate ( adminFqdn , domainObject . domain , function ( error , bundle ) {
2018-12-13 22:15:08 -08:00
if ( error ) return callback ( error ) ;
2020-09-23 15:45:04 -07:00
writeDashboardNginxConfig ( bundle , ` ${ adminFqdn } .conf ` , adminFqdn , callback ) ;
2018-12-13 21:42:47 -08:00
} ) ;
2018-01-30 13:54:13 -08:00
} ) ;
}
2018-01-30 12:23:27 -08:00
2021-01-18 17:26:26 -08:00
function writeAppNginxConfig ( app , fqdn , bundle , callback ) {
2018-01-30 16:16:10 -08:00
assert . strictEqual ( typeof app , 'object' ) ;
2021-01-18 17:26:26 -08:00
assert . strictEqual ( typeof fqdn , 'string' ) ;
2018-01-30 16:16:10 -08:00
assert . strictEqual ( typeof bundle , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2020-11-20 14:13:16 -08:00
var sourceDir = path . resolve ( _ _dirname , '..' ) ;
var endpoint = 'app' ;
2018-01-30 16:16:10 -08:00
2020-11-20 14:13:16 -08:00
let robotsTxtQuoted = null , hideHeaders = [ ] , cspQuoted = null ;
const reverseProxyConfig = app . reverseProxyConfig || { } ; // some of our code uses fake app objects
if ( reverseProxyConfig . robotsTxt ) robotsTxtQuoted = JSON . stringify ( app . reverseProxyConfig . robotsTxt ) ;
if ( reverseProxyConfig . csp ) {
cspQuoted = ` " ${ app . reverseProxyConfig . csp } " ` ;
hideHeaders = [ 'Content-Security-Policy' ] ;
if ( reverseProxyConfig . csp . includes ( 'frame-ancestors ' ) ) hideHeaders . push ( 'X-Frame-Options' ) ;
}
2019-10-13 18:22:03 -07:00
2020-11-20 14:13:16 -08:00
var data = {
sourceDir : sourceDir ,
adminOrigin : settings . adminOrigin ( ) ,
2021-01-18 17:26:26 -08:00
vhost : fqdn ,
2020-11-20 14:13:16 -08:00
hasIPv6 : sysinfo . hasIPv6 ( ) ,
ip : app . containerIp ,
port : app . manifest . httpPort ,
endpoint : endpoint ,
certFilePath : bundle . certFilePath ,
keyFilePath : bundle . keyFilePath ,
robotsTxtQuoted ,
cspQuoted ,
hideHeaders ,
proxyAuth : {
enabled : app . sso && app . manifest . addons && app . manifest . addons . proxyAuth ,
2020-11-20 18:13:23 -08:00
id : app . id ,
2021-01-08 14:10:11 -08:00
location : nginxLocation ( safe . query ( app . manifest , 'addons.proxyAuth.path' ) || '/' )
2020-11-20 14:13:16 -08:00
} ,
httpPaths : app . manifest . httpPaths || { }
} ;
var nginxConf = ejs . render ( NGINX _APPCONFIG _EJS , data ) ;
2018-01-30 16:16:10 -08:00
2021-01-19 13:36:17 -08:00
const aliasSuffix = app . fqdn === fqdn ? '' : ` -alias- ${ fqdn . replace ( '*' , '_' ) } ` ;
2021-01-18 17:26:26 -08:00
var nginxConfigFilename = path . join ( paths . NGINX _APPCONFIG _DIR , ` ${ app . id } ${ aliasSuffix } .conf ` ) ;
debug ( 'writeAppNginxConfig: writing config for "%s" to %s with options %j' , fqdn , nginxConfigFilename , data ) ;
2018-01-30 16:16:10 -08:00
2020-11-20 14:13:16 -08:00
if ( ! safe . fs . writeFileSync ( nginxConfigFilename , nginxConf ) ) {
debug ( 'Error creating nginx config for "%s" : %s' , app . fqdn , safe . error . message ) ;
return callback ( new BoxError ( BoxError . FS _ERROR , safe . error ) ) ;
}
reload ( callback ) ;
2018-01-30 16:16:10 -08:00
}
2018-12-13 22:15:08 -08:00
function writeAppRedirectNginxConfig ( app , fqdn , bundle , callback ) {
2018-06-29 16:14:13 +02:00
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof fqdn , 'string' ) ;
assert . strictEqual ( typeof bundle , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
var data = {
sourceDir : path . resolve ( _ _dirname , '..' ) ,
vhost : fqdn ,
redirectTo : app . fqdn ,
2019-07-25 11:26:53 -07:00
hasIPv6 : sysinfo . hasIPv6 ( ) ,
2018-06-29 16:14:13 +02:00
endpoint : 'redirect' ,
certFilePath : bundle . certFilePath ,
keyFilePath : bundle . keyFilePath ,
2019-10-13 18:22:03 -07:00
robotsTxtQuoted : null ,
2019-10-14 16:59:22 -07:00
cspQuoted : null ,
2020-11-09 20:34:48 -08:00
hideHeaders : [ ] ,
2021-01-08 14:10:11 -08:00
proxyAuth : { enabled : false , id : app . id , location : nginxLocation ( '/' ) }
2018-06-29 16:14:13 +02:00
} ;
var nginxConf = ejs . render ( NGINX _APPCONFIG _EJS , data ) ;
2018-06-29 19:04:48 +02:00
// if we change the filename, also change it in unconfigureApp()
2018-06-29 17:20:02 +02:00
var nginxConfigFilename = path . join ( paths . NGINX _APPCONFIG _DIR , ` ${ app . id } -redirect- ${ fqdn } .conf ` ) ;
2018-06-29 16:14:13 +02:00
debug ( 'writing config for "%s" redirecting to "%s" to %s with options %j' , app . fqdn , fqdn , nginxConfigFilename , data ) ;
if ( ! safe . fs . writeFileSync ( nginxConfigFilename , nginxConf ) ) {
debug ( 'Error creating nginx redirect config for "%s" : %s' , app . fqdn , safe . error . message ) ;
2019-10-24 10:28:38 -07:00
return callback ( new BoxError ( BoxError . FS _ERROR , safe . error ) ) ;
2018-06-29 16:14:13 +02:00
}
reload ( callback ) ;
}
2019-09-09 21:41:55 -07:00
function writeAppConfig ( app , callback ) {
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2021-01-18 17:26:26 -08:00
let appDomains = [ ] ;
2021-01-18 22:31:10 -08:00
appDomains . push ( { domain : app . domain , fqdn : app . fqdn , type : 'primary' } ) ;
2019-09-09 21:41:55 -07:00
2021-01-18 17:26:26 -08:00
app . alternateDomains . forEach ( function ( alternateDomain ) {
appDomains . push ( { domain : alternateDomain . domain , fqdn : alternateDomain . fqdn , type : 'alternate' } ) ;
} ) ;
2019-09-09 21:41:55 -07:00
2021-01-18 17:26:26 -08:00
app . aliasDomains . forEach ( function ( aliasDomain ) {
appDomains . push ( { domain : aliasDomain . domain , fqdn : aliasDomain . fqdn , type : 'alias' } ) ;
} ) ;
2019-09-09 21:41:55 -07:00
2021-01-18 17:26:26 -08:00
async . eachSeries ( appDomains , function ( appDomain , iteratorDone ) {
getCertificate ( appDomain . fqdn , appDomain . domain , function ( error , bundle ) {
if ( error ) return iteratorDone ( error ) ;
2021-01-18 22:31:10 -08:00
if ( appDomain . type === 'primary' ) {
2021-01-18 17:26:26 -08:00
writeAppNginxConfig ( app , appDomain . fqdn , bundle , iteratorDone ) ;
} else if ( appDomain . type === 'alternate' ) {
writeAppRedirectNginxConfig ( app , appDomain . fqdn , bundle , iteratorDone ) ;
} else if ( appDomain . type === 'alias' ) {
writeAppNginxConfig ( app , appDomain . fqdn , bundle , iteratorDone ) ;
}
2019-09-09 21:41:55 -07:00
} ) ;
2021-01-18 17:26:26 -08:00
} , callback ) ;
2019-09-09 21:41:55 -07:00
}
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
2021-01-18 17:26:26 -08:00
let appDomains = [ ] ;
2021-01-18 22:31:10 -08:00
appDomains . push ( { domain : app . domain , fqdn : app . fqdn , type : 'primary' } ) ;
2018-01-30 12:23:27 -08:00
2021-01-18 17:26:26 -08:00
app . alternateDomains . forEach ( function ( alternateDomain ) {
appDomains . push ( { domain : alternateDomain . domain , fqdn : alternateDomain . fqdn , type : 'alternate' } ) ;
} ) ;
2018-06-29 16:02:33 +02:00
2021-01-18 17:26:26 -08:00
app . aliasDomains . forEach ( function ( aliasDomain ) {
appDomains . push ( { domain : aliasDomain . domain , fqdn : aliasDomain . fqdn , type : 'alias' } ) ;
} ) ;
2018-06-29 16:14:13 +02:00
2021-01-18 17:26:26 -08:00
async . eachSeries ( appDomains , function ( appDomain , iteratorDone ) {
ensureCertificate ( appDomain . fqdn , appDomain . domain , auditSource , function ( error , bundle ) {
if ( error ) return iteratorDone ( error ) ;
2021-01-18 22:31:10 -08:00
if ( appDomain . type === 'primary' ) {
2021-01-18 17:26:26 -08:00
writeAppNginxConfig ( app , appDomain . fqdn , bundle , iteratorDone ) ;
} else if ( appDomain . type === 'alternate' ) {
writeAppRedirectNginxConfig ( app , appDomain . fqdn , bundle , iteratorDone ) ;
} else if ( appDomain . type === 'alias' ) {
writeAppNginxConfig ( app , appDomain . fqdn , bundle , iteratorDone ) ;
}
2018-06-29 16:02:33 +02:00
} ) ;
2021-01-18 17:26:26 -08:00
} , callback ) ;
2018-01-30 12:23:27 -08:00
}
function unconfigureApp ( app , callback ) {
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2018-06-29 23:01:22 +02:00
// we use globbing to find all nginx configs for an app
rimraf ( path . join ( paths . NGINX _APPCONFIG _DIR , ` ${ app . id } *.conf ` ) , function ( error ) {
if ( error ) debug ( 'Error removing nginx configurations of "%s":' , app . fqdn , error ) ;
2018-06-29 19:04:48 +02:00
2018-06-29 23:01:22 +02:00
reload ( callback ) ;
2018-06-29 19:04:48 +02:00
} ) ;
2018-01-30 12:23:27 -08:00
}
2018-12-10 20:20:53 -08:00
function renewCerts ( options , auditSource , progressCallback , callback ) {
2018-10-24 13:01:45 -07:00
assert . strictEqual ( typeof options , 'object' ) ;
2018-01-30 16:16:10 -08:00
assert . strictEqual ( typeof auditSource , 'object' ) ;
2018-12-10 20:20:53 -08:00
assert . strictEqual ( typeof progressCallback , 'function' ) ;
2018-01-30 16:16:10 -08:00
assert . strictEqual ( typeof callback , 'function' ) ;
apps . getAll ( function ( error , allApps ) {
if ( error ) return callback ( error ) ;
2021-01-18 17:26:26 -08:00
let appDomains = [ ] ;
2018-01-30 16:16:10 -08:00
2020-08-15 23:17:47 -07:00
// add webadmin and mail domain
if ( settings . mailFqdn ( ) === settings . adminFqdn ( ) ) {
appDomains . push ( { domain : settings . adminDomain ( ) , fqdn : settings . adminFqdn ( ) , type : 'webadmin+mail' , nginxConfigFilename : path . join ( paths . NGINX _APPCONFIG _DIR , ` ${ settings . adminFqdn ( ) } .conf ` ) } ) ;
} else {
appDomains . push ( { domain : settings . adminDomain ( ) , fqdn : settings . adminFqdn ( ) , type : 'webadmin' , nginxConfigFilename : path . join ( paths . NGINX _APPCONFIG _DIR , ` ${ settings . adminFqdn ( ) } .conf ` ) } ) ;
appDomains . push ( { domain : settings . mailDomain ( ) , fqdn : settings . mailFqdn ( ) , type : 'mail' } ) ;
}
2018-08-25 11:04:49 +02:00
allApps . forEach ( function ( app ) {
2020-01-26 16:05:23 -08:00
if ( app . runState === apps . RSTATE _STOPPED ) return ; // do not renew certs of stopped apps
2021-01-18 22:31:10 -08:00
appDomains . push ( { domain : app . domain , fqdn : app . fqdn , type : 'primary' , app : app , nginxConfigFilename : path . join ( paths . NGINX _APPCONFIG _DIR , app . id + '.conf' ) } ) ;
2018-08-25 11:04:49 +02:00
2018-09-22 16:10:46 -07:00
app . alternateDomains . forEach ( function ( alternateDomain ) {
2020-08-15 23:17:47 -07:00
const nginxConfigFilename = path . join ( paths . NGINX _APPCONFIG _DIR , ` ${ app . id } -redirect- ${ alternateDomain . fqdn } .conf ` ) ;
appDomains . push ( { domain : alternateDomain . domain , fqdn : alternateDomain . fqdn , type : 'alternate' , app : app , nginxConfigFilename } ) ;
2018-08-25 11:04:49 +02:00
} ) ;
2021-01-18 17:26:26 -08:00
app . aliasDomains . forEach ( function ( aliasDomain ) {
2021-01-19 13:36:17 -08:00
const nginxConfigFilename = path . join ( paths . NGINX _APPCONFIG _DIR , ` ${ app . id } -alias- ${ aliasDomain . fqdn . replace ( '*' , '_' ) } .conf ` ) ;
2021-01-18 17:26:26 -08:00
appDomains . push ( { domain : aliasDomain . domain , fqdn : aliasDomain . fqdn , type : 'alias' , app : app , nginxConfigFilename } ) ;
} ) ;
2018-08-25 11:04:49 +02:00
} ) ;
2018-10-24 13:01:45 -07:00
if ( options . domain ) appDomains = appDomains . filter ( function ( appDomain ) { return appDomain . domain === options . domain ; } ) ;
2019-10-01 11:25:17 -07:00
let progress = 1 , renewed = [ ] ;
2018-09-12 12:50:04 -07:00
async . eachSeries ( appDomains , function ( appDomain , iteratorCallback ) {
2018-12-10 20:20:53 -08:00
progressCallback ( { percent : progress , message : ` Renewing certs of ${ appDomain . fqdn } ` } ) ;
progress += Math . round ( 100 / appDomains . length ) ;
2019-10-01 11:25:17 -07:00
ensureCertificate ( appDomain . fqdn , appDomain . domain , auditSource , function ( error , bundle , state ) {
2018-06-05 21:27:32 -07:00
if ( error ) return iteratorCallback ( error ) ; // this can happen if cloudron is not setup yet
2018-01-30 16:16:10 -08:00
2019-10-01 11:25:17 -07:00
if ( state . renewed ) renewed . push ( appDomain . fqdn ) ;
2020-08-15 23:17:47 -07:00
if ( appDomain . type === 'mail' ) return iteratorCallback ( ) ; // mail has no nginx config to check current cert
2018-11-14 19:36:12 -08:00
// hack to check if the app's cert changed or not. this doesn't handle prod/staging le change since they use same file name
2018-09-12 14:19:25 -07:00
let currentNginxConfig = safe . fs . readFileSync ( appDomain . nginxConfigFilename , 'utf8' ) || '' ;
if ( currentNginxConfig . includes ( bundle . certFilePath ) ) return iteratorCallback ( ) ;
2018-10-24 20:06:43 -07:00
debug ( ` renewCerts: creating new nginx config since ${ appDomain . nginxConfigFilename } does not have ${ bundle . certFilePath } ` ) ;
2018-09-12 14:19:25 -07:00
// reconfigure since the cert changed
2020-08-15 23:17:47 -07:00
if ( appDomain . type === 'webadmin' ) {
2020-09-23 15:45:04 -07:00
return writeDashboardNginxConfig ( bundle , ` ${ settings . adminFqdn ( ) } .conf ` , settings . adminFqdn ( ) , iteratorCallback ) ;
2020-08-15 23:17:47 -07:00
} else if ( appDomain . type === 'webadmin+mail' ) {
return async . series ( [
mail . handleCertChanged ,
2020-09-23 15:45:04 -07:00
writeDashboardNginxConfig . bind ( null , bundle , ` ${ settings . adminFqdn ( ) } .conf ` , settings . adminFqdn ( ) )
2020-08-15 23:17:47 -07:00
] , iteratorCallback ) ;
2021-01-18 22:31:10 -08:00
} else if ( appDomain . type === 'primary' ) {
2021-01-18 17:26:26 -08:00
return writeAppNginxConfig ( appDomain . app , appDomain . fqdn , bundle , iteratorCallback ) ;
2020-08-15 23:17:47 -07:00
} else if ( appDomain . type === 'alternate' ) {
return writeAppRedirectNginxConfig ( appDomain . app , appDomain . fqdn , bundle , iteratorCallback ) ;
2021-01-18 17:26:26 -08:00
} else if ( appDomain . type === 'alias' ) {
return writeAppNginxConfig ( appDomain . app , appDomain . fqdn , bundle , iteratorCallback ) ;
2020-08-15 23:17:47 -07:00
}
2018-01-30 16:16:10 -08:00
2020-08-15 23:17:47 -07:00
iteratorCallback ( new BoxError ( BoxError . INTERNAL _ERROR , ` Unknown domain type for ${ appDomain . fqdn } . This should never happen ` ) ) ;
2018-01-30 16:16:10 -08:00
} ) ;
2019-10-01 11:25:17 -07:00
} , function ( error ) {
if ( error ) return callback ( error ) ;
debug ( ` renewCerts: Renewed certs of ${ JSON . stringify ( renewed ) } ` ) ;
if ( renewed . length === 0 ) return callback ( null ) ;
2020-08-15 23:17:47 -07:00
async . series ( [
( next ) => { return renewed . includes ( settings . mailFqdn ( ) ) ? mail . handleCertChanged ( next ) : next ( ) ; } , // mail cert renewed
2021-02-18 09:43:42 -08:00
reload , // reload nginx if any certs were updated but the config was not rewritten
( next ) => { // restart tls apps on cert change
const tlsApps = allApps . filter ( app => app . manifest . addons && app . manifest . addons . tls && renewed . includes ( app . fqdn ) ) ;
async . eachSeries ( tlsApps , function ( app , iteratorDone ) {
apps . restart ( app , auditSource , ( ) => iteratorDone ( ) ) ;
} , next ) ;
}
2020-08-15 23:17:47 -07:00
] , callback ) ;
2019-10-01 11:25:17 -07:00
} ) ;
2018-01-30 16:16:10 -08:00
} ) ;
}
2018-01-30 12:23:27 -08:00
function removeAppConfigs ( ) {
2018-11-10 22:02:42 -08:00
for ( let appConfigFile of fs . readdirSync ( paths . NGINX _APPCONFIG _DIR ) ) {
2018-12-13 22:36:11 -08:00
if ( appConfigFile !== constants . NGINX _DEFAULT _CONFIG _FILE _NAME && ! appConfigFile . startsWith ( constants . ADMIN _LOCATION ) ) {
2018-11-10 22:02:42 -08:00
fs . unlinkSync ( path . join ( paths . NGINX _APPCONFIG _DIR , appConfigFile ) ) ;
}
2018-01-30 12:23:27 -08:00
}
}
2020-09-23 22:13:02 -07:00
function writeDefaultConfig ( options , callback ) {
assert . strictEqual ( typeof options , 'object' ) ;
2018-11-10 18:21:15 -08:00
assert . strictEqual ( typeof callback , 'function' ) ;
2018-01-30 12:23:27 -08:00
2020-09-23 22:13:02 -07:00
const certFilePath = path . join ( paths . NGINX _CERT _DIR , 'default.cert' ) ;
const keyFilePath = path . join ( paths . NGINX _CERT _DIR , 'default.key' ) ;
2018-01-30 12:23:27 -08:00
if ( ! fs . existsSync ( certFilePath ) || ! fs . existsSync ( keyFilePath ) ) {
2019-09-30 15:28:05 -07:00
debug ( 'writeDefaultConfig: create new cert' ) ;
2018-01-30 12:23:27 -08:00
2020-09-23 22:13:02 -07:00
const cn = 'cloudron-' + ( new Date ( ) ) . toISOString ( ) ; // randomize date a bit to keep firefox happy
2020-10-08 14:38:52 -07:00
// the days field is chosen to be less than 825 days per apple requirement (https://support.apple.com/en-us/HT210176)
if ( ! safe . child _process . execSync ( ` openssl req -x509 -newkey rsa:2048 -keyout ${ keyFilePath } -out ${ certFilePath } -days 800 -subj /CN= ${ cn } -nodes ` ) ) {
2019-09-30 15:28:05 -07:00
debug ( ` writeDefaultConfig: could not generate certificate: ${ safe . error . message } ` ) ;
2019-10-24 10:28:38 -07:00
return callback ( new BoxError ( BoxError . OPENSSL _ERROR , safe . error ) ) ;
2018-11-23 11:39:00 -08:00
}
2018-01-30 12:23:27 -08:00
}
2020-09-23 22:13:02 -07:00
const data = {
sourceDir : path . resolve ( _ _dirname , '..' ) ,
adminOrigin : settings . adminOrigin ( ) ,
vhost : '' ,
hasIPv6 : sysinfo . hasIPv6 ( ) ,
endpoint : options . activated ? 'ip' : 'setup' ,
certFilePath ,
keyFilePath ,
2020-11-09 20:34:48 -08:00
robotsTxtQuoted : JSON . stringify ( 'User-agent: *\nDisallow: /\n' ) ,
2021-01-08 14:10:11 -08:00
proxyAuth : { enabled : false , id : null , location : nginxLocation ( '/' ) }
2020-09-23 22:13:02 -07:00
} ;
const nginxConf = ejs . render ( NGINX _APPCONFIG _EJS , data ) ;
const nginxConfigFilename = path . join ( paths . NGINX _APPCONFIG _DIR , constants . NGINX _DEFAULT _CONFIG _FILE _NAME ) ;
2020-08-13 14:00:55 -07:00
2020-10-07 14:47:51 -07:00
debug ( ` writeDefaultConfig: writing configs for endpoint " ${ data . endpoint } " ` ) ;
2020-09-23 22:13:02 -07:00
if ( ! safe . fs . writeFileSync ( nginxConfigFilename , nginxConf ) ) return callback ( new BoxError ( BoxError . FS _ERROR , safe . error ) ) ;
2020-08-13 14:00:55 -07:00
reload ( callback ) ;
}