Sign in
← Linux: security
11 min · medium · Lesson 5 of 5

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.

grouped granted permissionsread pods, list logs roleviewer subjectalice, ci-bot

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

What you'll need for the tasks

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.

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.

Get access for $49

Tell the agent: use MCP izi, give me lesson linux-security-05-rbac-least-privilege