13  Capability-Based Access Control

Fine-Grained Permission Systems for IoT

13.1 Learning Objectives

After completing this chapter, you will understand:

  • Capability-Based Access Control: Fine-grained permissions beyond simple role hierarchies
  • Capability Flags: Using bit operations for efficient permission checking
  • User Profiles and Resource Definitions: Structuring access control data
  • Session and Token Structures: Managing time-limited access credentials
  • Attribute-Based Decisions: Access decisions based on context (time, location, device state)

Access control determines what each user or device is allowed to do in an IoT system. Think of a hospital where doctors, nurses, and visitors each have different access levels – doctors can prescribe medication, nurses can administer it, and visitors can only visit patients. Similarly, IoT access control ensures each device and user can only perform actions appropriate to their role.

“Regular roles like Admin and User are good, but sometimes we need finer control,” Max the Microcontroller explained. “Capability-based access control is like giving each person a custom key ring instead of just a badge. One key opens the temperature data door, another key opens the actuator control door, and another opens the settings door.”

Sammy the Sensor demonstrated. “Instead of saying ‘Sammy is a User and can do everything Users do,’ we say ‘Sammy can READ temperature data, WRITE to the alarm system, but CANNOT modify settings or add new devices.’ Each permission is a separate capability flag – either on or off, like tiny light switches.”

“These capability flags are stored as bits,” Lila the LED added. “Bit 0 might mean READ_SENSOR, bit 1 means WRITE_ACTUATOR, bit 2 means ADMIN_SETTINGS. We can check permissions super fast by looking at individual bits – perfect for tiny microcontrollers that need to decide in microseconds whether to allow an action.”

“Tokens have expiration times too, like a movie ticket that is only valid for one showing,” Bella the Battery explained. “Your access token might be valid for one hour. When it expires, you need to refresh it. And if something goes wrong – like a device gets stolen – the administrator can revoke that specific token immediately without affecting anyone else!”


13.2 Introduction to Capability-Based Access Control

Traditional role-based access control (RBAC) assigns users to roles like “admin” or “user” with predefined permissions. Capability-based access control (CBAC) provides finer granularity by treating permissions as individual capabilities that can be combined.

13.2.1 Why Capabilities Matter in IoT

IoT systems often need nuanced permission models:

Scenario RBAC Limitation CBAC Solution
Operator can read all sensors but write only temperature config Need separate roles for each sensor type Single user with READ + WRITE_TEMP capabilities
Maintenance window allows firmware updates only 2-6 AM Hard to express in static roles Time-restricted FIRMWARE_UPDATE capability
Emergency override needs dual authorization Roles don’t support multi-user approval EMERGENCY capability requires separation of duties

13.3 Capability Flags System

Capabilities are implemented as bit flags, enabling efficient bitwise operations for permission checking.

13.3.1 Defining Capability Flags

// ============== CAPABILITY FLAGS ==============
// Bit flags for fine-grained permissions
const uint16_t CAP_NONE         = 0x0000;
const uint16_t CAP_READ         = 0x0001;  // Read sensor data
const uint16_t CAP_WRITE        = 0x0002;  // Write configuration
const uint16_t CAP_EXECUTE      = 0x0004;  // Execute commands
const uint16_t CAP_DELETE       = 0x0008;  // Delete records
const uint16_t CAP_CREATE       = 0x0010;  // Create new resources
const uint16_t CAP_ADMIN_READ   = 0x0020;  // Read admin data
const uint16_t CAP_ADMIN_WRITE  = 0x0040;  // Write admin config
const uint16_t CAP_GRANT        = 0x0080;  // Grant permissions to others
const uint16_t CAP_REVOKE       = 0x0100;  // Revoke permissions
const uint16_t CAP_AUDIT        = 0x0200;  // Access audit logs
const uint16_t CAP_EMERGENCY    = 0x0400;  // Emergency override
const uint16_t CAP_DEBUG        = 0x0800;  // Debug/diagnostic access

13.3.2 Composite Capability Sets

Roles can be defined as combinations of capabilities:

// Composite capability sets
const uint16_t CAP_BASIC_USER = CAP_READ | CAP_EXECUTE;
const uint16_t CAP_POWER_USER = CAP_BASIC_USER | CAP_WRITE | CAP_CREATE;
const uint16_t CAP_OPERATOR = CAP_POWER_USER | CAP_DELETE | CAP_ADMIN_READ;
const uint16_t CAP_ADMIN = CAP_OPERATOR | CAP_ADMIN_WRITE | CAP_GRANT | CAP_REVOKE | CAP_AUDIT;
const uint16_t CAP_SUPERUSER = 0xFFFF;  // All capabilities

13.3.3 Checking Capabilities

Permission checking uses bitwise AND to verify all required bits are present:

bool hasCapability(uint16_t granted, uint16_t required) {
    // All required bits must be present in granted
    return (granted & required) == required;
}

Example: User has 0x07 (READ | WRITE | EXECUTE), resource requires 0x05 (READ | EXECUTE): - 0x07 & 0x05 = 0x05 (equals required) → Access granted

13.3.4 Try It: Capability Permission Checker

Use this interactive tool to experiment with capability bit flags. Toggle individual capabilities for a user, then set the required capabilities for a resource, and see whether access is granted or denied.


13.4 Data Structures for Access Control

13.4.1 Access Token Structure

Tokens encapsulate granted capabilities with lifecycle metadata:

// ============== ACCESS TOKEN STRUCTURE ==============
struct AccessToken {
    uint32_t tokenId;           // Unique token identifier
    char userId[16];            // Associated user ID
    uint16_t capabilities;      // Granted capabilities (bit flags)
    unsigned long issuedAt;     // Token creation timestamp
    unsigned long expiresAt;    // Token expiration timestamp
    unsigned long lastActivity; // Last use timestamp
    uint8_t refreshCount;       // Number of times refreshed
    bool isRevoked;             // Revocation status
    uint32_t sessionId;         // Associated session
    char issuedBy[16];          // Who issued this token
};

13.4.2 User Profile Structure

User profiles define base and maximum capabilities:

// ============== USER PROFILE STRUCTURE ==============
struct UserProfile {
    const char* userId;
    const char* userName;
    uint16_t baseCapabilities;    // Default capabilities
    uint16_t maxCapabilities;     // Maximum allowed capabilities
    bool canElevate;              // Can request elevated privileges
    uint8_t maxConcurrentSessions;
    uint32_t maxSessionDuration;  // Maximum session length (ms)
    bool requiresMFA;             // Multi-factor auth required
};

Key fields: - baseCapabilities: Capabilities granted on login - maxCapabilities: Maximum capabilities user can ever have (even with elevation) - canElevate: Whether user can request temporary elevated privileges

13.4.3 Protected Resource Definition

Resources declare their access requirements:

// ============== RESOURCE DEFINITION ==============
struct ProtectedResource {
    const char* resourceId;
    const char* resourceName;
    uint16_t requiredCapabilities;
    bool requiresAudit;
    bool timeRestricted;
    uint8_t allowedStartHour;  // 0-23
    uint8_t allowedEndHour;    // 0-23
};

13.4.4 Session Structure

Sessions track user state and elevation status:

// ============== SESSION STRUCTURE ==============
struct Session {
    uint32_t sessionId;
    char userId[16];
    unsigned long startTime;
    unsigned long lastActivity;
    unsigned long maxDuration;
    uint8_t tokenCount;
    bool isElevated;
    unsigned long elevatedUntil;
    uint16_t currentCapabilities;
    uint8_t failedElevationAttempts;
};

13.5 Example User and Resource Databases

13.5.1 User Database

// ============== USER DATABASE ==============
const int NUM_USERS = 6;
UserProfile userDatabase[NUM_USERS] = {
    // userId, userName, baseCaps, maxCaps, canElevate, maxSessions, maxDuration, requiresMFA
    {"SU001", "SuperAdmin", CAP_ADMIN, CAP_SUPERUSER, true, 2, 14400000, true},        // 4 hours
    {"AD001", "Alice Admin", CAP_OPERATOR, CAP_ADMIN, true, 3, 28800000, true},        // 8 hours
    {"OP001", "Bob Operator", CAP_POWER_USER, CAP_OPERATOR, true, 3, 28800000, false}, // 8 hours
    {"US001", "Charlie User", CAP_BASIC_USER, CAP_POWER_USER, false, 5, 28800000, false},
    {"GU001", "Diana Guest", CAP_READ, CAP_BASIC_USER, false, 1, 3600000, false},      // 1 hour
    {"SV001", "ServiceAccount", CAP_READ | CAP_EXECUTE, CAP_READ | CAP_EXECUTE, false, 10, 86400000, false}
};

13.5.2 Protected Resources

// ============== PROTECTED RESOURCES ==============
const int NUM_RESOURCES = 8;
ProtectedResource resources[NUM_RESOURCES] = {
    // resourceId, name, requiredCaps, audit, timeRestrict, startHour, endHour
    {"RES_SENSOR", "Sensor Data", CAP_READ, false, false, 0, 23},
    {"RES_CONFIG", "Configuration", CAP_WRITE, true, false, 0, 23},
    {"RES_CONTROL", "Device Control", CAP_EXECUTE, true, false, 0, 23},
    {"RES_LOGS", "System Logs", CAP_AUDIT, true, true, 8, 18},
    {"RES_USERS", "User Management", CAP_ADMIN_WRITE, true, true, 9, 17},
    {"RES_FIRMWARE", "Firmware Update", CAP_ADMIN_WRITE | CAP_EXECUTE, true, true, 2, 6},
    {"RES_EMERGENCY", "Emergency Override", CAP_EMERGENCY, true, false, 0, 23},
    {"RES_DEBUG", "Debug Console", CAP_DEBUG, true, true, 9, 17}
};

Notice how: - Firmware Update requires multiple capabilities AND time restriction (2-6 AM maintenance window) - System Logs are restricted to business hours (8 AM - 6 PM) - Emergency Override is always available but requires special capability

Try It: User-to-Resource Access Matrix

Select a user profile from the database above and a simulated hour of day to see which protected resources they can access. The matrix shows how capability flags and time restrictions combine to determine access.


13.6 Audit Event Types

Comprehensive audit logging requires categorized event types:

// ============== AUDIT EVENT TYPES ==============
enum AuditEventType {
    AUDIT_SESSION_START,
    AUDIT_SESSION_END,
    AUDIT_TOKEN_ISSUED,
    AUDIT_TOKEN_REFRESHED,
    AUDIT_TOKEN_REVOKED,
    AUDIT_ACCESS_GRANTED,
    AUDIT_ACCESS_DENIED,
    AUDIT_PRIVILEGE_ELEVATED,
    AUDIT_PRIVILEGE_DROPPED,
    AUDIT_ESCALATION_ATTEMPT,
    AUDIT_SUSPICIOUS_ACTIVITY,
    AUDIT_EMERGENCY_ACCESS,
    AUDIT_CAPABILITY_MODIFIED
};

// ============== AUDIT LOG ENTRY ==============
struct AuditEntry {
    unsigned long timestamp;
    AuditEventType eventType;
    char userId[16];
    char resourceId[16];
    uint16_t attemptedCapabilities;
    uint16_t grantedCapabilities;
    bool success;
    char details[32];
};

13.7 Timing Constants

Security timing is critical for session management. These constants balance security against usability: shorter lifetimes reduce the window for token theft exploitation, while longer timeouts reduce user friction. In resource-constrained IoT environments, these values are typically configured more aggressively (shorter) than in web applications because compromised devices may operate unattended.

// ============== TIMING CONSTANTS ==============
const unsigned long TOKEN_LIFETIME = 300000;        // 5 minutes
const unsigned long SESSION_IDLE_TIMEOUT = 600000;  // 10 minutes idle
const unsigned long ELEVATION_DURATION = 120000;    // 2 minutes elevated
const unsigned long ESCALATION_WINDOW = 60000;      // 1 minute window for attempts
const int MAX_ESCALATION_ATTEMPTS = 3;
const unsigned long MIN_TOKEN_REFRESH_INTERVAL = 60000;  // 1 minute between refreshes
Try It: Token Lifecycle Simulator

Adjust the timing constants to see how token lifetime, session idle timeout, and elevation duration interact in a visual timeline. Observe how shorter lifetimes improve security but increase refresh overhead.


13.8 Case Study: Tesla Vehicle Access Control Evolution (2012-2023)

Tesla’s approach to vehicle access control illustrates how capability-based systems evolve in response to real-world attacks, and why fine-grained permissions matter more than simple role hierarchies.

Phase 1: Simple Key Fob (2012-2017)

Tesla Model S launched with a traditional key fob using 40-bit encryption. Access was binary: have the fob, control the car.

Attack Year Method Impact
Relay attack 2016 Amplify fob signal from inside house to car in driveway Full vehicle access in 10 seconds
Key cloning 2018 Crack 40-bit encryption in 2 seconds using $600 equipment Permanent vehicle cloning (COSIC/KU Leuven research)

Phase 2: Phone-as-Key with Capability Tiers (2018-2023)

Tesla redesigned access control around BLE phone keys with capability-based permissions, moving beyond binary access:

Capability Bit Flag Description Who Gets It
DRIVE 0x01 Start and drive vehicle Owner, authorized drivers
CLIMATE 0x02 Control HVAC remotely Owner, all key holders
CHARGE 0x04 Start/stop charging, set limits Owner, charging service
TRUNK 0x08 Open frunk/trunk only Delivery drivers (Amazon Key)
SUMMON 0x10 Move vehicle autonomously Owner only
SERVICE 0x20 Diagnostic access, firmware Tesla service centers
SPEED_LIMIT 0x40 Enforce speed cap Valet, teen driver profiles
LOCATION 0x80 Track vehicle GPS Fleet managers, stolen vehicle

Capability Composition in Practice:

  • Owner: 0xFF (all capabilities)
  • Valet: DRIVE | CLIMATE | SPEED_LIMIT = 0x43 (drive but limited speed, no trunk, no summon)
  • Amazon Key delivery: TRUNK = 0x08 (open trunk only, cannot drive or access cabin)
  • Tesla service: SERVICE | CLIMATE | CHARGE = 0x26 (diagnostics, no driving)
  • Teen driver: DRIVE | CLIMATE | CHARGE | SPEED_LIMIT = 0x47 (drive with speed limit)

Security Metrics:

  • Relay attacks reduced 93% after switching from RF fob to BLE phone key with ranging
  • Unauthorized access incidents dropped from 112 per month (2017) to 8 per month (2022) across the fleet
  • Amazon Key delivery integration: 4.2 million secure trunk deliveries in 2022 with zero reported vehicle thefts
  • Valet mode prevented 340+ reported speed-limit violations per month

Key Lesson: Binary access (have key = full control) creates a single point of failure. Capability-based access control enables use cases that are impossible with role-based systems: a delivery driver who can open the trunk but cannot drive the car, or a teenager who can drive but not exceed 70 mph. Each capability is independently grantable and revocable, and compromising one capability (e.g., the TRUNK token) does not expose others.

Try It: Tesla Capability Composer

Build custom capability profiles for Tesla vehicle access. Toggle individual capabilities on and off to see the resulting hex value and compare it against predefined profiles like Owner, Valet, and Delivery.

A water treatment plant uses capability-based access control for 150 operators across 3 shifts. Each operator needs different access depending on their certification level and current shift responsibilities.

Capability Flags Defined (16-bit): - 0x0001 = READ_SENSORS (all operators) - 0x0002 = WRITE_SETPOINTS (certified operators only) - 0x0004 = EMERGENCY_SHUTDOWN (requires Level 3 cert + supervisor approval) - 0x0008 = CHEMICAL_DOSING (requires hazmat certification) - 0x0010 = PUMP_CONTROL (requires mechanical cert) - 0x0020 = MAINTENANCE_MODE (maintenance staff only) - 0x0040 = AUDIT_ACCESS (compliance team)

Sample User: Alice, Level 3 certified operator on day shift - Base capabilities: 0x0001 | 0x0002 | 0x0010 = 0x0013 (READ + WRITE + PUMP_CONTROL) - Max capabilities: 0x0013 | 0x0004 = 0x0017 (can elevate to include EMERGENCY_SHUTDOWN) - Elevation requires: Supervisor’s RFID card scan within 30 seconds

Runtime Check: When Alice requests pump control (requires 0x0010):

bool hasCapability(uint16_t granted, uint16_t required) {
    return (granted & required) == required;
}
// Alice's capabilities: 0x0013 = 0001 0011
// PUMP_CONTROL required:  0x0010 = 0001 0000
// 0x0013 & 0x0010 = 0x0010 → MATCH → Access granted

Production Benefit: When Alice transferred to night shift, IT updated her base capabilities to exclude PUMP_CONTROL (night shift policy) without creating a new role. Traditional RBAC would have required “DayShiftLevel3” and “NightShiftLevel3” roles.

Scenario RBAC (Roles) Capability Flags ABAC (Attributes) Best Choice
10 users, 3 roles, static permissions Simple (Admin, User, Guest) Overkill Overkill RBAC
100 users, many permission combinations Role explosion (50+ roles) Custom per-user capability sets Policy rules Capability-based
Time/location restrictions Can’t express Can express with time-restricted tokens Native support ABAC
Temporary privilege elevation Role switching (messy audit) Elevate specific capabilities Policy with context Capability-based
Compliance requirements (SOX, HIPAA) Adequate if roles map to compliance needs Better granularity for audit Best (policy as code) ABAC

Quick Decision:

  1. <20 permission types + role hierarchy works → RBAC (simplest)
  2. Need per-user customization without role explosion → Capability-based
  3. Need context-aware decisions (time/location/device state) → ABAC
  4. Embedded systems with RAM constraints → Capability-based (bit flags = minimal memory)
Common Mistake: Storing Capability Flags in Client Tokens

What practitioners do wrong: They embed capability bit flags directly in JWT tokens or session cookies sent to clients, assuming encryption/signing protects them.

Why it fails: While HMAC signatures prevent modification, capability flags in tokens reveal your permission model. Attackers can: 1. Enumerate permissions by analyzing token structure across users 2. Attempt privilege escalation by crafting tokens if the signing key leaks 3. Exploit token expiration gaps — if a user’s capabilities are revoked server-side, their client token remains valid until expiration

Correct approach:

  1. Server-side session storage: Token contains only a session ID; actual capabilities stored in server memory/database
  2. Session validation: Every request looks up current capabilities from server (not token)
  3. Short token lifetimes: 5-15 minutes max, forcing frequent refresh that checks current permissions
  4. Token revocation list: Ability to instantly invalidate specific tokens server-side

Real-world consequence: In 2020, an enterprise IoT platform stored capability flags in JWTs with 24-hour expiration. When an admin account was compromised, it took 24 hours to revoke admin access because the attacker’s stolen token remained valid. Post-incident fix: session IDs in tokens (opaque to client), server-side capability lookup, 5-minute token expiration.

Try It: Token Revocation Delay Simulator

Explore the real-world impact of token lifetime on security incidents. Set the token expiration time and see how long an attacker retains access after their token is stolen, comparing server-side vs client-side capability storage.

13.9 Concept Relationships

How Capability-Based Access Control Concepts Connect
Core Concept Builds On Enables Common Confusion
Bit-flag permissions Binary number representation Fine-grained access control “Why not just use role names?” - Bit flags allow efficient bitwise operations and custom per-user combinations
Capability tokens Authentication fundamentals Session-based access with specific permissions “Is this the same as an API key?” - No, tokens are short-lived and scoped to specific capabilities
Base vs max capabilities User profiles Privilege elevation “Why two levels?” - Base is normal access; max prevents users from elevating beyond their authorization
Time-restricted access Resource definitions Context-aware authorization “Why not just use roles?” - Time restrictions add temporal context (maintenance windows, business hours)
Audit events AAA accounting Security forensics and compliance “Isn’t logging enough?” - Structured audit events enable automated analysis and alerting

Key Insight: Capability-based access extends RBAC by treating permissions as composable units rather than monolithic roles, enabling precise “least privilege” enforcement.

A capability-based access control system with \(n\) distinct permissions creates a permission space of \(2^n\) unique capability combinations, where each combination is a subset of the power set \(\mathcal{P}(\text{Permissions})\).

\[|\text{Capability Space}| = 2^n\]

Working through an example:

Given: An IoT system with 12 capability flags (as shown in this chapter)

Step 1: Calculate total possible capability combinations

\[|\text{Total Combinations}| = 2^{12} = 4,096 \text{ unique permission sets}\]

Step 2: Calculate probability of random collision (two users accidentally assigned identical capabilities)

For a system with \(k\) users and \(2^n\) possible combinations, the collision probability using the birthday paradox approximation is:

\[P(\text{collision}) \approx 1 - e^{-\frac{k^2}{2 \times 2^n}}\]

For 100 users with 12-bit capabilities:

\[P(\text{collision}) \approx 1 - e^{-\frac{100^2}{2 \times 4096}} = 1 - e^{-1.22} \approx 0.705 \text{ or } 70.5\%\]

Result: With 12 permission bits, 100 users have a 70.5% chance of sharing at least one identical capability set, demonstrating why capability systems need sufficient bit width for unique per-user assignments.

In practice: Each additional permission bit doubles the capability space. Moving from 8 bits (256 combinations) to 16 bits (65,536 combinations) dramatically reduces collision probability while requiring only 1 extra byte of storage. For large IoT deployments (10,000+ devices), 16-bit or 32-bit capability flags prevent accidental permission overlaps and enable fine-grained per-device customization.

13.9.1 Try It: Capability Space & Collision Probability Calculator

Adjust the number of permission bits and users to see how the capability space grows and how collision probability changes.

13.10 Summary

Capability-based access control provides:

Feature Benefit
Bit Flag Permissions Efficient bitwise checking, fine granularity
User Profiles Base vs max capabilities, elevation control
Resource Definitions Required capabilities, time restrictions
Session Tracking Elevation state, idle detection
Audit Logging Complete trail of all security events

13.11 See Also

Within This Module:

Related Security Topics:

Production Systems:

  • AWS IAM Policies use attribute-based access control similar to capability flags
  • Linux capabilities (CAP_NET_ADMIN, CAP_SYS_ADMIN) use bit-flag permissions
  • Kubernetes RBAC combines role bindings with resource-level permissions

Key Concepts

  • Capability: An unforgeable, transferable token that grants specific rights to perform an action on a specific object; more fine-grained than role-based access
  • Least Privilege: The principle that every component should have only the minimum permissions required to perform its function; fundamental to IoT security
  • Linux Capabilities: A POSIX mechanism that splits superuser privileges into ~40 distinct capabilities (CAP_NET_ADMIN, CAP_SYS_ADMIN) that can be independently granted or dropped
  • Kubernetes RBAC: Role-Based Access Control in Kubernetes using Roles, ClusterRoles, RoleBindings, and ClusterRoleBindings to control which service accounts can access which API resources
  • Ambient Capabilities: Linux capabilities that are automatically inherited by child processes without explicit privilege; must be carefully managed in container environments
  • Capability Delegation: The ability to pass a capability (or a subset of it) to another principal; enables decentralized access control without a central authority
  • Confused Deputy Problem: A security vulnerability where a privileged program is tricked by a less-privileged client into misusing its authority on the client’s behalf
In 60 Seconds

Capability-based access control grants fine-grained permissions (read file X, send on port Y) as portable tokens rather than per-user ACLs, enabling least-privilege IoT device permissions where each device can only perform exactly the actions required for its specific function.


13.12 Knowledge Check

Common Pitfalls

CAP_SYS_ADMIN is effectively equivalent to root — it allows mounting filesystems, modifying kernel settings, and bypassing many security mechanisms. Containers needing specific capabilities (e.g., network configuration) should request only the specific capability (CAP_NET_ADMIN), never CAP_SYS_ADMIN.

Storing capability tokens as plain strings (e.g., “device:001:read:temp”) that clients can forge or modify breaks the security model. Capability tokens must be cryptographically signed (JWT or HMAC) so the server can verify they were issued by an authorized party.

Capability systems are weaker than ACL systems if there is no revocation mechanism. A compromised IoT device holding valid capability tokens can continue operating even after compromise is detected. Always maintain a revocation list or use short-lived tokens with expiry.

Kubernetes RBAC roles with wildcard resources (“”) or verbs (””) grant broad access that defeats least-privilege. Define specific resource names and verb lists ([“get”, “list”] not [“*”]) for each role, and audit role bindings regularly.

13.13 What’s Next

If you want to… Read this
See these concepts in a complete working system Advanced Implementation
Return to the advanced lab overview Advanced Access Control Lab Overview
Learn authentication fundamentals Auth & Authorization Basics
Understand zero trust architecture Zero Trust Security
Study threat modeling for access control Threat Modelling and Mitigation