2023-03-21 16:53:39 +10:00
const _ = require ( 'lodash' );
const fs = require ( 'fs' );
const https = require ( 'https' );
const tempWrite = require ( 'temp-write' );
const moment = require ( 'moment' );
const logger = require ( '../logger' ). ssl ;
const config = require ( '../lib/config' );
const error = require ( '../lib/error' );
const utils = require ( '../lib/utils' );
const certificateModel = require ( '../models/certificate' );
const dnsPlugins = require ( '../global/certbot-dns-plugins' );
const internalAuditLog = require ( './audit-log' );
const internalNginx = require ( './nginx' );
const internalHost = require ( './host' );
const archiver = require ( 'archiver' );
const path = require ( 'path' );
const { isArray } = require ( 'lodash' );
const letsencryptStaging = config . useLetsencryptStaging ();
2021-07-01 09:57:26 +10:00
const letsencryptConfig = '/etc/letsencrypt.ini' ;
const certbotCommand = 'certbot' ;
2020-02-19 15:55:06 +11:00
function omissions () {
return [ 'is_deleted' ];
}
const internalCertificate = {
2021-07-01 09:57:26 +10:00
allowedSslFiles : [ 'certificate' , 'certificate_key' , 'intermediate_certificate' ],
intervalTimeout : 1000 * 60 * 60 , // 1 hour
interval : null ,
intervalProcessing : false ,
2020-02-19 15:55:06 +11:00
initTimer : () => {
logger . info ( 'Let\'s Encrypt Renewal Timer initialized' );
2021-07-01 09:57:26 +10:00
internalCertificate . interval = setInterval ( internalCertificate . processExpiringHosts , internalCertificate . intervalTimeout );
2020-02-19 15:55:06 +11:00
// And do this now as well
internalCertificate . processExpiringHosts ();
},
/**
* Triggered by a timer, this will check for expiring hosts and renew their ssl certs if required
*/
processExpiringHosts : () => {
2021-07-01 09:57:26 +10:00
if ( ! internalCertificate . intervalProcessing ) {
internalCertificate . intervalProcessing = true ;
2020-02-19 15:55:06 +11:00
logger . info ( 'Renewing SSL certs close to expiry...' );
2021-07-01 09:57:26 +10:00
const cmd = certbotCommand + ' renew --non-interactive --quiet ' +
'--config "' + letsencryptConfig + '" ' +
2023-03-20 16:56:52 +10:00
'--work-dir "/tmp/letsencrypt-lib" ' +
'--logs-dir "/tmp/letsencrypt-log" ' +
2020-02-19 15:55:06 +11:00
'--preferred-challenges "dns,http" ' +
'--disable-hook-validation ' +
2021-07-01 09:57:26 +10:00
( letsencryptStaging ? '--staging' : '' );
2020-02-19 15:55:06 +11:00
return utils . exec ( cmd )
. then (( result ) => {
if ( result ) {
logger . info ( 'Renew Result: ' + result );
}
return internalNginx . reload ()
. then (() => {
logger . info ( 'Renew Complete' );
return result ;
});
})
. then (() => {
// Now go and fetch all the letsencrypt certs from the db and query the files and update expiry times
return certificateModel
. query ()
. where ( 'is_deleted' , 0 )
. andWhere ( 'provider' , 'letsencrypt' )
. then (( certificates ) => {
if ( certificates && certificates . length ) {
let promises = [];
certificates . map ( function ( certificate ) {
promises . push (
internalCertificate . getCertificateInfoFromFile ( '/etc/letsencrypt/live/npm-' + certificate . id + '/fullchain.pem' )
. then (( cert_info ) => {
return certificateModel
. query ()
. where ( 'id' , certificate . id )
. andWhere ( 'provider' , 'letsencrypt' )
. patch ({
2020-08-14 19:23:19 -04:00
expires_on : moment ( cert_info . dates . to , 'X' ). format ( 'YYYY-MM-DD HH:mm:ss' )
2020-02-19 15:55:06 +11:00
});
})
. catch (( err ) => {
// Don't want to stop the train here, just log the error
logger . error ( err . message );
})
);
});
return Promise . all ( promises );
}
});
})
. then (() => {
2021-07-01 09:57:26 +10:00
internalCertificate . intervalProcessing = false ;
2020-02-19 15:55:06 +11:00
})
. catch (( err ) => {
logger . error ( err );
2021-07-01 09:57:26 +10:00
internalCertificate . intervalProcessing = false ;
2020-02-19 15:55:06 +11:00
});
}
},
/**
* @param {Access} access
* @param {Object} data
* @returns {Promise}
*/
create : ( access , data ) => {
return access . can ( 'certificates:create' , data )
. then (() => {
data . owner_user_id = access . token . getUserId ( 1 );
if ( data . provider === 'letsencrypt' ) {
2021-10-12 16:18:11 +02:00
data . nice_name = data . domain_names . join ( ', ' );
2020-02-19 15:55:06 +11:00
}
return certificateModel
. query ()
2023-03-17 14:18:51 +10:00
. insertAndFetch ( data )
. then ( utils . omitRow ( omissions ()));
2020-02-19 15:55:06 +11:00
})
. then (( certificate ) => {
if ( certificate . provider === 'letsencrypt' ) {
// Request a new Cert from LE. Let the fun begin.
// 1. Find out any hosts that are using any of the hostnames in this cert
// 2. Disable them in nginx temporarily
// 3. Generate the LE config
// 4. Request cert
// 5. Remove LE config
// 6. Re-instate previously disabled hosts
// 1. Find out any hosts that are using any of the hostnames in this cert
return internalHost . getHostsWithDomains ( certificate . domain_names )
. then (( in_use_result ) => {
// 2. Disable them in nginx temporarily
return internalCertificate . disableInUseHosts ( in_use_result )
. then (() => {
return in_use_result ;
});
})
. then (( in_use_result ) => {
2020-10-06 14:52:06 +02:00
// With DNS challenge no config is needed, so skip 3 and 5.
if ( certificate . meta . dns_challenge ) {
2020-08-23 13:24:20 +00:00
return internalNginx . reload (). then (() => {
2020-02-19 15:55:06 +11:00
// 4. Request cert
2020-10-06 14:52:06 +02:00
return internalCertificate . requestLetsEncryptSslWithDnsChallenge ( certificate );
2020-02-19 15:55:06 +11:00
})
2020-08-23 18:56:25 +00:00
. then ( internalNginx . reload )
. then (() => {
// 6. Re-instate previously disabled hosts
return internalCertificate . enableInUseHosts ( in_use_result );
})
. then (() => {
return certificate ;
})
. catch (( err ) => {
// In the event of failure, revert things and throw err back
return internalCertificate . enableInUseHosts ( in_use_result )
. then ( internalNginx . reload )
. then (() => {
throw err ;
});
});
2020-08-23 13:24:20 +00:00
} else {
// 3. Generate the LE config
return internalNginx . generateLetsEncryptRequestConfig ( certificate )
. then ( internalNginx . reload )
2021-12-30 13:21:21 +01:00
. then ( async () => await new Promise (( r ) => setTimeout ( r , 5000 )))
2020-08-23 13:24:20 +00:00
. then (() => {
// 4. Request cert
return internalCertificate . requestLetsEncryptSsl ( certificate );
})
. then (() => {
// 5. Remove LE config
return internalNginx . deleteLetsEncryptRequestConfig ( certificate );
})
. then ( internalNginx . reload )
. then (() => {
// 6. Re-instate previously disabled hosts
return internalCertificate . enableInUseHosts ( in_use_result );
})
. then (() => {
return certificate ;
})
. catch (( err ) => {
// In the event of failure, revert things and throw err back
return internalNginx . deleteLetsEncryptRequestConfig ( certificate )
. then (() => {
return internalCertificate . enableInUseHosts ( in_use_result );
})
. then ( internalNginx . reload )
. then (() => {
throw err ;
});
});
}
2020-02-19 15:55:06 +11:00
})
. then (() => {
// At this point, the letsencrypt cert should exist on disk.
// Lets get the expiry date from the file and update the row silently
return internalCertificate . getCertificateInfoFromFile ( '/etc/letsencrypt/live/npm-' + certificate . id + '/fullchain.pem' )
. then (( cert_info ) => {
return certificateModel
. query ()
. patchAndFetchById ( certificate . id , {
2020-08-14 19:23:19 -04:00
expires_on : moment ( cert_info . dates . to , 'X' ). format ( 'YYYY-MM-DD HH:mm:ss' )
2020-02-19 15:55:06 +11:00
})
. then (( saved_row ) => {
// Add cert data for audit log
saved_row . meta = _ . assign ({}, saved_row . meta , {
letsencrypt_certificate : cert_info
});
return saved_row ;
});
});
2020-11-06 12:29:38 +01:00
}). catch ( async ( error ) => {
// Delete the certificate from the database if it was not created successfully
await certificateModel
. query ()
. deleteById ( certificate . id );
2021-07-01 09:57:26 +10:00
2020-11-06 12:29:38 +01:00
throw error ;
2020-02-19 15:55:06 +11:00
});
} else {
return certificate ;
}
}). then (( certificate ) => {
data . meta = _ . assign ({}, data . meta || {}, certificate . meta );
// Add to audit log
return internalAuditLog . add ( access , {
action : 'created' ,
object_type : 'certificate' ,
object_id : certificate . id ,
meta : data
})
. then (() => {
return certificate ;
});
});
},
/**
* @param {Access} access
* @param {Object} data
* @param {Number} data.id
* @param {String} [data.email]
* @param {String} [data.name]
* @return {Promise}
*/
update : ( access , data ) => {
return access . can ( 'certificates:update' , data . id )
. then (( /*access_data*/ ) => {
return internalCertificate . get ( access , { id : data . id });
})
. then (( row ) => {
if ( row . id !== data . id ) {
// Sanity check that something crazy hasn't happened
throw new error . InternalValidationError ( 'Certificate could not be updated, IDs do not match: ' + row . id + ' !== ' + data . id );
}
return certificateModel
. query ()
. patchAndFetchById ( row . id , data )
2023-03-17 14:18:51 +10:00
. then ( utils . omitRow ( omissions ()))
2020-02-19 15:55:06 +11:00
. then (( saved_row ) => {
saved_row . meta = internalCertificate . cleanMeta ( saved_row . meta );
data . meta = internalCertificate . cleanMeta ( data . meta );
// Add row.nice_name for custom certs
if ( saved_row . provider === 'other' ) {
data . nice_name = saved_row . nice_name ;
}
// Add to audit log
return internalAuditLog . add ( access , {
action : 'updated' ,
object_type : 'certificate' ,
object_id : row . id ,
meta : _ . omit ( data , [ 'expires_on' ]) // this prevents json circular reference because expires_on might be raw
})
. then (() => {
2023-03-17 14:18:51 +10:00
return saved_row ;
2020-02-19 15:55:06 +11:00
});
});
});
},
/**
* @param {Access} access
* @param {Object} data
* @param {Number} data.id
* @param {Array} [data.expand]
* @param {Array} [data.omit]
* @return {Promise}
*/
get : ( access , data ) => {
if ( typeof data === 'undefined' ) {
data = {};
}
return access . can ( 'certificates:get' , data . id )
. then (( access_data ) => {
let query = certificateModel
. query ()
. where ( 'is_deleted' , 0 )
. andWhere ( 'id' , data . id )
2023-03-17 14:18:51 +10:00
. allowGraph ( '[owner]' )
2020-02-19 15:55:06 +11:00
. first ();
if ( access_data . permission_visibility !== 'all' ) {
query . andWhere ( 'owner_user_id' , access . token . getUserId ( 1 ));
}
if ( typeof data . expand !== 'undefined' && data . expand !== null ) {
2023-03-17 14:18:51 +10:00
query . withGraphFetched ( '[' + data . expand . join ( ', ' ) + ']' );
2020-02-19 15:55:06 +11:00
}
2023-03-17 14:18:51 +10:00
return query . then ( utils . omitRow ( omissions ()));
2020-02-19 15:55:06 +11:00
})
. then (( row ) => {
2023-03-17 14:18:51 +10:00
if ( ! row ) {
2020-02-19 15:55:06 +11:00
throw new error . ItemNotFoundError ( data . id );
}
2023-03-17 14:18:51 +10:00
// Custom omissions
if ( typeof data . omit !== 'undefined' && data . omit !== null ) {
row = _ . omit ( row , data . omit );
}
return row ;
2020-02-19 15:55:06 +11:00
});
},
2021-08-23 09:17:42 +05:30
/**
2021-08-24 06:01:08 +05:30
* @param {Access} access
2021-08-23 09:03:24 +05:30
* @param {Object} data
* @param {Number} data.id
* @returns {Promise}
*/
2021-08-24 06:01:08 +05:30
download : ( access , data ) => {
2021-08-23 09:17:42 +05:30
return new Promise (( resolve , reject ) => {
2021-08-24 06:01:08 +05:30
access . can ( 'certificates:get' , data )
2021-08-23 09:17:42 +05:30
. then (() => {
2021-08-24 06:01:08 +05:30
return internalCertificate . get ( access , data );
})
. then (( certificate ) => {
if ( certificate . provider === 'letsencrypt' ) {
2021-09-01 11:41:27 +05:30
const zipDirectory = '/etc/letsencrypt/live/npm-' + data . id ;
2021-08-24 06:01:08 +05:30
if ( ! fs . existsSync ( zipDirectory )) {
throw new error . ItemNotFoundError ( 'Certificate ' + certificate . nice_name + ' does not exists' );
}
2021-09-01 11:41:27 +05:30
let certFiles = fs . readdirSync ( zipDirectory )
. filter (( fn ) => fn . endsWith ( '.pem' ))
. map (( fn ) => fs . realpathSync ( path . join ( zipDirectory , fn )));
2021-08-24 06:01:08 +05:30
const downloadName = 'npm-' + data . id + '-' + ` ${ Date . now () } .zip` ;
const opName = '/tmp/' + downloadName ;
2021-09-01 11:41:27 +05:30
internalCertificate . zipFiles ( certFiles , opName )
2021-08-24 06:01:08 +05:30
. then (() => {
logger . debug ( 'zip completed : ' , opName );
const resp = {
fileName : opName
};
resolve ( resp );
2021-09-01 11:41:27 +05:30
}). catch (( err ) => reject ( err ));
2021-08-24 06:01:08 +05:30
} else {
2021-08-30 16:36:13 +05:30
throw new error . ValidationError ( 'Only Let\'sEncrypt certificates can be downloaded' );
2021-08-24 06:01:08 +05:30
}
}). catch (( err ) => reject ( err ));
2021-08-23 09:17:42 +05:30
});
},
2021-08-24 06:01:08 +05:30
2021-08-23 09:17:42 +05:30
/**
2021-08-24 07:28:17 +05:30
* @param {String} source
* @param {String} out
* @returns {Promise}
*/
2021-09-01 11:41:27 +05:30
zipFiles ( source , out ) {
2021-08-23 09:17:42 +05:30
const archive = archiver ( 'zip' , { zlib : { level : 9 } });
const stream = fs . createWriteStream ( out );
2022-02-12 12:46:06 +07:00
2021-08-23 09:17:42 +05:30
return new Promise (( resolve , reject ) => {
2021-09-01 11:41:27 +05:30
source
. map (( fl ) => {
let fileName = path . basename ( fl );
2021-09-01 11:50:51 +05:30
logger . debug ( fl , 'added to certificate zip' );
2021-09-01 11:41:27 +05:30
archive . file ( fl , { name : fileName });
});
2021-08-23 09:17:42 +05:30
archive
. on ( 'error' , ( err ) => reject ( err ))
. pipe ( stream );
2022-02-12 12:46:06 +07:00
2021-08-23 09:17:42 +05:30
stream . on ( 'close' , () => resolve ());
archive . finalize ();
});
},
2021-09-01 11:41:27 +05:30
2020-02-19 15:55:06 +11:00
/**
* @param {Access} access
* @param {Object} data
* @param {Number} data.id
* @param {String} [data.reason]
* @returns {Promise}
*/
delete : ( access , data ) => {
return access . can ( 'certificates:delete' , data . id )
. then (() => {
return internalCertificate . get ( access , { id : data . id });
})
. then (( row ) => {
if ( ! row ) {
throw new error . ItemNotFoundError ( data . id );
}
return certificateModel
. query ()
. where ( 'id' , row . id )
. patch ({
is_deleted : 1
})
. then (() => {
// Add to audit log
row . meta = internalCertificate . cleanMeta ( row . meta );
return internalAuditLog . add ( access , {
action : 'deleted' ,
object_type : 'certificate' ,
object_id : row . id ,
meta : _ . omit ( row , omissions ())
});
})
. then (() => {
if ( row . provider === 'letsencrypt' ) {
// Revoke the cert
return internalCertificate . revokeLetsEncryptSsl ( row );
}
});
})
. then (() => {
return true ;
});
},
/**
* All Certs
*
* @param {Access} access
* @param {Array} [expand]
* @param {String} [search_query]
* @returns {Promise}
*/
getAll : ( access , expand , search_query ) => {
return access . can ( 'certificates:list' )
. then (( access_data ) => {
let query = certificateModel
. query ()
. where ( 'is_deleted' , 0 )
. groupBy ( 'id' )
2023-03-17 14:18:51 +10:00
. allowGraph ( '[owner]' )
2020-02-19 15:55:06 +11:00
. orderBy ( 'nice_name' , 'ASC' );
if ( access_data . permission_visibility !== 'all' ) {
query . andWhere ( 'owner_user_id' , access . token . getUserId ( 1 ));
}
// Query is used for searching
if ( typeof search_query === 'string' ) {
query . where ( function () {
2022-02-12 12:46:06 +07:00
this . where ( 'nice_name' , 'like' , '%' + search_query + '%' );
2020-02-19 15:55:06 +11:00
});
}
if ( typeof expand !== 'undefined' && expand !== null ) {
2023-03-17 14:18:51 +10:00
query . withGraphFetched ( '[' + expand . join ( ', ' ) + ']' );
2020-02-19 15:55:06 +11:00
}
2023-03-17 14:18:51 +10:00
return query . then ( utils . omitRows ( omissions ()));
2020-02-19 15:55:06 +11:00
});
},
/**
* Report use
*
* @param {Number} user_id
* @param {String} visibility
* @returns {Promise}
*/
getCount : ( user_id , visibility ) => {
let query = certificateModel
. query ()
. count ( 'id as count' )
. where ( 'is_deleted' , 0 );
if ( visibility !== 'all' ) {
query . andWhere ( 'owner_user_id' , user_id );
}
return query . first ()
. then (( row ) => {
return parseInt ( row . count , 10 );
});
},
/**
* @param {Object} certificate
* @returns {Promise}
*/
writeCustomCert : ( certificate ) => {
2021-07-01 09:57:26 +10:00
logger . info ( 'Writing Custom Certificate:' , certificate );
2020-02-19 15:55:06 +11:00
2021-07-01 09:57:26 +10:00
const dir = '/data/custom_ssl/npm-' + certificate . id ;
2020-02-19 15:55:06 +11:00
return new Promise (( resolve , reject ) => {
if ( certificate . provider === 'letsencrypt' ) {
reject ( new Error ( 'Refusing to write letsencrypt certs here' ));
return ;
}
2021-07-01 09:57:26 +10:00
let certData = certificate . meta . certificate ;
2020-02-19 15:55:06 +11:00
if ( typeof certificate . meta . intermediate_certificate !== 'undefined' ) {
2021-07-01 09:57:26 +10:00
certData = certData + '\n' + certificate . meta . intermediate_certificate ;
2020-02-19 15:55:06 +11:00
}
try {
if ( ! fs . existsSync ( dir )) {
fs . mkdirSync ( dir );
}
} catch ( err ) {
reject ( err );
return ;
}
2021-07-01 09:57:26 +10:00
fs . writeFile ( dir + '/fullchain.pem' , certData , function ( err ) {
2020-02-19 15:55:06 +11:00
if ( err ) {
reject ( err );
} else {
resolve ();
}
});
})
. then (() => {
return new Promise (( resolve , reject ) => {
fs . writeFile ( dir + '/privkey.pem' , certificate . meta . certificate_key , function ( err ) {
if ( err ) {
reject ( err );
} else {
resolve ();
}
});
});
});
},
/**
* @param {Access} access
* @param {Object} data
* @param {Array} data.domain_names
* @param {String} data.meta.letsencrypt_email
* @param {Boolean} data.meta.letsencrypt_agree
* @returns {Promise}
*/
createQuickCertificate : ( access , data ) => {
return internalCertificate . create ( access , {
provider : 'letsencrypt' ,
domain_names : data . domain_names ,
meta : data . meta
});
},
/**
* Validates that the certs provided are good.
* No access required here, nothing is changed or stored.
*
* @param {Object} data
* @param {Object} data.files
* @returns {Promise}
*/
validate : ( data ) => {
return new Promise (( resolve ) => {
// Put file contents into an object
let files = {};
_ . map ( data . files , ( file , name ) => {
2021-07-01 09:57:26 +10:00
if ( internalCertificate . allowedSslFiles . indexOf ( name ) !== - 1 ) {
2020-02-19 15:55:06 +11:00
files [ name ] = file . data . toString ();
}
});
resolve ( files );
})
. then (( files ) => {
// For each file, create a temp file and write the contents to it
// Then test it depending on the file type
let promises = [];
_ . map ( files , ( content , type ) => {
promises . push ( new Promise (( resolve ) => {
if ( type === 'certificate_key' ) {
resolve ( internalCertificate . checkPrivateKey ( content ));
} else {
// this should handle `certificate` and intermediate certificate
resolve ( internalCertificate . getCertificateInfo ( content , true ));
}
}). then (( res ) => {
return {[ type ] : res };
}));
});
return Promise . all ( promises )
. then (( files ) => {
let data = {};
_ . each ( files , ( file ) => {
data = _ . assign ({}, data , file );
});
return data ;
});
});
},
/**
* @param {Access} access
* @param {Object} data
* @param {Number} data.id
* @param {Object} data.files
* @returns {Promise}
*/
upload : ( access , data ) => {
return internalCertificate . get ( access , { id : data . id })
. then (( row ) => {
if ( row . provider !== 'other' ) {
throw new error . ValidationError ( 'Cannot upload certificates for this type of provider' );
}
return internalCertificate . validate ( data )
. then (( validations ) => {
if ( typeof validations . certificate === 'undefined' ) {
throw new error . ValidationError ( 'Certificate file was not provided' );
}
_ . map ( data . files , ( file , name ) => {
2021-07-01 09:57:26 +10:00
if ( internalCertificate . allowedSslFiles . indexOf ( name ) !== - 1 ) {
2020-02-19 15:55:06 +11:00
row . meta [ name ] = file . data . toString ();
}
});
// TODO: This uses a mysql only raw function that won't translate to postgres
return internalCertificate . update ( access , {
id : data . id ,
2020-08-14 19:23:19 -04:00
expires_on : moment ( validations . certificate . dates . to , 'X' ). format ( 'YYYY-MM-DD HH:mm:ss' ),
2020-02-19 15:55:06 +11:00
domain_names : [ validations . certificate . cn ],
meta : _ . clone ( row . meta ) // Prevent the update method from changing this value that we'll use later
})
. then (( certificate ) => {
certificate . meta = row . meta ;
return internalCertificate . writeCustomCert ( certificate );
});
})
. then (() => {
2021-07-01 09:57:26 +10:00
return _ . pick ( row . meta , internalCertificate . allowedSslFiles );
2020-02-19 15:55:06 +11:00
});
});
},
/**
* Uses the openssl command to validate the private key.
* It will save the file to disk first, then run commands on it, then delete the file.
*
* @param {String} private_key This is the entire key contents as a string
*/
checkPrivateKey : ( private_key ) => {
return tempWrite ( private_key , '/tmp' )
. then (( filepath ) => {
2020-12-14 12:08:39 +01:00
return new Promise (( resolve , reject ) => {
const failTimeout = setTimeout (() => {
reject ( new error . ValidationError ( 'Result Validation Error: Validation timed out. This could be due to the key being passphrase-protected.' ));
}, 10000 );
utils
. exec ( 'openssl pkey -in ' + filepath + ' -check -noout 2>&1 ' )
. then (( result ) => {
clearTimeout ( failTimeout );
if ( ! result . toLowerCase (). includes ( 'key is valid' )) {
reject ( new error . ValidationError ( 'Result Validation Error: ' + result ));
}
fs . unlinkSync ( filepath );
resolve ( true );
})
. catch (( err ) => {
clearTimeout ( failTimeout );
fs . unlinkSync ( filepath );
reject ( new error . ValidationError ( 'Certificate Key is not valid (' + err . message + ')' , err ));
});
});
2020-02-19 15:55:06 +11:00
});
},
/**
* Uses the openssl command to both validate and get info out of the certificate.
* It will save the file to disk first, then run commands on it, then delete the file.
*
* @param {String} certificate This is the entire cert contents as a string
* @param {Boolean} [throw_expired] Throw when the certificate is out of date
*/
getCertificateInfo : ( certificate , throw_expired ) => {
return tempWrite ( certificate , '/tmp' )
. then (( filepath ) => {
return internalCertificate . getCertificateInfoFromFile ( filepath , throw_expired )
2021-07-01 09:57:26 +10:00
. then (( certData ) => {
2020-02-19 15:55:06 +11:00
fs . unlinkSync ( filepath );
2021-07-01 09:57:26 +10:00
return certData ;
2020-02-19 15:55:06 +11:00
}). catch (( err ) => {
fs . unlinkSync ( filepath );
throw err ;
});
});
},
/**
* Uses the openssl command to both validate and get info out of the certificate.
* It will save the file to disk first, then run commands on it, then delete the file.
*
* @param {String} certificate_file The file location on disk
* @param {Boolean} [throw_expired] Throw when the certificate is out of date
*/
getCertificateInfoFromFile : ( certificate_file , throw_expired ) => {
2021-07-01 09:57:26 +10:00
let certData = {};
2020-02-19 15:55:06 +11:00
return utils . exec ( 'openssl x509 -in ' + certificate_file + ' -subject -noout' )
. then (( result ) => {
// subject=CN = something.example.com
2021-07-01 09:57:26 +10:00
const regex = /(?:subject=)?[^=]+=\s+(\S+)/gim ;
const match = regex . exec ( result );
2020-02-19 15:55:06 +11:00
if ( typeof match [ 1 ] === 'undefined' ) {
throw new error . ValidationError ( 'Could not determine subject from certificate: ' + result );
}
2021-07-01 09:57:26 +10:00
certData [ 'cn' ] = match [ 1 ];
2020-02-19 15:55:06 +11:00
})
. then (() => {
return utils . exec ( 'openssl x509 -in ' + certificate_file + ' -issuer -noout' );
})
. then (( result ) => {
// issuer=C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
2021-07-01 09:57:26 +10:00
const regex = /^(?:issuer=)?(.*)$/gim ;
const match = regex . exec ( result );
2020-02-19 15:55:06 +11:00
if ( typeof match [ 1 ] === 'undefined' ) {
throw new error . ValidationError ( 'Could not determine issuer from certificate: ' + result );
}
2021-07-01 09:57:26 +10:00
certData [ 'issuer' ] = match [ 1 ];
2020-02-19 15:55:06 +11:00
})
. then (() => {
return utils . exec ( 'openssl x509 -in ' + certificate_file + ' -dates -noout' );
})
. then (( result ) => {
// notBefore=Jul 14 04:04:29 2018 GMT
// notAfter=Oct 12 04:04:29 2018 GMT
2021-07-01 09:57:26 +10:00
let validFrom = null ;
let validTo = null ;
2020-02-19 15:55:06 +11:00
2021-07-01 09:57:26 +10:00
const lines = result . split ( '\n' );
2020-02-19 15:55:06 +11:00
lines . map ( function ( str ) {
2021-07-01 09:57:26 +10:00
const regex = /^(\S+)=(.*)$/gim ;
const match = regex . exec ( str . trim ());
2020-02-19 15:55:06 +11:00
if ( match && typeof match [ 2 ] !== 'undefined' ) {
2021-07-01 09:57:26 +10:00
const date = parseInt ( moment ( match [ 2 ], 'MMM DD HH:mm:ss YYYY z' ). format ( 'X' ), 10 );
2020-02-19 15:55:06 +11:00
if ( match [ 1 ]. toLowerCase () === 'notbefore' ) {
2021-07-01 09:57:26 +10:00
validFrom = date ;
2020-02-19 15:55:06 +11:00
} else if ( match [ 1 ]. toLowerCase () === 'notafter' ) {
2021-07-01 09:57:26 +10:00
validTo = date ;
2020-02-19 15:55:06 +11:00
}
}
});
2021-07-01 09:57:26 +10:00
if ( ! validFrom || ! validTo ) {
2020-02-19 15:55:06 +11:00
throw new error . ValidationError ( 'Could not determine dates from certificate: ' + result );
}
2021-07-01 09:57:26 +10:00
if ( throw_expired && validTo < parseInt ( moment (). format ( 'X' ), 10 )) {
2020-02-19 15:55:06 +11:00
throw new error . ValidationError ( 'Certificate has expired' );
}
2021-07-01 09:57:26 +10:00
certData [ 'dates' ] = {
from : validFrom ,
to : validTo
2020-02-19 15:55:06 +11:00
};
2021-07-01 09:57:26 +10:00
return certData ;
2020-02-19 15:55:06 +11:00
}). catch (( err ) => {
throw new error . ValidationError ( 'Certificate is not valid (' + err . message + ')' , err );
});
},
/**
* Cleans the ssl keys from the meta object and sets them to "true"
*
* @param {Object} meta
* @param {Boolean} [remove]
* @returns {Object}
*/
cleanMeta : function ( meta , remove ) {
2021-07-01 09:57:26 +10:00
internalCertificate . allowedSslFiles . map (( key ) => {
2020-02-19 15:55:06 +11:00
if ( typeof meta [ key ] !== 'undefined' && meta [ key ]) {
if ( remove ) {
delete meta [ key ];
} else {
meta [ key ] = true ;
}
}
});
return meta ;
},
/**
2021-08-06 10:56:06 +02:00
* Request a certificate using the http challenge
2020-02-19 15:55:06 +11:00
* @param {Object} certificate the certificate row
* @returns {Promise}
*/
requestLetsEncryptSsl : ( certificate ) => {
logger . info ( 'Requesting Let\'sEncrypt certificates for Cert #' + certificate . id + ': ' + certificate . domain_names . join ( ', ' ));
2021-11-06 22:31:13 +01:00
const cmd = certbotCommand + ' certonly ' +
2021-07-01 09:57:26 +10:00
'--config "' + letsencryptConfig + '" ' +
2023-03-20 16:56:52 +10:00
'--work-dir "/tmp/letsencrypt-lib" ' +
'--logs-dir "/tmp/letsencrypt-log" ' +
2020-02-19 15:55:06 +11:00
'--cert-name "npm-' + certificate . id + '" ' +
'--agree-tos ' +
2021-08-06 10:56:06 +02:00
'--authenticator webroot ' +
2020-02-19 15:55:06 +11:00
'--email "' + certificate . meta . letsencrypt_email + '" ' +
'--preferred-challenges "dns,http" ' +
'--domains "' + certificate . domain_names . join ( ',' ) + '" ' +
2021-07-01 09:57:26 +10:00
( letsencryptStaging ? '--staging' : '' );
2020-02-19 15:55:06 +11:00
2021-07-01 09:57:26 +10:00
logger . info ( 'Command:' , cmd );
2020-02-19 15:55:06 +11:00
return utils . exec ( cmd )
. then (( result ) => {
logger . success ( result );
return result ;
});
},
2020-08-23 12:50:41 +00:00
/**
2021-07-01 09:57:26 +10:00
* @param {Object} certificate the certificate row
* @param {String} dns_provider the dns provider name (key used in `certbot-dns-plugins.js`)
* @param {String | null} credentials the content of this providers credentials file
* @param {String} propagation_seconds the cloudflare api token
2020-08-23 12:50:41 +00:00
* @returns {Promise}
*/
2020-10-06 14:52:06 +02:00
requestLetsEncryptSslWithDnsChallenge : ( certificate ) => {
2021-07-01 09:57:26 +10:00
const dns_plugin = dnsPlugins [ certificate . meta . dns_provider ];
2020-10-06 14:52:06 +02:00
2020-10-08 14:23:21 +02:00
if ( ! dns_plugin ) {
throw Error ( `Unknown DNS provider ' ${ certificate . meta . dns_provider } '` );
2020-10-06 14:52:06 +02:00
}
logger . info ( `Requesting Let'sEncrypt certificates via ${ dns_plugin . display_name } for Cert # ${ certificate . id } : ${ certificate . domain_names . join ( ', ' ) } ` );
2020-08-23 12:50:41 +00:00
2021-07-01 09:57:26 +10:00
const credentialsLocation = '/etc/letsencrypt/credentials/credentials-' + certificate . id ;
2021-12-29 16:30:49 +01:00
// Escape single quotes and backslashes
const escapedCredentials = certificate . meta . dns_provider_credentials . replaceAll ( '\'' , '\\\'' ). replaceAll ( '\\' , '\\\\' );
const credentialsCmd = 'mkdir -p /etc/letsencrypt/credentials 2> /dev/null; echo \'' + escapedCredentials + '\' > \'' + credentialsLocation + '\' && chmod 600 \'' + credentialsLocation + '\'' ;
2022-11-18 15:06:19 +03:00
// we call `. /opt/certbot/bin/activate` (`.` is alternative to `source` in dash) to access certbot venv
2023-03-22 09:41:59 +10:00
const prepareCmd = '. /opt/certbot/bin/activate && pip install --no-cache-dir --user ' + dns_plugin . package_name + ( dns_plugin . version_requirement || '' ) + ' ' + dns_plugin . dependencies + ' && deactivate' ;
2020-08-23 12:50:41 +00:00
2020-10-14 09:20:52 +02:00
// Whether the plugin has a --<name>-credentials argument
2021-07-01 09:57:26 +10:00
const hasConfigArg = certificate . meta . dns_provider !== 'route53' ;
2020-10-14 09:20:52 +02:00
2021-11-06 22:31:13 +01:00
let mainCmd = certbotCommand + ' certonly ' +
'--config "' + letsencryptConfig + '" ' +
2023-03-20 16:56:52 +10:00
'--work-dir "/tmp/letsencrypt-lib" ' +
'--logs-dir "/tmp/letsencrypt-log" ' +
2020-08-23 12:50:41 +00:00
'--cert-name "npm-' + certificate . id + '" ' +
'--agree-tos ' +
2021-07-01 09:57:26 +10:00
'--email "' + certificate . meta . letsencrypt_email + '" ' +
2020-08-23 12:50:41 +00:00
'--domains "' + certificate . domain_names . join ( ',' ) + '" ' +
2020-10-06 14:52:06 +02:00
'--authenticator ' + dns_plugin . full_plugin_name + ' ' +
2020-10-14 09:20:52 +02:00
(
2021-07-01 09:57:26 +10:00
hasConfigArg
? '--' + dns_plugin . full_plugin_name + '-credentials "' + credentialsLocation + '"'
2020-10-14 09:20:52 +02:00
: ''
) +
2020-10-06 14:52:06 +02:00
(
2021-07-01 09:57:26 +10:00
certificate . meta . propagation_seconds !== undefined
? ' --' + dns_plugin . full_plugin_name + '-propagation-seconds ' + certificate . meta . propagation_seconds
2020-10-08 14:23:21 +02:00
: ''
2020-10-06 14:52:06 +02:00
) +
2021-07-01 09:57:26 +10:00
( letsencryptStaging ? ' --staging' : '' );
2020-10-14 09:20:52 +02:00
2020-10-14 09:55:45 +02:00
// Prepend the path to the credentials file as an environment variable
if ( certificate . meta . dns_provider === 'route53' ) {
2021-07-01 09:57:26 +10:00
mainCmd = 'AWS_CONFIG_FILE=\'' + credentialsLocation + '\' ' + mainCmd ;
2020-10-14 09:55:45 +02:00
}
2020-08-23 12:50:41 +00:00
2021-07-01 09:57:26 +10:00
logger . info ( 'Command:' , ` ${ credentialsCmd } && ${ prepareCmd } && ${ mainCmd } ` );
2020-08-23 12:50:41 +00:00
2021-07-01 09:57:26 +10:00
return utils . exec ( credentialsCmd )
2020-10-06 14:52:06 +02:00
. then (() => {
2021-07-01 09:57:26 +10:00
return utils . exec ( prepareCmd )
2020-10-06 14:52:06 +02:00
. then (() => {
2021-07-01 09:57:26 +10:00
return utils . exec ( mainCmd )
2020-10-06 14:52:06 +02:00
. then ( async ( result ) => {
logger . info ( result );
return result ;
});
});
2020-10-17 12:13:08 +02:00
}). catch ( async ( err ) => {
// Don't fail if file does not exist
2021-07-01 09:57:26 +10:00
const delete_credentialsCmd = `rm -f ' ${ credentialsLocation } ' || true` ;
await utils . exec ( delete_credentialsCmd );
2020-10-17 12:13:08 +02:00
throw err ;
2020-10-06 14:52:06 +02:00
});
2020-08-23 12:50:41 +00:00
},
2020-02-19 15:55:06 +11:00
/**
* @param {Access} access
* @param {Object} data
* @param {Number} data.id
* @returns {Promise}
*/
renew : ( access , data ) => {
return access . can ( 'certificates:update' , data )
. then (() => {
return internalCertificate . get ( access , data );
})
. then (( certificate ) => {
if ( certificate . provider === 'letsencrypt' ) {
2021-07-01 09:57:26 +10:00
const renewMethod = certificate . meta . dns_challenge ? internalCertificate . renewLetsEncryptSslWithDnsChallenge : internalCertificate . renewLetsEncryptSsl ;
2020-08-23 18:29:16 +00:00
return renewMethod ( certificate )
2020-02-19 15:55:06 +11:00
. then (() => {
return internalCertificate . getCertificateInfoFromFile ( '/etc/letsencrypt/live/npm-' + certificate . id + '/fullchain.pem' );
})
. then (( cert_info ) => {
return certificateModel
. query ()
. patchAndFetchById ( certificate . id , {
2020-08-14 19:23:19 -04:00
expires_on : moment ( cert_info . dates . to , 'X' ). format ( 'YYYY-MM-DD HH:mm:ss' )
2020-02-19 15:55:06 +11:00
});
})
. then (( updated_certificate ) => {
// Add to audit log
return internalAuditLog . add ( access , {
action : 'renewed' ,
object_type : 'certificate' ,
object_id : updated_certificate . id ,
meta : updated_certificate
})
. then (() => {
return updated_certificate ;
});
});
} else {
throw new error . ValidationError ( 'Only Let\'sEncrypt certificates can be renewed' );
}
});
},
/**
* @param {Object} certificate the certificate row
* @returns {Promise}
*/
renewLetsEncryptSsl : ( certificate ) => {
logger . info ( 'Renewing Let\'sEncrypt certificates for Cert #' + certificate . id + ': ' + certificate . domain_names . join ( ', ' ));
2021-11-06 22:31:13 +01:00
const cmd = certbotCommand + ' renew --force-renewal ' +
2021-07-01 09:57:26 +10:00
'--config "' + letsencryptConfig + '" ' +
2023-03-22 13:40:36 +10:00
'--work-dir "/tmp/letsencrypt-lib" ' +
'--logs-dir "/tmp/letsencrypt-log" ' +
2020-02-19 15:55:06 +11:00
'--cert-name "npm-' + certificate . id + '" ' +
'--preferred-challenges "dns,http" ' +
2021-11-05 14:20:12 +01:00
'--no-random-sleep-on-renew ' +
2020-02-19 15:55:06 +11:00
'--disable-hook-validation ' +
2021-07-01 09:57:26 +10:00
( letsencryptStaging ? '--staging' : '' );
2020-02-19 15:55:06 +11:00
2021-07-01 09:57:26 +10:00
logger . info ( 'Command:' , cmd );
2020-02-19 15:55:06 +11:00
return utils . exec ( cmd )
. then (( result ) => {
logger . info ( result );
return result ;
});
},
2020-08-23 18:29:16 +00:00
/**
* @param {Object} certificate the certificate row
* @returns {Promise}
*/
2020-10-06 14:52:06 +02:00
renewLetsEncryptSslWithDnsChallenge : ( certificate ) => {
2021-07-01 09:57:26 +10:00
const dns_plugin = dnsPlugins [ certificate . meta . dns_provider ];
2020-08-23 18:29:16 +00:00
2020-10-08 14:23:21 +02:00
if ( ! dns_plugin ) {
throw Error ( `Unknown DNS provider ' ${ certificate . meta . dns_provider } '` );
2020-10-06 14:52:06 +02:00
}
logger . info ( `Renewing Let'sEncrypt certificates via ${ dns_plugin . display_name } for Cert # ${ certificate . id } : ${ certificate . domain_names . join ( ', ' ) } ` );
2021-11-06 22:31:13 +01:00
let mainCmd = certbotCommand + ' renew ' +
'--config "' + letsencryptConfig + '" ' +
2023-03-22 13:40:36 +10:00
'--work-dir "/tmp/letsencrypt-lib" ' +
'--logs-dir "/tmp/letsencrypt-log" ' +
2020-08-23 18:29:16 +00:00
'--cert-name "npm-' + certificate . id + '" ' +
2021-11-05 14:20:12 +01:00
'--disable-hook-validation ' +
'--no-random-sleep-on-renew ' +
2021-07-01 09:57:26 +10:00
( letsencryptStaging ? ' --staging' : '' );
2020-10-06 14:52:06 +02:00
2020-10-14 09:20:52 +02:00
// Prepend the path to the credentials file as an environment variable
if ( certificate . meta . dns_provider === 'route53' ) {
2021-07-01 09:57:26 +10:00
const credentialsLocation = '/etc/letsencrypt/credentials/credentials-' + certificate . id ;
mainCmd = 'AWS_CONFIG_FILE=\'' + credentialsLocation + '\' ' + mainCmd ;
2020-10-14 09:20:52 +02:00
}
2021-07-01 09:57:26 +10:00
logger . info ( 'Command:' , mainCmd );
2020-08-23 18:29:16 +00:00
2021-07-01 09:57:26 +10:00
return utils . exec ( mainCmd )
2020-10-17 12:13:08 +02:00
. then ( async ( result ) => {
logger . info ( result );
return result ;
2020-08-23 18:29:16 +00:00
});
},
2020-02-19 15:55:06 +11:00
/**
* @param {Object} certificate the certificate row
* @param {Boolean} [throw_errors]
* @returns {Promise}
*/
revokeLetsEncryptSsl : ( certificate , throw_errors ) => {
logger . info ( 'Revoking Let\'sEncrypt certificates for Cert #' + certificate . id + ': ' + certificate . domain_names . join ( ', ' ));
2021-11-06 22:31:13 +01:00
const mainCmd = certbotCommand + ' revoke ' +
'--config "' + letsencryptConfig + '" ' +
2020-02-19 15:55:06 +11:00
'--cert-path "/etc/letsencrypt/live/npm-' + certificate . id + '/fullchain.pem" ' +
'--delete-after-revoke ' +
2021-07-01 09:57:26 +10:00
( letsencryptStaging ? '--staging' : '' );
2020-02-19 15:55:06 +11:00
2020-10-17 12:13:08 +02:00
// Don't fail command if file does not exist
2021-07-01 09:57:26 +10:00
const delete_credentialsCmd = `rm -f '/etc/letsencrypt/credentials/credentials- ${ certificate . id } ' || true` ;
2020-10-17 12:13:08 +02:00
2021-07-01 09:57:26 +10:00
logger . info ( 'Command:' , mainCmd + '; ' + delete_credentialsCmd );
2020-02-19 15:55:06 +11:00
2021-07-01 09:57:26 +10:00
return utils . exec ( mainCmd )
2020-10-17 12:13:08 +02:00
. then ( async ( result ) => {
2021-07-01 09:57:26 +10:00
await utils . exec ( delete_credentialsCmd );
2020-02-19 15:55:06 +11:00
logger . info ( result );
return result ;
})
. catch (( err ) => {
2021-07-01 09:57:26 +10:00
logger . error ( err . message );
2020-02-19 15:55:06 +11:00
if ( throw_errors ) {
throw err ;
}
});
},
/**
* @param {Object} certificate
* @returns {Boolean}
*/
hasLetsEncryptSslCerts : ( certificate ) => {
2021-07-01 09:57:26 +10:00
const letsencryptPath = '/etc/letsencrypt/live/npm-' + certificate . id ;
2020-02-19 15:55:06 +11:00
2021-07-01 09:57:26 +10:00
return fs . existsSync ( letsencryptPath + '/fullchain.pem' ) && fs . existsSync ( letsencryptPath + '/privkey.pem' );
2020-02-19 15:55:06 +11:00
},
/**
* @param {Object} in_use_result
* @param {Number} in_use_result.total_count
* @param {Array} in_use_result.proxy_hosts
* @param {Array} in_use_result.redirection_hosts
* @param {Array} in_use_result.dead_hosts
*/
disableInUseHosts : ( in_use_result ) => {
if ( in_use_result . total_count ) {
let promises = [];
if ( in_use_result . proxy_hosts . length ) {
promises . push ( internalNginx . bulkDeleteConfigs ( 'proxy_host' , in_use_result . proxy_hosts ));
}
if ( in_use_result . redirection_hosts . length ) {
promises . push ( internalNginx . bulkDeleteConfigs ( 'redirection_host' , in_use_result . redirection_hosts ));
}
if ( in_use_result . dead_hosts . length ) {
promises . push ( internalNginx . bulkDeleteConfigs ( 'dead_host' , in_use_result . dead_hosts ));
}
return Promise . all ( promises );
} else {
return Promise . resolve ();
}
},
/**
* @param {Object} in_use_result
* @param {Number} in_use_result.total_count
* @param {Array} in_use_result.proxy_hosts
* @param {Array} in_use_result.redirection_hosts
* @param {Array} in_use_result.dead_hosts
*/
enableInUseHosts : ( in_use_result ) => {
if ( in_use_result . total_count ) {
let promises = [];
if ( in_use_result . proxy_hosts . length ) {
promises . push ( internalNginx . bulkGenerateConfigs ( 'proxy_host' , in_use_result . proxy_hosts ));
}
if ( in_use_result . redirection_hosts . length ) {
promises . push ( internalNginx . bulkGenerateConfigs ( 'redirection_host' , in_use_result . redirection_hosts ));
}
if ( in_use_result . dead_hosts . length ) {
promises . push ( internalNginx . bulkGenerateConfigs ( 'dead_host' , in_use_result . dead_hosts ));
}
return Promise . all ( promises );
} else {
return Promise . resolve ();
}
2021-10-31 00:19:18 +02:00
},
testHttpsChallenge : async ( access , domains ) => {
await access . can ( 'certificates:list' );
if ( ! isArray ( domains )) {
throw new error . InternalValidationError ( 'Domains must be an array of strings' );
}
if ( domains . length === 0 ) {
throw new error . InternalValidationError ( 'No domains provided' );
}
2022-02-12 12:46:06 +07:00
2021-10-31 00:19:18 +02:00
// Create a test challenge file
2021-10-31 00:28:43 +02:00
const testChallengeDir = '/data/letsencrypt-acme-challenge/.well-known/acme-challenge' ;
2021-10-31 00:19:18 +02:00
const testChallengeFile = testChallengeDir + '/test-challenge' ;
fs . mkdirSync ( testChallengeDir , { recursive : true });
fs . writeFileSync ( testChallengeFile , 'Success' , { encoding : 'utf8' });
async function performTestForDomain ( domain ) {
logger . info ( 'Testing http challenge for ' + domain );
const url = `http:// ${ domain } /.well-known/acme-challenge/test-challenge` ;
const formBody = `method=G&url= ${ encodeURI ( url ) } &bodytype=T&requestbody=&headername=User-Agent&headervalue=None&locationid=1&ch=false&cc=false` ;
const options = {
method : 'POST' ,
headers : {
'Content-Type' : 'application/x-www-form-urlencoded' ,
'Content-Length' : Buffer . byteLength ( formBody )
}
};
const result = await new Promise (( resolve ) => {
const req = https . request ( 'https://www.site24x7.com/tools/restapi-tester' , options , function ( res ) {
let responseBody = '' ;
res . on ( 'data' , ( chunk ) => responseBody = responseBody + chunk );
res . on ( 'end' , function () {
const parsedBody = JSON . parse ( responseBody + '' );
if ( res . statusCode !== 200 ) {
logger . warn ( `Failed to test HTTP challenge for domain ${ domain } ` , res );
resolve ( undefined );
}
resolve ( parsedBody );
});
});
// Make sure to write the request body.
req . write ( formBody );
req . end ();
req . on ( 'error' , function ( e ) { logger . warn ( `Failed to test HTTP challenge for domain ${ domain } ` , e );
resolve ( undefined ); });
});
if ( ! result ) {
// Some error occurred while trying to get the data
return 'failed' ;
} else if ( ` ${ result . responsecode } ` === '200' && result . htmlresponse === 'Success' ) {
// Server exists and has responded with the correct data
return 'ok' ;
2021-10-31 13:41:29 +01:00
} else if ( ` ${ result . responsecode } ` === '200' ) {
2021-11-04 11:16:23 +01:00
// Server exists but has responded with wrong data
logger . info ( `HTTP challenge test failed for domain ${ domain } because of invalid returned data:` , result . htmlresponse );
2021-10-31 13:41:29 +01:00
return 'wrong-data' ;
2021-10-31 00:19:18 +02:00
} else if ( ` ${ result . responsecode } ` === '404' ) {
// Server exists but responded with a 404
2021-11-04 11:16:23 +01:00
logger . info ( `HTTP challenge test failed for domain ${ domain } because code 404 was returned` );
2021-10-31 00:19:18 +02:00
return '404' ;
2021-10-31 13:41:29 +01:00
} else if ( ` ${ result . responsecode } ` === '0' || ( typeof result . reason === 'string' && result . reason . toLowerCase () === 'host unavailable' )) {
2021-10-31 00:19:18 +02:00
// Server does not exist at domain
2021-11-04 11:16:23 +01:00
logger . info ( `HTTP challenge test failed for domain ${ domain } the host was not found` );
2021-10-31 00:19:18 +02:00
return 'no-host' ;
} else {
// Other errors
2021-11-04 11:16:23 +01:00
logger . info ( `HTTP challenge test failed for domain ${ domain } because code ${ result . responsecode } was returned` );
2021-10-31 00:19:18 +02:00
return `other: ${ result . responsecode } ` ;
}
}
const results = {};
for ( const domain of domains ){
results [ domain ] = await performTestForDomain ( domain );
}
// Remove the test challenge file
fs . unlinkSync ( testChallengeFile );
2022-02-12 12:46:06 +07:00
2021-10-31 00:19:18 +02:00
return results ;
2020-02-19 15:55:06 +11:00
}
};
module . exports = internalCertificate ;