More narrowly defining sudo privileges for ghost install/service account?


Currently the installation instructions simply say to add the ghost install account to the sudoers group by running usermod -aG sudo <user>. This obviously works fine, but assuming you’ve already got a go-to administration account on the server, this step grants the ghost install account far more potential privileges than are necessary. It also creates a server-wide security problem if the ghost install user’s password is ever compromised, since the attacker would have access to an account that effectively has full root privileges.

It would be considerably safer and more optimal to correctly define what commands the ghost user should be able to invoke via sudo, by using visudo (or whatever other preferred method for editing /etc/sudoers) and explicitly defining a privilege specification.

Do you guys happen to have a list of what the ghost install user should be able to run in an escalated context? I can gen up the correct sudoers line from it if you’d like.


I have the funny feeling that it is a long list; apparently, the ghost-cli has features to install the systemd service file, configure nginx and other items. This has been one of my major beefs with the new setup; I can understand why it was done for some additional functionality to assist with configuration and setup, but I do agree that it is security risk.

I have found it best to just lock the ghost install account. passwd -l <account name> should work, and ensure that there are not any authorized_keys mentioned in the ~/.ssh/ directory.


It’s definitely an improvement over the pre-1.0 situation, but on the other hand it’s another disappointing choice in a long list of disappointing choices over the past four years—some of which were eventually rectified, and some of which were not. I have high hopes this issue will eventually wind up in the former category.

(For now, I’m dealing with it not by locking, but by forcing 2FA authentication on it the same as I do with any other privileged account. At least that way I’ll know if it’s compromised and someone is trying to access it, though locking is probably the better idea.)


99% of the commands run with sudo use a sudo proxy method. I might have a chance to clean up this list later today, but here is the list of sudo commands that are run:

extensions/nginx/acme.js:        return ui.sudo('./ --install --home /etc/letsencrypt', {cwd: acmeCodeDir});
extensions/nginx/acme.js:    return ui.sudo(cmd).catch((error) => {
extensions/nginx/acme.js:    return ui.sudo(cmd).catch((error) => {
extensions/nginx/index.js:            () => this.ui.sudo(`ln -sf /etc/nginx/sites-available/${confFile} /etc/nginx/sites-enabled/${confFile}`)
extensions/nginx/index.js:                return this.ui.sudo(`openssl dhparam -out ${dhparamFile} 2048`)
extensions/nginx/index.js:                    return this.ui.sudo(`mv ${tmpfile} ${sslParamsFile}`).catch(
extensions/nginx/index.js:                    () => this.ui.sudo(`ln -sf /etc/nginx/sites-available/${confFile} /etc/nginx/sites-enabled/${confFile}`)
extensions/nginx/index.js:                    this.ui.sudo(`rm -f /etc/nginx/sites-available/${confFile}`),
extensions/nginx/index.js:                    this.ui.sudo(`rm -f /etc/nginx/sites-enabled/${confFile}`)
extensions/nginx/index.js:                    this.ui.sudo(`rm -f /etc/nginx/sites-available/${sslConfFile}`),
extensions/nginx/index.js:                    this.ui.sudo(`rm -f /etc/nginx/sites-enabled/${sslConfFile}`)
extensions/nginx/index.js:        return this.ui.sudo('nginx -s reload')
extensions/systemd/index.js:            () => this.ui.sudo('systemctl daemon-reload')
extensions/systemd/index.js:            return this.ui.sudo(`rm ${serviceFilename}`).catch(
extensions/systemd/systemd.js:        return this.ui.sudo(`systemctl start ${this.systemdName}`)
extensions/systemd/systemd.js:        return this.ui.sudo(`systemctl stop ${this.systemdName}`)
extensions/systemd/systemd.js:        return this.ui.sudo(`systemctl restart ${this.systemdName}`)
extensions/systemd/systemd.js:        return this.ui.sudo(`systemctl is-enabled ${this.systemdName}`)
extensions/systemd/systemd.js:        return this.ui.sudo(`systemctl enable ${this.systemdName} --quiet`)
extensions/systemd/systemd.js:        return this.ui.sudo(`systemctl disable ${this.systemdName} --quiet`)
extensions/systemd/systemd.js:        return this.ui.sudo(`systemctl is-active ${this.systemdName}`)
lib/commands/run.js:        this.ui.log('Running sudo command: node current/index.js', 'gray');
lib/commands/run.js:        this.child = spawn('sudo', `-E -u ghost ${process.execPath} current/index.js`.split(' '), {
lib/commands/uninstall.js:                task: () => this.ui.sudo(`rm -rf ${path.join(instance.dir, 'content')}`)
lib/instance.js:            promises.push(() => this.ui.sudo(`ln -sf ${tmplFile} ${outputLocation}`));
lib/migrations.js:        return context.ui.sudo(`mkdir ${path.resolve(contentDir, 'settings')}`, {sudoArgs: '-E -u ghost'});
lib/tasks/linux.js:        task: () => ctx.ui.sudo('useradd --system --user-group ghost')
lib/tasks/linux.js:        task: () => ctx.ui.sudo(`chown -R ghost:ghost ${path.join(ctx.instance.dir, 'content')}`)
lib/tasks/migrate.js:        knexMigratorPromise = context.ui.sudo(`${knexMigratorPath} ${args.join(' ')}`, {sudoArgs: '-E -u ghost'});```


That’s not too nuts—though if we’re allowing rm, ln, useradd, chown, and systemctl to be run under root context then it’s almost not worth bothering with granularity at that point.


True, but you could also restrict how the command could be run - i.e. use globbing (of course, globbing is it’s own can of worms)