2018-01-12 22:41:26 +03:00
/ * *
2018-01-15 08:01:06 +03:00
* @ description MeshCentral letsEncrypt module , uses GreenLock to do all the work .
2018-01-12 22:41:26 +03:00
* @ author Ylian Saint - Hilaire
2019-01-04 03:22:15 +03:00
* @ copyright Intel Corporation 2018 - 2019
2018-01-12 22:41:26 +03:00
* @ license Apache - 2.0
2018-01-15 08:01:06 +03:00
* @ version v0 . 0.2
2018-01-12 22:41:26 +03:00
* /
2018-08-30 03:40:30 +03:00
/*xjslint node: true */
/*xjslint plusplus: true */
/*xjslint maxlen: 256 */
/*jshint node: true */
/*jshint strict: false */
/*jshint esversion: 6 */
"use strict" ;
2018-08-27 22:24:15 +03:00
2018-01-12 22:41:26 +03:00
module . exports . CreateLetsEncrypt = function ( parent ) {
2018-01-15 08:01:06 +03:00
try {
2019-01-12 01:01:36 +03:00
// Try to delete the "./ursa-optional" or "./node_modules/ursa-optional" folder if present.
// This is an optional module that GreenLock uses that causes issues.
try {
const fs = require ( 'fs' ) ;
if ( fs . existsSync ( obj . path . join ( _ _dirname , 'ursa-optional' ) ) ) { fs . unlinkSync ( obj . path . join ( _ _dirname , 'ursa-optional' ) ) ; }
if ( fs . existsSync ( obj . path . join ( _ _dirname , 'node_modules' , 'ursa-optional' ) ) ) { fs . unlinkSync ( obj . path . join ( _ _dirname , 'node_modules' , 'ursa-optional' ) ) ; }
} catch ( ex ) { }
2018-01-15 08:01:06 +03:00
2019-01-12 01:01:36 +03:00
// Get GreenLock setup and running.
const greenlock = require ( 'greenlock' ) ;
2018-01-15 08:01:06 +03:00
var obj = { } ;
obj . parent = parent ;
obj . redirWebServerHooked = false ;
obj . leDomains = null ;
obj . leResults = null ;
// Setup the certificate storage paths
obj . configPath = obj . parent . path . join ( obj . parent . datapath , 'letsencrypt' ) ;
obj . webrootPath = obj . parent . path . join ( obj . parent . datapath , 'letsencrypt' , 'webroot' ) ;
try { obj . parent . fs . mkdirSync ( obj . configPath ) ; } catch ( e ) { }
try { obj . parent . fs . mkdirSync ( obj . webrootPath ) ; } catch ( e ) { }
// Storage Backend, store data in the "meshcentral-data/letencrypt" folder.
var leStore = require ( 'le-store-certbot' ) . create ( { configDir : obj . configPath , webrootPath : obj . webrootPath , debug : obj . parent . args . debug > 0 } ) ;
// ACME Challenge Handlers
var leHttpChallenge = require ( 'le-challenge-fs' ) . create ( { webrootPath : obj . webrootPath , debug : obj . parent . args . debug > 0 } ) ;
// Function to agree to terms of service
function leAgree ( opts , agreeCb ) { agreeCb ( null , opts . tosUrl ) ; }
// Create the main GreenLock code module.
var greenlockargs = {
2018-09-25 21:51:40 +03:00
version : 'draft-12' ,
2019-01-06 01:15:14 +03:00
server : ( obj . parent . config . letsencrypt . production === true ) ? 'https://acme-v02.api.letsencrypt.org/directory' : 'https://acme-staging-v02.api.letsencrypt.org/directory' ,
2018-01-15 08:01:06 +03:00
store : leStore ,
challenges : { 'http-01' : leHttpChallenge } ,
challengeType : 'http-01' ,
agreeToTerms : leAgree ,
debug : obj . parent . args . debug > 0
2018-08-30 03:40:30 +03:00
} ;
if ( obj . parent . args . debug == null ) { greenlockargs . log = function ( debug ) { } ; } // If not in debug mode, ignore all console output from greenlock (makes things clean).
2018-01-15 08:01:06 +03:00
obj . le = greenlock . create ( greenlockargs ) ;
2018-01-12 22:41:26 +03:00
2018-01-15 08:01:06 +03:00
// Hook up GreenLock to the redirection server
if ( obj . parent . redirserver . port == 80 ) { obj . parent . redirserver . app . use ( '/' , obj . le . middleware ( ) ) ; obj . redirWebServerHooked = true ; }
2018-01-12 22:41:26 +03:00
2018-01-15 08:01:06 +03:00
obj . getCertificate = function ( certs , func ) {
2019-03-05 10:48:45 +03:00
if ( certs . CommonName . indexOf ( '.' ) == - 1 ) { console . log ( "ERROR: Use --cert to setup the default server name before using Let's Encrypt." ) ; func ( certs ) ; return ; }
2018-01-15 08:01:06 +03:00
if ( obj . parent . config . letsencrypt == null ) { func ( certs ) ; return ; }
if ( obj . parent . config . letsencrypt . email == null ) { console . log ( "ERROR: Let's Encrypt email address not specified." ) ; func ( certs ) ; return ; }
if ( ( obj . parent . redirserver == null ) || ( obj . parent . redirserver . port !== 80 ) ) { console . log ( "ERROR: Redirection web server must be active on port 80 for Let's Encrypt to work." ) ; func ( certs ) ; return ; }
if ( obj . redirWebServerHooked !== true ) { console . log ( "ERROR: Redirection web server not setup for Let's Encrypt to work." ) ; func ( certs ) ; return ; }
if ( ( obj . parent . config . letsencrypt . rsakeysize != null ) && ( obj . parent . config . letsencrypt . rsakeysize !== 2048 ) && ( obj . parent . config . letsencrypt . rsakeysize !== 3072 ) ) { console . log ( "ERROR: Invalid Let's Encrypt certificate key size, must be 2048 or 3072." ) ; func ( certs ) ; return ; }
2018-01-12 22:41:26 +03:00
2018-01-15 08:01:06 +03:00
// Get the list of domains
obj . leDomains = [ certs . CommonName ] ;
if ( obj . parent . config . letsencrypt . names != null ) {
if ( typeof obj . parent . config . letsencrypt . names == 'string' ) { obj . parent . config . letsencrypt . names = obj . parent . config . letsencrypt . names . split ( ',' ) ; }
2018-08-30 03:40:30 +03:00
obj . parent . config . letsencrypt . names . map ( function ( s ) { return s . trim ( ) ; } ) ; // Trim each name
2018-01-15 08:01:06 +03:00
if ( ( typeof obj . parent . config . letsencrypt . names != 'object' ) || ( obj . parent . config . letsencrypt . names . length == null ) ) { console . log ( "ERROR: Let's Encrypt names must be an array in config.json." ) ; func ( certs ) ; return ; }
obj . leDomains = obj . parent . config . letsencrypt . names ;
obj . leDomains . sort ( ) ; // Sort the array so it's always going to be in the same order.
}
2018-01-12 22:41:26 +03:00
2018-01-15 08:01:06 +03:00
obj . le . check ( { domains : obj . leDomains } ) . then ( function ( results ) {
if ( results ) {
obj . leResults = results ;
2018-01-12 22:41:26 +03:00
2018-01-15 08:01:06 +03:00
// If we already have real certificates, use them.
2018-12-20 23:12:24 +03:00
if ( results . altnames . indexOf ( certs . CommonName ) >= 0 ) {
certs . web . cert = results . cert ;
certs . web . key = results . privkey ;
certs . web . ca = [ results . chain ] ;
}
for ( var i in obj . parent . config . domains ) {
2019-06-08 01:55:24 +03:00
if ( ( obj . parent . config . domains [ i ] . dns != null ) && ( obj . parent . certificateOperations . compareCertificateNames ( results . altnames , obj . parent . config . domains [ i ] . dns ) ) ) {
2018-12-20 23:12:24 +03:00
certs . dns [ i ] . cert = results . cert ;
certs . dns [ i ] . key = results . privkey ;
certs . dns [ i ] . ca = [ results . chain ] ;
}
}
2018-01-15 08:01:06 +03:00
func ( certs ) ;
// Check if the Let's Encrypt certificate needs to be renewed.
2018-04-20 04:19:15 +03:00
setTimeout ( obj . checkRenewCertificate , 60000 ) ; // Check in 1 minute.
2018-01-15 08:01:06 +03:00
setInterval ( obj . checkRenewCertificate , 86400000 ) ; // Check again in 24 hours and every 24 hours.
return ;
} else {
// Otherwise return default certificates and try to get a real one
func ( certs ) ;
}
console . log ( "Attempting to get Let's Encrypt certificate, may take a few minutes..." ) ;
// Figure out the RSA key size
var rsaKeySize = ( obj . parent . config . letsencrypt . rsakeysize === 2048 ) ? 2048 : 3072 ;
// TODO: Only register on one of the peers if multi-peers are active.
// Register Certificate manually
obj . le . register ( {
domains : obj . leDomains ,
email : obj . parent . config . letsencrypt . email ,
agreeTos : true ,
rsaKeySize : rsaKeySize ,
challengeType : 'http-01'
2018-04-13 04:14:03 +03:00
//renewWithin: 15 * 24 * 60 * 60 * 1000 // 15 days
//renewWithin: 81 * 24 * 60 * 60 * 1000, // 81 days
//renewBy: 80 * 24 * 60 * 60 * 1000 // 80 days
2018-01-15 08:01:06 +03:00
} ) . then ( function ( xresults ) {
obj . parent . performServerCertUpdate ( ) ; // Reset the server, TODO: Reset all peers
} , function ( err ) {
console . error ( "ERROR: Let's encrypt error: " , err ) ;
} ) ;
} ) ;
2018-08-30 03:40:30 +03:00
} ;
2018-01-15 08:01:06 +03:00
// Check if we need to renew the certificate, call this every day.
obj . checkRenewCertificate = function ( ) {
if ( obj . leResults == null ) { return ; }
// TODO: Only renew on one of the peers if multi-peers are active.
// Check if we need to renew the certificate
2018-04-20 04:19:15 +03:00
obj . le . renew ( { duplicate : false , domains : obj . leDomains , email : obj . parent . config . letsencrypt . email } , obj . leResults ) . then ( function ( xresults ) {
2018-01-15 08:01:06 +03:00
obj . parent . performServerCertUpdate ( ) ; // Reset the server, TODO: Reset all peers
} , function ( err ) { } ) ; // If we can't renew, ignore.
2018-08-30 03:40:30 +03:00
} ;
2018-01-12 22:41:26 +03:00
2018-08-30 03:40:30 +03:00
return obj ;
2018-11-30 04:59:29 +03:00
} catch ( ex ) { console . log ( ex ) ; } // Unable to start Let's Encrypt
2018-08-30 03:40:30 +03:00
return null ;
} ;