Verified Commit 2e8c4879 authored by Aral Balkan's avatar Aral Balkan

Fixes #107: merge branch 'issue-107-fix-npm-audit-issues'

parents 8358c102 49d7bc80
......@@ -16,6 +16,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
### Changed
- Update @small-tech/https to version 1.2.1. Through this change, TLS certificates are now managed by [Auto Encrypt](https://source.ind.ie/site.js/lib/auto-encrypt) and [Auto Encrypt Localhost](https://source.ind.ie/site.js/lib/auto-encrypt-localhost).
- Instead of using `setcap`, [Site.js now disables privileged ports on Linux](https://source.small-tech.org/site.js/app/-/issues/169).
- Upgrade version of bundled Node LTS to 12.16.2.
- Upgrade Nexe to latest 4.x beta.
- Naming and directory placement conventions for archival cascades. Existing conventions will continue to work but have been deprecated. Please see the README file for further details.
......
......@@ -118,6 +118,7 @@ Site.js tries to seamlessly install the dependencies it needs when run. That sai
- `sudo`
- `libcap2-bin` (we use `setcap` to escalate privileges on the binary as necessary)
- `bash` (on Linux, macOS, etc.)
__For production use, passwordless sudo is required.__ On systems where the sudo configuration directory is set to `/etc/sudoers.d`, Site.js will automatically install this rule. On other systems, you might have to [set it up yourself](https://serverfault.com/questions/160581/how-to-setup-passwordless-sudo-on-linux).
......@@ -1103,12 +1104,12 @@ Options is an optional parameter object that may contain the following propertie
__Returns:__ Site instance.
__Note:__ if you want to run the site on a port < 1024 on Linux, ensure your process has the necessary privileges to bind to such ports. E.g., use:
__Note:__ if you want to run the site on a port < 1024 on Linux, ensure that privileged ports are disabled ([see details](https://source.small-tech.org/site.js/app/-/issues/169)). e.g., use:
```js
require('lib/ensure').weCanBindToPort(port, () => {
// You can safely bind to a ‘privileged’ port on Linux now.
})
require('lib/ensure').disablePrivilegedPorts()
// You can safely bind to ports below 1024 on Linux now.
```
### serve(callback)
......
This diff is collapsed.
......@@ -152,73 +152,75 @@ function serve (args) {
// No need to start a server if all we want to do is sync and there’s no generated content.
sync(syncOptions)
} else {
// Ensure privileged ports are disabled on Linux machines.
// For details, see: https://source.small-tech.org/site.js/app/-/issues/169
ensure.privilegedPortsAreDisabled()
// Start a server and also sync if requested.
ensure.weCanBindToPort(port, () => {
tcpPortUsed.check(port)
.then(async inUse => {
if (inUse) {
// Check to see if the problem is that Site.js is running as a daemon and
// display a more specific error message if so. (Remember that daemons are
// only supported on port 443 at the moment.)
if (port === 443) {
if (ensure.commandExists('systemctl')) {
if ({ isActive } = status()) {
console.log(`\n 🤯 Error: Cannot start server. Site.js is already running as a daemon on port ${clr(port.toString(), 'cyan')}. Use the ${clr('stop', 'green')} command to stop it.\n`)
process.exit(1)
}
tcpPortUsed.check(port)
.then(async inUse => {
if (inUse) {
// Check to see if the problem is that Site.js is running as a daemon and
// display a more specific error message if so. (Remember that daemons are
// only supported on port 443 at the moment.)
if (port === 443) {
if (ensure.commandExists('systemctl')) {
if ({ isActive } = status()) {
console.log(`\n 🤯 Error: Cannot start server. Site.js is already running as a daemon on port ${clr(port.toString(), 'cyan')}. Use the ${clr('stop', 'green')} command to stop it.\n`)
process.exit(1)
}
}
}
// Generic port-in-use error message.
console.log(`\n 🤯 Error: Cannot start server. Port ${clr(port.toString(), 'cyan')} is already in use.\n`)
process.exit(1)
} else {
// Generic port-in-use error message.
console.log(`\n 🤯 Error: Cannot start server. Port ${clr(port.toString(), 'cyan')} is already in use.\n`)
process.exit(1)
} else {
const options = {
path,
port,
global,
proxyPort,
aliases
}
const options = {
path,
port,
global,
proxyPort,
aliases
}
// Start serving the site.
let site
try {
site = new Site(options)
} catch (error) {
// Rethrow
throw(error)
}
// Start serving the site.
let site
try {
site = new Site(options)
} catch (error) {
// Start serving.
try {
await site.serve()
} catch (error) {
if (error instanceof errors.InvalidPathToServeError) {
console.log(` 🤯 ${clr('Error:', 'red')} The path to serve ${clr(options.path, 'yellow')} does not exist.\n`)
process.exit(1)
} else {
// Rethrow
throw(error)
}
}
// Start serving.
try {
await site.serve()
} catch (error) {
if (error instanceof errors.InvalidPathToServeError) {
console.log(` 🤯 ${clr('Error:', 'red')} The path to serve ${clr(options.path, 'yellow')} does not exist.\n`)
process.exit(1)
} else {
// Rethrow
throw(error)
}
}
const server = site.server
const server = site.server
// Start sync if requested.
if (syncOptions !== null) {
sync(syncOptions)
}
// Start sync if requested.
if (syncOptions !== null) {
sync(syncOptions)
}
if (!syncRequested && exitOnSync) {
// Person has provided the --exit-on-sync option but has not specified where to sync to.
// Warn them and continue.
console.log (` ⚠ --exit-on-sync option specified without --sync-to option; ignoring.`)
}
if (!syncRequested && exitOnSync) {
// Person has provided the --exit-on-sync option but has not specified where to sync to.
// Warn them and continue.
console.log (` ⚠ --exit-on-sync option specified without --sync-to option; ignoring.`)
}
})
}
})
}
}
......
......@@ -111,62 +111,32 @@ class Ensure {
}
}
// If we’re on Linux and the requested port is < 1024 ensure that we can bind to it.
// (As of macOS Mojave, privileged ports are only an issue on Linux. Good riddance too,
// as these so-called privileged ports are a relic from the days of mainframes and they
// actually have a negative impact on security today:
// https://www.staldal.nu/tech/2007/10/31/why-can-only-root-listen-to-ports-below-1024/
// Linux has an archaic security restriction dating from the mainframe/dumb-terminal era where
// ports < 1024 are “privileged” and can only be connected to by the root process. This has no
// practical security advantage today (and actually can lead to security issues). Instead of
// bending over backwards and adding more complexity to accommodate this, we use a feature that’s
// been in the Linux kernel since version 4.11 to disable privileged ports.
//
// Note: this might cause issues if https-server is used as a library as it assumes that the
// ===== current app is in index.js and that it can be forked. This might be an issue if a
// process manager is already being used, etc. Worth keeping an eye on and possibly
// making this method an optional part of server startup.
weCanBindToPort (port, callback) {
if (port < 1024 && os.platform() === 'linux') {
const options = {env: process.env}
// As this change is not persisted between reboots and takes a trivial amount of time to
// execute, we carry it out every time.
//
// For more details, see: https://source.small-tech.org/site.js/app/-/issues/169
privilegedPortsAreDisabled () {
if (os.platform() === 'linux') {
try {
childProcess.execSync(`setcap -v 'cap_net_bind_service=+ep' $(which ${process.title})`, options)
callback()
Site.logAppNameAndVersion()
console.log(' 😇 [Site.js] Linux: about to disable privileged ports so we can bind to ports < 1024.')
console.log(' 👉 For details, see: https://source.small-tech.org/site.js/app/-/issues/169')
childProcess.execSync('sudo sysctl -w net.ipv4.ip_unprivileged_port_start=0', {env: process.env})
} catch (error) {
try {
// Allow Node.js to bind to ports < 1024.
childProcess.execSync(`sudo setcap 'cap_net_bind_service=+ep' $(which ${process.title})`, options)
Site.logAppNameAndVersion()
console.log(' 😇 [Site.js] First run on Linux: got privileges to bind to ports < 1024.')
// Fork a new instance of the server so that it is launched with the privileged Node.js.
const newArgs = process.argv.slice(2)
newArgs.push('--dont-log-app-name-and-version')
const luke = childProcess.fork(path.resolve(path.join(__dirname, '..', 'site.js')), newArgs, {env: process.env})
// Let the child process know it’s a child process.
luke.send({IAmYourFather: process.pid})
function exitMainProcess () {
// Exit main process in response to child process event.
process.exit()
}
luke.on('exit', exitMainProcess)
luke.on('disconnect', exitMainProcess)
luke.on('close', exitMainProcess)
luke.on('error', exitMainProcess)
} catch (error) {
console.log(`\n Error: could not get privileges for Node.js to bind to port ${port}.`, error)
process.exit(1)
}
console.log('\n ❌ [Site.js] Error: could not disable privileged ports. Cannot bind to port 80 and 443. Exiting.', error)
process.exit(1)
}
} else {
// This is only relevant for Linux, which is the last major platform to
// carry forward the archaic security theatre of privileged ports. On other
// Linux-like platforms (notably, macOS), just call the callback.
callback()
}
}
// If the sync option is specified, ensure that Rsync exists on the system.
// (This will install it automatically if a supported package manager exists.)
rsyncExists() {
......
......@@ -72,11 +72,14 @@ class Site {
this.#manifest = JSON.parse(fs.readFileSync(path.join(__dirname, 'manifest.json'), 'utf-8'))
} catch (error) {
// When running under Node (not wrapped as a binary), there will be no manifest file. So mock one.
const options = {shell: '/bin/bash', env: process.env}
// Note: we switch to __dirname because we need to if Site.js is running as a daemon from source.
this.#manifest = {
releaseChannel: 'npm',
binaryVersion: '20000101000000',
packageVersion: (require(path.join(__dirname, 'package.json'))).version,
sourceVersion: childProcess.execSync('git log -1 --oneline').toString().match(/^[0-9a-fA-F]{7}/)[0],
sourceVersion: childProcess.execSync(`pushd ${__dirname} > /dev/null && git log -1 --oneline`,options).toString().match(/^[0-9a-fA-F]{7}/)[0],
hugoVersion: (new Hugo()).version
}
}
......@@ -192,8 +195,10 @@ class Site {
// • aliases: (string) comma-separated list of domains that we should get TLS certs
// for and serve.
//
// Note: if you want to run the site on a port < 1024 on Linux, ensure your process has the
// ===== necessary privileges to bind to such ports. E.g., use require('lib/ensure').weCanBindToPort(port, callback)
// Note: if you want to run the site on a port < 1024 on Linux, ensure that privileged ports are disabled.
// ===== e.g., use require('lib/ensure').disablePrivilegedPorts()
//
// For details, see: https://source.small-tech.org/site.js/app/-/issues/169
constructor (options) {
// Introduce ourselves.
Site.logAppNameAndVersion()
......@@ -230,13 +235,6 @@ class Site {
// Create the HTTPS server.
this.createServer()
// If running as child process, notify person.
process.on('message', (m) => {
if (m.IAmYourFather !== undefined) {
process.stdout.write(`\n 👶 Running as child process.`)
}
})
}
......@@ -755,8 +753,14 @@ class Site {
const checkForUpdates = () => {
this.log(' 🛰 Running auto update check…')
const options = {env: process.env, stdio: 'inherit'}
childProcess.exec('site update', options, (error, stdout, stderr) => {
const options = {env: process.env, stdio: 'inherit', shell: 'bash'}
let appReference = process.title
if (appReference.includes('node')) {
appReference = `${appReference} ${path.join(__dirname, 'bin', 'site.js')}`
}
childProcess.exec(`${appReference} update`, options, (error, stdout, stderr) => {
if (error !== null) {
this.log(' 😱 Error: Could not check for updates.')
} else {
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment