diff --git a/setup/start.sh b/setup/start.sh index 39a757b34..130970d5f 100755 --- a/setup/start.sh +++ b/setup/start.sh @@ -145,7 +145,6 @@ setfacl -n -m u:yellowtent:r /var/log/journal/*/system.journal echo "==> Creating config directory" rm -rf "${CONFIG_DIR}" && mkdir "${CONFIG_DIR}" -chown yellowtent:yellowtent "${CONFIG_DIR}" echo "==> Adding systemd services" cp -r "${script_dir}/start/systemd/." /etc/systemd/system/ @@ -175,35 +174,10 @@ echo "==> Configuring nginx" unlink /etc/nginx 2>/dev/null || rm -rf /etc/nginx ln -s "${DATA_DIR}/nginx" /etc/nginx mkdir -p "${DATA_DIR}/nginx/applications" +mkdir -p "${DATA_DIR}/nginx/cert" cp "${script_dir}/start/nginx/nginx.conf" "${DATA_DIR}/nginx/nginx.conf" cp "${script_dir}/start/nginx/mime.types" "${DATA_DIR}/nginx/mime.types" -# generate these for update code paths as well to overwrite splash -admin_cert_file="${DATA_DIR}/nginx/cert/host.cert" -admin_key_file="${DATA_DIR}/nginx/cert/host.key" -if [[ -f "${DATA_DIR}/box/certs/${admin_fqdn}.cert" && -f "${DATA_DIR}/box/certs/${admin_fqdn}.key" ]]; then - admin_cert_file="${DATA_DIR}/box/certs/${admin_fqdn}.cert" - admin_key_file="${DATA_DIR}/box/certs/${admin_fqdn}.key" -fi -${BOX_SRC_DIR}/node_modules/.bin/ejs-cli -f "${script_dir}/start/nginx/appconfig.ejs" \ - -O "{ \"vhost\": \"${admin_fqdn}\", \"adminOrigin\": \"${admin_origin}\", \"endpoint\": \"admin\", \"sourceDir\": \"${BOX_SRC_DIR}\", \"certFilePath\": \"${admin_cert_file}\", \"keyFilePath\": \"${admin_key_file}\", \"xFrameOptions\": \"SAMEORIGIN\" }" > "${DATA_DIR}/nginx/applications/admin.conf" - -mkdir -p "${DATA_DIR}/nginx/cert" -if [[ -f "${DATA_DIR}/box/certs/host.cert" && -f "${DATA_DIR}/box/certs/host.key" ]]; then - cp "${DATA_DIR}/box/certs/host.cert" "${DATA_DIR}/nginx/cert/host.cert" - cp "${DATA_DIR}/box/certs/host.key" "${DATA_DIR}/nginx/cert/host.key" -else - if [[ -z "${arg_tls_cert}" || -z "${arg_tls_key}" ]]; then - if [[ -n "${arg_fqdn}" ]]; then - echo "==> Creating fallback certs" - openssl req -x509 -newkey rsa:2048 -keyout "${DATA_DIR}/nginx/cert/host.key" -out "${DATA_DIR}/nginx/cert/host.cert" -days 3650 -subj "/CN=${arg_fqdn}" -nodes - fi - else - echo "${arg_tls_cert}" > "${DATA_DIR}/nginx/cert/host.cert" - echo "${arg_tls_key}" > "${DATA_DIR}/nginx/cert/host.key" - fi -fi - # bookkeep the version as part of data echo "{ \"version\": \"${arg_version}\", \"boxVersionsUrl\": \"${arg_box_versions_url}\" }" > "${DATA_DIR}/box/version" @@ -272,6 +246,11 @@ cat > "${CONFIG_DIR}/cloudron.conf" < "${CONFIG_DIR}/host.cert" + echo "${arg_tls_key}" > "${CONFIG_DIR}/host.key" +fi echo "==> Creating config.json for webadmin" cat > "${BOX_SRC_DIR}/webadmin/dist/config.json" < "${BOX_SRC_DIR}/webadmin/dist/config.json" < Changing ownership" -chown "${USER}:${USER}" "${CONFIG_DIR}/cloudron.conf" +chown "${USER}:${USER}" -R "${CONFIG_DIR}" chown "${USER}:${USER}" -R "${DATA_DIR}/nginx" "${DATA_DIR}/collectd" "${DATA_DIR}/addons" "${DATA_DIR}/acme" # during updates, do not trample mail ownership behind the the mail container's back find "${DATA_DIR}/box" -mindepth 1 -maxdepth 1 -not -path "${DATA_DIR}/box/mail" -print0 | xargs -0 chown -R "${USER}:${USER}" @@ -329,7 +308,4 @@ systemctl start cloudron.target sleep 2 # give systemd sometime to start the processes -set_progress "80" "Reloading nginx" -nginx -s reload - set_progress "100" "Done" diff --git a/src/certificates.js b/src/certificates.js index 8cf96957f..d8377d29a 100644 --- a/src/certificates.js +++ b/src/certificates.js @@ -1,6 +1,8 @@ 'use strict'; exports = module.exports = { + initialize: initialize, + installAdminCertificate: installAdminCertificate, renewAll: renewAll, setFallbackCertificate: setFallbackCertificate, @@ -92,12 +94,47 @@ function getApi(app, callback) { }); } +function initialize(callback) { + // ensure a fallback certificate that much of our code requires + var certFilePath = path.join(paths.APP_CERTS_DIR, 'host.cert'); + var keyFilePath = path.join(paths.APP_CERTS_DIR, 'host.key'); + + var fallbackCertPath = path.join(paths.NGINX_CERT_DIR, 'host.cert'); + var fallbackKeyPath = path.join(paths.NGINX_CERT_DIR, 'host.key'); + + if (fs.existsSync(certFilePath) && fs.existsSync(keyFilePath)) { // user's custom fallback certs (when restoring, updating) + debug('ensureFallbackCertificate: using fallback certs provided by user'); + if (!safe.child_process.execSync('cp ' + certFilePath + ' ' + fallbackCertPath)) return callback(new CertificatesError(CertificatesError.INTERNAL_ERROR, safe.error.message)); + if (!safe.child_process.execSync('cp ' + keyFilePath + ' ' + fallbackKeyPath)) return callback(new CertificatesError(CertificatesError.INTERNAL_ERROR, safe.error.message)); + + return callback(); + } + + if (config.tlsCert() && config.tlsKey()) { // cert from CaaS or cloudron-setup + debug('ensureFallbackCertificate: using CaaS/cloudron-setup fallback certs'); + if (!safe.fs.writeFileSync(fallbackCertPath, config.tlsCert())) return callback(new CertificatesError(CertificatesError.INTERNAL_ERROR, safe.error.message)); + if (!safe.fs.writeFileSync(fallbackKeyPath, config.tlsKey())) return callback(new CertificatesError(CertificatesError.INTERNAL_ERROR, safe.error.message)); + + return callback(); + } + + // generate a self-signed cert (FIXME: this cert does not cover the naked domain. needs SAN) + if (config.fqdn()) { + debug('ensureFallbackCertificate: generating self-signed certificate'); + var certCommand = util.format('openssl req -x509 -newkey rsa:2048 -keyout %s -out %s -days 3650 -subj /CN=*.%s -nodes', fallbackKeyPath, fallbackCertPath, config.fqdn()); + safe.child_process.execSync(certCommand); + } else { + debug('ensureFallbackCertificate: skip generating self-signed certificate (no domain set)'); + } + + return callback(); +} + function installAdminCertificate(callback) { if (process.env.BOX_ENV === 'test') return callback(); debug('installAdminCertificate'); - sysinfo.getIp(function (error, ip) { if (error) return callback(error); diff --git a/src/cloudron.js b/src/cloudron.js index cf3c247c4..5e422bf35 100644 --- a/src/cloudron.js +++ b/src/cloudron.js @@ -641,8 +641,8 @@ function doUpdate(boxUpdateInfo, callback) { apiServerOrigin: config.apiServerOrigin(), webServerOrigin: config.webServerOrigin(), fqdn: config.fqdn(), - tlsCert: fs.readFileSync(path.join(paths.NGINX_CERT_DIR, 'host.cert'), 'utf8'), - tlsKey: fs.readFileSync(path.join(paths.NGINX_CERT_DIR, 'host.key'), 'utf8'), + tlsCert: config.tlsCert(), + tlsKey: config.tlsKey(), isCustomDomain: config.isCustomDomain(), isDemo: config.isDemo(), diff --git a/src/config.js b/src/config.js index ce541df92..4ecface81 100644 --- a/src/config.js +++ b/src/config.js @@ -35,6 +35,9 @@ exports = module.exports = { isDev: isDev, isDemo: isDemo, + tlsCert: tlsCert, + tlsKey: tlsKey, + // for testing resets to defaults _reset: _reset }; @@ -216,3 +219,13 @@ function isDemo() { function provider() { return get('provider'); } + +function tlsCert() { + var certFile = path.join(baseDir(), 'configs/host.cert'); + return safe.fs.readFileSync(certFile, 'utf8'); +} + +function tlsKey() { + var keyFile = path.join(baseDir(), 'configs/host.key'); + return safe.fs.readFileSync(keyFile, 'utf8'); +} diff --git a/src/server.js b/src/server.js index 505444e17..5fc0f1813 100644 --- a/src/server.js +++ b/src/server.js @@ -270,6 +270,7 @@ function start(callback) { auth.initialize, database.initialize, cloudron.initialize, // keep this here because it reads activation state that others depend on + certificates.initialize, certificates.installAdminCertificate, // keep this before cron to block heartbeats until cert is ready platform.initialize, taskmanager.initialize,