Designing User Roles: Least Privilege in Practice

linux security permissions

One User Is a Liability

If your only user has full access and that account is compromised, everything is compromised. The attacker gets your web files, your sudo access, your service control — everything, in one shot. The blast radius is the entire server.

The alternative: design roles with distinct responsibilities, each with the minimum permissions needed to do their job. A breach of one role stays contained.

Three Roles

I set up three users, each scoped to a specific function.

The admin has full sudo access but requires a password for every privileged command. The original cloud image came with NOPASSWD: ALL — meaning any process running as this user had silent root access to everything. I removed that immediately. The password prompt is a final barrier: even if an attacker gets a shell as this user, they still need the password to escalate.

The deployer manages web application files. It has tightly scoped sudo privileges — it can restart and reload the web server, and nothing else. The sudo rule is stored in a separate file under /etc/sudoers.d/, not in the main sudoers config. System updates can overwrite the main file, and one file per role keeps things auditable. The rule uses full binary paths to prevent PATH manipulation attacks.

The service user runs the web server process. Its shell is /sbin/nologin, so interactive login is impossible. It has read-only access to website files. If the web server itself is compromised through a vulnerability, the attacker gets this user's minimal permissions: they can read the site content, but they can't modify files, restart services, or escalate privileges.

The Permission Model

The deployer and service user both belong to a shared group. The website directory is owned by the deployer with full access (read, write, execute). The group has read and execute only. Others have no access at all: mode 750.

This means the deployer can create and modify files, the web server can read and serve them, and everyone else is locked out.

Why Setgid Matters

There's a subtle problem with group-based permissions. When the deployer creates a new file, it normally inherits the deployer's primary group — not the shared group. The web server process can't read files owned by a group it doesn't belong to.

The fix: set the setgid bit on the website directory. With setgid, new files automatically inherit the directory's group instead of the creator's primary group. The web server can read everything the deployer creates, automatically, without manual permission fixes after every deployment.

chmod g+s /var/www/mysite

This is a one-time setup that prevents an entire class of "it works for me but not for the web server" bugs.

Blast Radius Analysis

If the admin is compromised: the attacker has sudo, but needs a password to use it. They can read user-level files but can't silently escalate. Bad, but not instant total compromise.

If the deployer is compromised: the attacker can modify web content and bounce the web server. They can't touch system config, can't access the admin's files, can't modify firewall rules. The blast radius is the web application.

If the service user is compromised: the attacker can read the site files. That's it. They can't write, can't sudo, can't even log in interactively. The blast radius is read-only access to public content — content that's already served to the internet anyway.

Each role's compromise is contained. That's least privilege working as intended.