Least privilege and roles
The principle of least privilege in practice: roles instead of personal permissions, separation of duties, and why it limits the damage.
The principle of least privilege in practice: roles instead of personal permissions, separation of duties, and why this limits the damage. We'll work through two systems almost everyone has: sudo in Linux and roles in PostgreSQL.
Why this matters
A CI bot's token leaks, someone's laptop gets stolen, a vulnerability turns up in a service. What the attacker can do depends on one thing: what permissions that account had. If the bot had full sudo, the attacker now has root on the server. If the bot could only run one deploy script, the attacker only gets that script.
Permissions are always handed out ahead of time, before the incident. That is exactly why you trim them ahead of time.
Concepts
Principle of least privilege: every subject gets exactly the permissions it needs to do its job, and nothing more. We've already seen this idea: system users for services instead of root, sudo instead of the root password, a service only able to reach its own secrets, root login over SSH disabled.
RBAC (Role-Based Access Control): a model where permissions are grouped into roles, and subjects are granted roles instead of individual permissions.
Subject: whoever acts: a person, a service, a CI bot.
Role: a set of permissions, what's allowed.
Resource: what's being acted on, such as a table, a file, a Pod, a bucket.
Separation of duties: a dangerous operation can't be carried out by one subject alone; it needs permissions from different roles, or a second person's approval.
Limit the damage
The whole point of least privilege is limiting the damage. If a component has minimal permissions, breaking into it hands the attacker minimal access. An account that can read one database leaks that one database if it's stolen, not the whole server.
That gives a simple audit rule. For every account, ask: "what happens if this gets stolen today?" If the answer is "everything," it has too many permissions.
Roles instead of personal permissions
Granting permissions to each person and service one by one doesn't scale. A year later, nobody remembers why Pete has access to the payments database, or why the build bot can drop tables.
RBAC solves this: permissions are grouped into roles, and subjects are granted roles.
A new person joins, and they get a role that fits, instead of someone listing out permissions by hand. The role's requirements change, and that applies to everyone who has it. A person leaves, and their role is taken away, instead of someone searching the whole system for what they had access to.
A role ties permissions to resources: "read Pods in the prod namespace,"
"write to the backups bucket," "run the deploy script as the app
user." A subject gets a role and exactly those capabilities. This is how
permissions work in Kubernetes (Role and RoleBinding), in clouds (IAM), and
in databases. In Linux, a group most often plays the role.
Roles in Linux: groups and sudo
File permissions in Linux are already role-based: every file has an owner and a group. Giving someone access to a log directory means adding them to a group, not opening the directory to everyone.
It's the same with sudo. In /etc/sudoers and the files under
/etc/sudoers.d/, a rule can target a user or a group. A group is written
with a % sign:
%support ALL=(root) NOPASSWD: /usr/local/bin/restart-web
The rule reads: members of the support group, on any host (ALL), can
run exactly /usr/local/bin/restart-web as root, without a password.
The user in parentheses is who the command runs as. If it says app
instead of root, the command gets app's permissions, and you run it
through sudo -u app.
A new on-call engineer doesn't get their own rule written. They get added to the group:
addgroup ivan support
That's the Alpine (BusyBox) command. On Debian and Ubuntu,
usermod -aG support ivan does the same thing.
sudo shows exactly what a user can do:
sudo -l -U ivan
User ivan may run the following commands on server:
(root) NOPASSWD: /usr/local/bin/restart-web
sudo rules are edited through visudo, or dropped as a separate file in
/etc/sudoers.d/ with permissions 440. Before you log off the server,
check the syntax: visudo -c. A mistake in sudoers breaks sudo for
everyone.
/etc/sudoers.d/99-bad:1:19: syntax error
A narrow rule that's actually root
A rule that says "you can only run one program" is only safe if that program can't launch other programs. Many can: editors and pagers can open a shell, and that shell gets the same permissions as the program itself. The GTFOBins project maintains a list of such programs.
The second trap is in the permissions on the file itself. The rule allows
running /usr/local/bin/restart-web as root. If anyone other than root
can overwrite that file, they can write any command into it and run it
through sudo. So sudo -l only shows half the picture. The other half is
in ls -l on every allowed program, and in which groups can modify it.
Roles in PostgreSQL
In PostgreSQL, a user and a group are the same entity: a role. A role with
the LOGIN attribute works as a user; a role with NOLOGIN works as a
group of permissions. Permissions are granted to the group role, and
people are made members of it:
CREATE ROLE support NOLOGIN;
GRANT SELECT ON tickets TO support;
CREATE ROLE ivan LOGIN PASSWORD 'lab-only-password';
GRANT support TO ivan;
Now ivan can read tickets because he's a member of support. Logging
in as the support role itself isn't possible:
FATAL: role "support" is not permitted to log in
There's a detail people often trip over. GRANT ... ON ALL TABLES IN SCHEMA grants permissions on tables that exist right now. A table created
tomorrow arrives with no permissions, and the analyst gets
permission denied. For future objects there's ALTER DEFAULT PRIVILEGES: it sets permissions on tables that a given role will create
later (by default, the one running the command).
Since PostgreSQL 15, only the database owner can create objects in the
public schema. A regular user gets
permission denied for schema public, which is a good default.
You can check what a role can do with the has_table_privilege and
pg_has_role functions, and \du in psql lists the roles.
Separation of duties
Least privilege leads to separation of duties. One subject shouldn't be able to do everything on its own, especially for critical operations. A CI bot can build an image and push it to the registry, but it has no permission to drop the prod database. A deploy to prod waits for a second person's manual approval in the pipeline.
That way, one stolen account or one mistake doesn't lead to a disaster: a dangerous action needs several permissions or several people.
Common mistakes
- Personal rules instead of roles. In sudoers, lines like
petya ALL=(ALL) NOPASSWD: ALL; in the database,GRANT ... TO petya. Six months later nobody knows where these permissions came from or whether they're still needed. NOPASSWD: ALLfor a bot "so the deploy doesn't fail." The bot gets root on the server, and so does whoever steals its key.- A program allowed through sudo can launch a shell (an editor, a pager, an interpreter), or its file can be overwritten by someone other than root.
- Permissions were granted on existing tables but forgotten for future
ones. A week later, an analyst gets
permission deniedon a new table. - Editing sudoers without
visudo -c. A typo breaks sudo for everyone, including whoever made the edit. - Auditing only with
sudo -l. File permissions and group membership are separate and need their own checks:ls -l,id.
What you'll need for the tasks
- Enter a container:
docker exec -it <container> bash. - psql inside the database container:
psql -U <role> -d <database>, exit with\q. - PostgreSQL:
CREATE ROLE,GRANT ... ON ... TO,GRANT <role> TO <role>,\du,\dp,\ddp,has_table_privilege. Docs: GRANT, ALTER DEFAULT PRIVILEGES. - sudo:
sudo -l -U <user>,visudo -c, rule syntax in sudoers(5). - Groups in Alpine:
addgroup <group>,addgroup <user> <group>,delgroup <user> <group>,id <user>. - Run a command as another user while you're root:
su <user> -c '<command>'. - For the mini-project, everything from the module's earlier lessons:
keys and
authorized_keys,~/.ssh/config, sshd settings, and SIGHUP.
What to remember
The principle of least privilege limits the damage: give everyone exactly the permissions they need, and breaking into a component opens up the least possible amount.
RBAC groups permissions into roles and grants roles to subjects. In Linux
a role is a group in file permissions and in sudoers; in PostgreSQL it's a
role with NOLOGIN.
A narrow sudo rule is narrow only if the program doesn't launch other programs and its file can't be overwritten by just anyone.
Separation of duties keeps one subject from doing everything alone: dangerous operations need several permissions or a second person's approval.
This closes out the linux-security module: keys, config, hardening, scanning, and roles. The mini-project in lab 2 brings them together on one server.
Done reading?
Mark the lesson as read to move your track progress forward.
Lesson marked as done.
Go through it with an AI agent
The agent explains anything unclear in plain words, gives you 2 labs for this lesson and drills you on interview questions. $49 one-time, lifetime access.
Tell the agent: use MCP izi, give me lesson linux-security-05-rbac-least-privilege
Progress saved in this browser
Clear your history or open this on your phone, and the marks will be gone. Leave your email and they'll move to your account. No password needed.
Done. moved to your account. You can see your progress on any device. My progress