Access Control Models
In the last tutorial we started to discuss about the basic concepts of Identity and Access management. In this article we are going to dive deep of the different access control methods.
We know that there are two main parts in IAM as Authentication and Authorization. Access control is the concept that can be used to implement authorization.
That’s access control in a nutshell. It’s the mechanism that determines who can do what to which resource.
If you have used a Linux system Chmod is something that you can not avoid. Every time you run chmod 755 on a file you use access controlling.
Also, each time you assign an “Admin” role to a user, or set up an IAM policy in AWS you’re implementing some form of access control. But here’s the thing: not all access control works the same way. There are fundamentally different models for how access decisions are made, and understanding them is crucial whether you’re building applications, managing infrastructure, or breaking into systems.
Alright, lets look at the major ones.
Discretionary Access Control (DAC)
The Concept
Discretionary Access Control is a model where the resource owner decides who can access the resource and what operations they can perform. The key word here is discretionary — it’s at the owner’s discretion.
Real-World Analogy
Think of DAC like owning a house. You have the keys, and you can decide who gets a copy. You might give your neighbor a key to water your plants, or give your friend access to your garage. It’s entirely your call — no central authority tells you who can enter your house.
The upside? Flexibility. The downside? If you give your key to someone careless, they might make copies and hand them out to people you never intended to have access. Oops.
How It Works in Practice
In Linux, every file has three sets of permissions — owner, group, and others — with read (r), write (w), and execute (x) flags.
thilan@wso2 ~ $ ls -la secret.txt
-rw-r--r--@ 1 thilan staff 0 Apr 1 16:35 secret.txt
Lets break this down:
| Field | Value | Meaning |
|---|---|---|
| Owner | thilan |
Has read and write access |
| Group | staff |
Has read-only access |
| Others | - |
Has read-only access |
As the owner, I can change these permissions anytime I want:
$ chmod 644 secret.txt # Give others read access.
$ chmod 600 secret.txt # Only I can read and write.
$ chmod 700 secret.txt # Only I can read, write, and execute.
I can even transfer ownership:
$ chown alice secret.txt # Now Alice owns the file
That’s DAC in action, the owner controls access. Simple as that.
But wait. What does chmod 755 actually mean? Lets decode it real quick.
7 5 5
| | |
| | └── Others: r-x (read + execute = 4+1 = 5)
| └────── Group: r-x (read + execute = 4+1 = 5)
└────────── Owner: rwx (read + write + execute = 4+2+1 = 7)
Each digit is a sum of: read (4) + write (2) + execute (1). So chmod 755 means the owner can do everything, while group and others can read and execute but not write. Makes sense right?
The Access Control Matrix
DAC is often represented using an Access Control Matrix. It is a table that maps subjects (users) to objects (resources) and their permissions:
secret.txt |
config.yml |
deploy.sh |
|
|---|---|---|---|
| thilan | read, write | read, write | read, write, execute |
| alice | read | read, write | — |
| bob | — | read | read, execute |
In real systems, this matrix is implemented either as:
- Access Control Lists (ACLs) — stored per object, listing who can access it
- Capability Lists — stored per subject, listing what they can access
See that? With ACLs, we can set granular permissions for specific users without changing the file’s ownership or group. Pretty handy.
Pros and Cons
Pros:
- Simple and intuitive
- Flexible — owners have full control
- Easy to implement
Cons:
- No centralized policy enforcement
- Vulnerable to Trojan horse attacks — a malicious program running as the user inherits all of the user’s permissions
- Doesn’t scale well in enterprise environments
- Users can accidentally grant too much access
DAC is like giving everyone the freedom to manage their own locks. Great for personal use, risky for organizations.
Mandatory Access Control (MAC)
The Concept
Mandatory Access Control is the opposite philosophy from DAC. Here, access decisions are not made by the resource owner. They’re enforced by a central authority based on security labels and policies. Users cannot override these policies, no matter what.
The word mandatory says it all. The system mandates the rules, and nobody (not even the file owner) can change them.
Real-World Analogy
Think of MAC like a military base. You don’t get to decide who enters your office the base commander sets the clearance levels. Even if you’re a Colonel, you can’t grant a Private access to a Top Secret document. The security policy is enforced from the top down, regardless of individual preferences.
The Bell-LaPadula Model
The most famous MAC model is the Bell-LaPadula Model, designed for confidentiality. It uses security labels arranged in a hierarchy:
Top Secret (highest)
↑
Secret
↑
Confidential
↑
Unclassified (lowest)
It enforces two critical rules. And these are the ones you really need to remember:
1. No Read Up (Simple Security Property)
A subject at a lower clearance level cannot read objects at a higher classification level.
A user with “Secret” clearance cannot read “Top Secret” documents. Makes sense right? You shouldn’t see stuff above your pay grade.
2. No Write Down (Star Property)
A subject at a higher clearance level cannot write to objects at a lower classification level.
Wait, what? A “Top Secret” user cannot write to an “Unclassified” file? Why would we restrict that?
Think about it. If a Top Secret user could write to an Unclassified file, they could intentionally or accidentally declassify sensitive information. Imagine copying classified intelligence into a public document. That’s an information leak. The “no write down” rule prevents this entirely.
Lets visualize this:
┌─────────────┐
│ TOP SECRET │
│ │ ← Can read Top Secret & below
│ User: Alice │ ← CANNOT write to Secret or below
└──────┬───────┘
│ ✗ no write down
▼
┌─────────────┐
│ SECRET │
│ │ ← Can read Secret & below
│ User: Bob │ ← CANNOT read Top Secret (no read up)
└──────┬───────┘
│ ✗ no write down
▼
┌─────────────┐
│ UNCLASSIFIED │
│ │ ← Can only read Unclassified
│ User: Eve │ ← CANNOT read anything above
└─────────────┘
The Biba Model
While Bell-LaPadula focuses on confidentiality, the Biba Model focuses on integrity. It basically flips the rules:
1. No Read Down A subject cannot read objects at a lower integrity level.
2. No Write Up A subject cannot write to objects at a higher integrity level.
Why? You don’t want unreliable data contaminating trusted resources. Imagine a junior analyst modifying a database that feeds into executive reports. Or untrusted software writing to system-critical files. That’s an integrity violation.
Here’s an easy way to remember both models:
| Model | Protects | Rules | Memory Trick |
|---|---|---|---|
| Bell-LaPadula | Confidentiality | No read up, No write down | “Don’t read above your level, don’t leak downward” |
| Biba | Integrity | No read down, No write up | “Don’t trust lower sources, don’t corrupt higher ones” |
MAC in the Real World — SELinux
You’ve probably encountered MAC if you’ve ever been frustrated by SELinux denying something that Unix permissions said should work. That’s MAC in action — overriding DAC.
$ ls -Z /etc/passwd
system_u:object_r:passwd_file_t:s0 /etc/passwd
That system_u:object_r:passwd_file_t:s0 is the SELinux security context. Every file and process has one, and the kernel checks it on every access — regardless of what chmod says.
Lets see this in action. Say you have a web server trying to read a file:
# Unix permissions say this is fine
$ ls -la /var/www/data.txt
-rw-r--r-- 1 www-data www-data 1024 Apr 08 10:00 /var/www/data.txt
# But SELinux says NO
$ cat /var/log/audit/audit.log | grep denied
type=AVC msg=audit(1234567890.123:456): avc: denied { read } for
pid=1234 comm="httpd" name="data.txt"
scontext=system_u:system_r:httpd_t:s0
tcontext=system_u:object_r:user_home_t:s0
# The file has the wrong SELinux type (user_home_t instead of httpd_sys_content_t)
# Fix it:
$ chcon -t httpd_sys_content_t /var/www/data.txt
See what happened there? The Unix permissions were totally fine (-rw-r--r--). But SELinux blocked the access because the httpd process (type httpd_t) isn’t allowed to read files with the type user_home_t. That’s MAC overriding DAC. The system enforces its policy regardless of what the file permissions say.
Other MAC implementations you might encounter:
- AppArmor — Another Linux MAC implementation. Uses profiles instead of security contexts. Simpler than SELinux but less granular.
- Windows Mandatory Integrity Control — Windows uses integrity levels (Low, Medium, High, System) to prevent lower-integrity processes from modifying higher-integrity objects. That’s why a browser running at “Low” integrity can’t modify system files at “High” integrity.
Pros and Cons
Pros:
- Extremely secure — users can’t override policies
- Prevents information leakage and privilege escalation
- Centralized policy management
Cons:
- Complex to configure and maintain (ask anyone who’s fought with SELinux)
- Less flexible — can slow down productivity
- Requires careful planning of classification levels
- Overkill for most non-military applications
MAC is like having armed guards at every door with a strict rulebook. Maximum security, minimum flexibility.
Role-Based Access Control (RBAC)
The Concept
Role-Based Access Control assigns permissions not to individual users, but to roles. Users are then assigned to roles, and they inherit the permissions associated with those roles.
This is probably the most widely used access control model in modern applications. If you’ve ever been assigned an “Admin”, “Editor”, or “Viewer” role in any application — congrats, you’ve used RBAC.
Real-World Analogy
Think of a hospital. A doctor can prescribe medication, view patient records, and order lab tests. A nurse can view patient records and administer medication, but can’t prescribe. A receptionist can view appointment schedules but can’t access medical records.
Nobody sits down and configures permissions for each individual staff member. Instead, they assign a role, and the permissions come with it. New doctor joins the hospital? Assign the “Doctor” role. Done.
How RBAC Works
The model has three core components:
- Users — The people or entities in the system
- Roles — Named collections of permissions (e.g., Admin, Editor, Viewer)
- Permissions — Actions allowed on resources (e.g., read, write, delete)
The relationship flows like this:
Users → assigned to → Roles → granted → Permissions
Lets say we’re building a blog platform. Here’s how we’d define the roles:
| Role | Permissions |
|---|---|
| Admin | Create, Read, Update, Delete posts; Manage users; Configure settings |
| Editor | Create, Read, Update posts; Publish/Unpublish |
| Author | Create, Read, Update own posts |
| Viewer | Read published posts |
Now when a new editor joins the team, you just assign them the “Editor” role — done. No need to manually configure 15 different permissions. That’s the beauty of RBAC.
RBAC in Code
Alright, lets see how this looks in actual code. Here’s a simple Express.js middleware:
function authorize(requiredRole) {
return (req, res, next) => {
const userRole = req.user.role;
if (userRole !== requiredRole && userRole !== "admin") {
return res.status(403).json({ error: "Access denied" });
}
next();
};
}
// Usage
app.delete("/posts/:id", authorize("admin"), deletePost);
app.put("/posts/:id", authorize("editor"), updatePost);
app.get("/posts/:id", authorize("viewer"), getPost);
Simple right? But what if a user needs multiple roles? Lets make it a bit more flexible:
function authorize(...allowedRoles) {
return (req, res, next) => {
const userRoles = req.user.roles; // Array of roles
const hasAccess = userRoles.some(role => allowedRoles.includes(role));
if (!hasAccess) {
return res.status(403).json({ error: "Access denied" });
}
next();
};
}
// Now we can pass multiple roles
app.put("/posts/:id", authorize("admin", "editor"), updatePost);
app.get("/reports", authorize("admin", "manager", "analyst"), getReports);
Hierarchical RBAC
In more advanced implementations, roles can form a hierarchy. A Senior Editor inherits all permissions of an Editor, plus additional ones. An Admin inherits everything.
Admin
└── Senior Editor
└── Editor
└── Author
└── Viewer
This reduces redundancy — you don’t need to duplicate permissions across roles. The Admin role doesn’t need to list every single permission because it inherits them all from the roles below.
RBAC in Kubernetes — A Real Example
If you’re working with Kubernetes, you’ve definitely encountered RBAC. Lets see how it works:
# Define a Role that can read pods
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "watch", "list"]
---
# Bind the role to a user
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-pods
namespace: default
subjects:
- kind: User
name: alice
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
Now alice can list, watch, and get pods in the default namespace — but she can’t delete them, can’t create new ones, and can’t touch anything in other namespaces. That’s RBAC doing its job.
You can check who has what access:
# Can alice list pods?
$ kubectl auth can-i list pods --as alice
yes
# Can alice delete pods?
$ kubectl auth can-i delete pods --as alice
no
RBAC is everywhere:
- AWS IAM — Uses roles extensively for granting permissions to services and users
- Kubernetes — RBAC is the default authorization mode
- PostgreSQL — Uses roles for managing database access
- Nearly every SaaS app — Slack, GitHub, Jira — all use RBAC under the hood
Pros and Cons
Pros:
- Easy to understand and manage
- Scales well for organizations
- Simplifies auditing — you can review access by role
- Reduces administrative overhead
Cons:
- Can lead to role explosion — too many fine-grained roles become unmanageable
- Not ideal for dynamic or context-dependent access decisions
- Doesn’t handle attributes like time, location, or device type
RBAC is the sweet spot for most organizations — structured enough to be secure, flexible enough to be practical.
Attribute-Based Access Control (ABAC)
The Concept
Attribute-Based Access Control is the most flexible (and most complex) model. Instead of assigning permissions through roles, ABAC evaluates a set of attributes to make access decisions at runtime.
These attributes can describe anything:
- Subject attributes — Who is requesting? (role, department, clearance, age)
- Resource attributes — What are they accessing? (classification, owner, type)
- Action attributes — What are they trying to do? (read, write, delete)
- Environment attributes — What’s the context? (time of day, IP address, device type)
Real-World Analogy
Imagine a smart building where the doors don’t just check your badge — they check your badge, the time of day, which floor you’re on, whether there’s an emergency, and whether your department has a current project on that floor. That’s ABAC — dynamic, context-aware access control.
How ABAC Works
ABAC uses policies written as rules that evaluate attributes. A policy might look like this in plain English:
Allow access if the user’s department is “Engineering” AND the resource’s classification is “Internal” AND the time is between 9:00 AM and 6:00 PM AND the request comes from a corporate IP.
In a more structured format (like an AWS IAM policy):
{
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::internal-docs/*",
"Condition": {
"StringEquals": {
"aws:PrincipalTag/department": "Engineering"
},
"IpAddress": {
"aws:SourceIp": "10.0.0.0/8"
},
"DateGreaterThan": {
"aws:CurrentTime": "2024-01-01T09:00:00Z"
},
"DateLessThan": {
"aws:CurrentTime": "2024-01-01T18:00:00Z"
}
}
}
Every access request is evaluated against these policies in real time. If all conditions are met, access is granted. Otherwise, denied. No exceptions.
Lets look at a simpler example in Python to get the idea across:
def check_access(user, resource, action, environment):
"""ABAC policy engine - evaluates attributes at runtime"""
# Policy 1: Engineers can read internal docs during work hours
if (action == "read"
and user["department"] == "Engineering"
and resource["classification"] == "Internal"
and 9 <= environment["hour"] <= 18
and environment["ip"].startswith("10.")):
return True
# Policy 2: Managers can approve expenses under $10k
if (action == "approve"
and "Manager" in user["roles"]
and resource["type"] == "expense"
and resource["amount"] < 10000):
return True
# Policy 3: Anyone can read public resources
if (action == "read"
and resource["classification"] == "Public"):
return True
return False # Default deny
See how different this is from RBAC? We’re not just checking “is this user an admin?” — we’re evaluating multiple attributes from the user, the resource, and the environment to make a decision. That’s the power of ABAC.
ABAC vs RBAC — When to Choose What?
This is a question that comes up a lot. So lets settle it:
| Aspect | RBAC | ABAC |
|---|---|---|
| Complexity | Simple | Complex |
| Flexibility | Limited to roles | Unlimited — any attribute |
| Context-awareness | No | Yes (time, location, device) |
| Scalability | Role explosion risk | Scales with policies |
| Implementation effort | Low | High |
| Debugging | Easy — “user has role X” | Hard — “which of 20 conditions failed?” |
| Best for | Standard enterprise apps | Dynamic, fine-grained access control |
In practice, many organizations use a hybrid approach — RBAC for broad access categories, with ABAC policies layered on top for fine-grained, context-dependent decisions. AWS IAM is a perfect example of this. You assign roles (RBAC), but the policies attached to those roles can include conditions based on tags, time, IP, and more (ABAC).
ABAC in Real Systems
- AWS IAM Policies — While AWS uses roles (RBAC), the policy conditions are ABAC. You can write conditions based on tags, time, source IP, MFA status, and more.
- XACML — The eXtensible Access Control Markup Language is an OASIS standard for implementing ABAC policies. It’s XML-based (yes, XML in 2024), but it’s the most comprehensive standard for policy expression.
- Azure Conditional Access — Microsoft’s implementation of ABAC for Azure AD, evaluating signals like user risk, device compliance, and location.
- Open Policy Agent (OPA) — A modern, general-purpose policy engine that lets you write ABAC policies in Rego (a purpose-built query language).
Pros and Cons
Pros:
- Extremely flexible and fine-grained
- Context-aware — adapts to dynamic conditions
- Reduces role explosion
- Policy-driven — easy to audit and reason about
Cons:
- Complex to design and implement
- Performance overhead — every request requires policy evaluation
- Harder to debug — “why was this denied?” can be a nightmare
- Requires a well-defined attribute schema
ABAC is like having an AI bouncer that evaluates 20 different factors before letting you in. Powerful, but you need to program it right.
Rule-Based Access Control
There’s one more model worth mentioning — Rule-Based Access Control. It’s sometimes confused with RBAC because of the similar acronym, but they’re fundamentally different.
Rule-Based Access Control uses predefined rules that apply to all users equally, regardless of their role or identity. These rules are typically based on conditions like time, network, or resource properties.
A firewall is the classic example:
# iptables rules — classic Rule-Based Access Control
$ iptables -A INPUT -s 192.168.1.0/24 -p tcp --dport 443 -j ACCEPT
$ iptables -A INPUT -p tcp --dport 22 -j DROP
$ iptables -A INPUT -s 10.0.0.0/8 -p tcp --dport 22 -j ACCEPT
These rules don’t care who you are — they care about what the traffic looks like. Same IP, same port, same result, regardless of the user. A CEO’s SSH packet gets dropped just like an intern’s if it’s coming from outside the allowed network.
Routers, firewalls, and network ACLs are all implementations of Rule-Based Access Control.
Putting It All Together
Alright, lets zoom out and compare everything we’ve covered:
| Model | Who Decides? | Based On | Best For | Example |
|---|---|---|---|---|
| DAC | Resource owner | Owner’s discretion | Personal systems, file sharing | Linux chmod, Windows NTFS |
| MAC | Central authority | Security labels | Military, government, classified data | SELinux, AppArmor |
| RBAC | System admin | User roles | Enterprise apps, SaaS platforms | K8s RBAC, AWS IAM Roles |
| ABAC | Policy engine | Attributes & context | Dynamic, fine-grained control | AWS IAM Policies, Azure CA |
| Rule-Based | Predefined rules | Conditions | Network security, firewalls | iptables, ACLs |
In the real world, you’ll rarely see a system using just one model. Most modern systems combine multiple models — RBAC for broad strokes, ABAC for fine-grained policies, and maybe MAC for the most sensitive resources. Even your Linux machine uses DAC (file permissions) and MAC (SELinux/AppArmor) simultaneously.
The key takeaway? There’s no one-size-fits-all. The right model depends on your security requirements, organizational structure, and the sensitivity of your data.
Quick Decision Guide
Not sure which model to use? Here’s a quick flowchart:
Are you building a personal/small project?
└── YES → DAC is probably fine
└── NO →
Do you need military-grade security for classified data?
└── YES → MAC (SELinux, Bell-LaPadula)
└── NO →
Do you need context-aware decisions (time, location, device)?
└── YES → ABAC (or RBAC + ABAC hybrid)
└── NO → RBAC (covers 80% of use cases)
If you’re building an application and wondering where to start — go with RBAC. It covers the vast majority of use cases with minimal complexity. If you hit a wall where roles aren’t enough (you need time-based access, IP restrictions, attribute-level policies), layer ABAC on top. And if you’re working with classified or highly sensitive data, look into MAC.
Start simple, add complexity only when you need it.
Final Thoughts
Access control might seem like a dry topic at first. But once you understand these models, you start seeing them everywhere — in the operating systems you use daily, the applications you build, and the networks you secure.
What I find interesting is that these models aren’t just theoretical. Every chmod you run is DAC. Every time SELinux blocks something and you curse at it — that’s MAC doing its job. Every time you assign a role in AWS — RBAC. Every IAM policy condition — ABAC.
Whether you’re a developer implementing authorization in your app, a sysadmin hardening a Linux server, or a security researcher analyzing a system’s attack surface — understanding these models gives you a mental framework for reasoning about who can do what, and why.
If you want to go deeper into the IAM side of things — authentication, federation, identity provisioning — check out my article on Identity and Access Management (IAM). This access control article is essentially the authorization deep-dive that complements the broader IAM picture.
Thanks for reading!