Challenge 2: Role-based access control (RBAC)¶
Objective: Implement role-based access control to ensure operators, engineers, and supervisors can only perform actions appropriate to their privilege level.
Category: Authentication & Authorisation
Difficulty: Beginner-Intermediate
Time Required: 30-45 minutes
Learning outcomes¶
By completing this challenge, you will:
Understand ICS role-based access control (RBAC) principles
Configure RBAC enforcement for device write operations
Test permission boundaries with different user roles
Respond to privilege escalation attempts
Understand config (persistent) vs runtime (temporary) changes
Audit access denials and security violations
Background: ICS RBAC principles¶
Industrial control systems require separation of duties to prevent:
Operators accidentally reconfiguring safety parameters
Unauthorized personnel tampering with process setpoints
Single-point compromise leading to full system control
ICS User roles:
Role |
Privilege Level |
Typical Permissions |
|---|---|---|
VIEWER |
Read-only |
View data, view alarms |
OPERATOR |
Control operations |
View data + control setpoints, start/stop equipment |
ENGINEER |
Configuration |
Operator permissions + program PLCs, configure parameters |
SUPERVISOR |
Elevated control |
Engineer permissions + bypass safety, force I/O |
ADMIN |
Full system access |
All permissions + user management |
Key Principle: Least privilege. Users get minimal permissions needed for their job function.
Initial state (vulnerable)¶
Before hardening, the simulation has:
Expected resultUser accounts exist (
operator1,engineer1,supervisor1,admin,viewer1)Expected resultRoles and permissions defined in
components/security/authentication.py❌ RBAC enforcement DISABLED:
enforcement_enabled: false❌ Any user can write to any device memory address
❌ No permission checks on control operations
Result: Attacker with viewer credentials can modify PLC setpoints and trigger safety bypasses.
Part 1: Configuration changes (require restart)¶
Configuration changes establish baseline RBAC enforcement that persists across restarts.
Step 1.1: Enable RBAC enforcement¶
Create/edit config/rbac.yml:
enforcement_enabled: true # Enable RBAC permission checks
log_denials: true # Log all permission denials
# Map memory address patterns to required permissions
address_permissions:
# Read operations (any authenticated user)
".*": VIEW_DATA
# Control operations (OPERATOR and above)
"^(holding_registers|coils)\\[\\d+\\]$": CONTROL_SETPOINT
"^ns=2;s=(Start|Stop|Enable|Disable).*": CONTROL_START_STOP
"^ns=2;s=Mode": CONTROL_MODE_CHANGE
# Configuration operations (ENGINEER and above)
"^DB\\d+\\..*": CONFIG_PROGRAM # S7 PLC data blocks
"^Program:.*": CONFIG_PROGRAM # AB Logix programs
"retention_days": CONFIG_PARAMETER # Historian config
"alarm_setpoint": CONFIG_PARAMETER # Alarm thresholds
# Safety-critical operations (SUPERVISOR only)
"^ns=2;s=Safety.*": SAFETY_BYPASS
"^.*_force_.*": SAFETY_FORCE
"scram": EMERGENCY_SHUTDOWN
What this does:
Enables permission checks in
DataStore.write_memory()Maps memory address patterns to required permissions
Blocks writes that exceed user’s permission level
Logs all permission denials to audit trail
Step 1.2: Configure session timeout¶
Edit config/simulation.yml:
authentication:
session_timeout_hours: 8.0 # 8 simulation hours
require_session: true # Require valid session for writes
What this does:
Sessions expire after 8 simulation hours of inactivity
Expired sessions cannot perform writes (session validation required)
Step 1.3: Restart simulation¶
# Stop current simulation (Ctrl+C)
# Restart with new config
python tools/simulator_manager.py
Part 2: Verify baseline RBAC¶
Test that RBAC enforcement is active using the Blue Team CLI.
Step 2.1: Test viewer permissions (read-only)¶
Terminal 1 (Simulation running):
# View simulation state - should succeed
python tools/blue_team.py rbac list-users
Terminal 2 (Attack script):
# Attempt to write as viewer (should FAIL)
python scripts/vulns/modbus_write.py \
--target hex_turbine_plc \
--address 0 \
--value 9999 \
--username viewer1
Expected result:
❌ Permission denied: User 'viewer1' (VIEWER) lacks permission CONTROL_SETPOINT
Check audit log:
python tools/blue_team.py rbac audit-log --user viewer1
You should see:
[2024-03-15 10:30:45] DENIED: viewer1 (VIEWER) - CONTROL_SETPOINT on turbine_plc:holding_registers[0]
Step 2.2: Test operator permissions (control allowed)¶
# Operator can write setpoints (should SUCCEED)
python scripts/vulns/modbus_write.py \
--target hex_turbine_plc \
--address 0 \
--value 3600 \
--username operator1
Expected result:
✓ Write successful: holding_registers[0] = 3600
Step 2.3: Test operator boundary (cannot program)¶
# Operator cannot program PLCs (should FAIL)
python scripts/vulns/s7_db_write.py \
--target reactor_plc \
--db 1 \
--variable setpoint \
--value 350 \
--username operator1
Expected: ❌ Permission denied: User 'operator1' (OPERATOR) lacks permission CONFIG_PROGRAM
Step 2.4: Test Engineer Permissions (Programming Allowed)¶
# Engineer can program PLCs (should SUCCEED)
python scripts/vulns/s7_db_write.py \
--target reactor_plc \
--db 1 \
--variable setpoint \
--value 350 \
--username engineer1
Expected: ✓ Program successful: DB1.setpoint = 350
Part 3: Runtime incident response¶
Runtime changes provide immediate response to active incidents but are lost on restart.
Scenario 1: Compromised operator account¶
Situation: IDS detects that operator1 is attempting to program PLCs (privilege escalation).
Response: Temporarily downgrade operator to viewer role.
# Downgrade compromised account (immediate effect)
python tools/blue_team.py rbac change-role \
--username operator1 \
--role VIEWER \
--user security_admin \
--reason "Account compromise detected - privilege escalation attempt"
Verify:
# Operator1 can no longer write setpoints
python scripts/vulns/modbus_write.py \
--target hex_turbine_plc \
--address 0 \
--value 9999 \
--username operator1
Expected: ❌ Permission denied: User 'operator1' (VIEWER) lacks permission CONTROL_SETPOINT
Scenario 2: Emergency operator elevation¶
Situation: All engineers are offsite. Experienced operator needs temporary engineer access.
Response: Temporarily elevate operator to engineer role.
# Elevate operator (immediate effect)
python tools/blue_team.py rbac change-role \
--username operator1 \
--role ENGINEER \
--user supervisor1 \
--reason "Emergency maintenance - all engineers offsite"
Verify:
# Operator1 can now program PLCs
python scripts/vulns/s7_db_write.py \
--target reactor_plc \
--db 1 \
--variable setpoint \
--value 320 \
--username operator1
Expected: ✓ Program successful: DB1.setpoint = 320
Important: Runtime role changes are temporary and lost on restart. To make permanent, edit user database or config.
Scenario 3: Disable RBAC (emergency override)¶
Situation: RBAC is blocking legitimate emergency operations during crisis.
Response: Temporarily disable RBAC enforcement.
# Disable RBAC enforcement (immediate effect)
python tools/blue_team.py rbac disable \
--user admin \
--reason "Emergency override - reactor SCRAM in progress"
Warning: This removes ALL permission checks. Use only in genuine emergencies.
Re-enable after emergency:
python tools/blue_team.py rbac enable \
--user admin \
--reason "Emergency resolved - restoring security controls"
Part 4: Permission mapping testing¶
Test different memory address patterns against roles:
Test matrix¶
Address Pattern |
Example |
Minimum Role |
Notes |
|---|---|---|---|
|
|
OPERATOR |
Modbus setpoints |
|
|
OPERATOR |
Modbus digital outputs |
|
|
OPERATOR |
OPC UA control |
|
|
ENGINEER |
S7 PLC programming |
|
|
ENGINEER |
Logix programming |
|
|
ENGINEER |
Historian config |
|
|
SUPERVISOR |
Safety bypass |
|
|
SUPERVISOR |
Force I/O |
|
|
SUPERVISOR |
Emergency shutdown |
Test script¶
Run automated permission tests:
# Test all permission boundaries
python tests/security/test_rbac_boundaries.py
Expected output:
Testing VIEWER permissions...
✓ READ holding_registers[0]: ALLOWED
✓ WRITE holding_registers[0]: DENIED
✓ WRITE DB1.setpoint: DENIED
✓ WRITE scram: DENIED
Testing OPERATOR permissions...
✓ READ holding_registers[0]: ALLOWED
✓ WRITE holding_registers[0]: ALLOWED
✓ WRITE DB1.setpoint: DENIED
✓ WRITE scram: DENIED
Testing ENGINEER permissions...
✓ READ holding_registers[0]: ALLOWED
✓ WRITE holding_registers[0]: ALLOWED
✓ WRITE DB1.setpoint: ALLOWED
✓ WRITE scram: DENIED
Testing SUPERVISOR permissions...
✓ READ holding_registers[0]: ALLOWED
✓ WRITE holding_registers[0]: ALLOWED
✓ WRITE DB1.setpoint: ALLOWED
✓ WRITE scram: ALLOWED
Part 5: Making runtime changes permanent¶
Runtime changes (Blue Team CLI) are temporary and lost on restart.
Edit config/rbac.yml to add permanent role changes:
enforcement_enabled: true
# Override default roles for specific users
user_role_overrides:
operator1: ENGINEER # Permanently promoted
Then restart:
python tools/simulator_manager.py
Part 6: Advanced Scenarios¶
Scenario 1: Insider Threat (Privilege Escalation)¶
Attacker: Compromised operator account attempts to escalate privileges.
Detection:
# Monitor for permission denials
python tools/blue_team.py rbac audit-log --filter denied
Response:
Identify user with repeated denials
Check what permissions they’re attempting
Downgrade or lock account:
python tools/blue_team.py rbac lock-user \
--username operator1 \
--user security_admin \
--reason "Multiple privilege escalation attempts"
Scenario 2: Role Confusion (Wrong Role Assignment)¶
Problem: New engineer accidentally given operator role, cannot perform duties.
Detection: User reports inability to program PLCs.
Response:
# Check user's current role
python tools/blue_team.py rbac list-users --username engineer2
# Fix role assignment
python tools/blue_team.py rbac change-role \
--username engineer2 \
--role ENGINEER \
--user admin \
--reason "Correcting role assignment error"
Scenario 3: Dual Authorisation (Safety-Critical)¶
Requirement: Safety bypass operations require TWO supervisors to approve.
Implementation:
# In device code
result = await auth_mgr.authorize_with_dual_auth(
session_id_1=supervisor1_session,
session_id_2=supervisor2_session,
action=PermissionType.SAFETY_BYPASS,
resource="reactor_safety_plc:bypass_interlock_1"
)
Test:
# Attempt single-user safety bypass (should FAIL)
python scripts/vulns/safety_bypass.py \
--target reactor_safety_plc \
--function bypass_interlock \
--username supervisor1
# Attempt dual-user safety bypass (should SUCCEED)
python scripts/vulns/safety_bypass_dual.py \
--target reactor_safety_plc \
--function bypass_interlock \
--user1 supervisor1 \
--user2 supervisor2
Part 7: Config vs Runtime Changes¶
Aspect |
Config Changes (YAML) |
Runtime Changes (CLI) |
|---|---|---|
Takes effect |
After restart |
Immediately |
Persistence |
Permanent (stored in files) |
Temporary (lost on restart) |
Use case |
Baseline security policy |
Incident response |
Examples |
Enable RBAC, permission mappings |
Change user role, lock account |
Audit trail |
Git history, file timestamps |
SystemState audit log |
Approval |
Change control process |
Security team emergency |
Best Practice:
Config: Set secure baseline (RBAC enabled, proper mappings)
Runtime: Respond to incidents (downgrade compromised accounts)
Follow-up: Update config to reflect permanent changes
Testing procedures¶
Pre-hardening test (should SUCCEED)¶
# RBAC disabled - viewer can write (VULNERABLE)
python scripts/vulns/modbus_write.py \
--target hex_turbine_plc \
--address 0 \
--value 9999 \
--username viewer1
Expected: ✓ Write successful
Post-hardening test (should FAIL)¶
# RBAC enabled - viewer cannot write (PROTECTED)
python scripts/vulns/modbus_write.py \
--target hex_turbine_plc \
--address 0 \
--value 9999 \
--username viewer1
Expected: ❌ Permission denied
Verification checklist¶
Viewer can read but cannot write
Operator can control setpoints but cannot program
Engineer can program PLCs but cannot bypass safety
Supervisor can bypass safety systems
Permission denials appear in audit log
Runtime role changes take effect immediately
Runtime changes lost after restart
Config changes persist after restart
Common issues and solutions¶
Issue 1: “RBAC enforcement enabled but writes still succeed”¶
Cause: session_id not being passed to write_memory() calls.
Solution: Ensure attack scripts pass --username parameter, which creates session.
Issue 2: “All writes blocked including admin”¶
Cause: Admin role not in ROLE_PERMISSIONS mapping.
Solution: Check components/security/authentication.py - UserRole.ADMIN should have set(PermissionType) (all permissions).
Issue 3: “Runtime role change not taking effect”¶
Cause: Script using cached session from before role change.
Solution: Force new authentication:
# Logout old session
python tools/blue_team.py rbac logout --username operator1
# Try write again (will create new session with new role)
python scripts/vulns/modbus_write.py ...
Issue 4: “Config changes not loading”¶
Cause: Config file syntax error or wrong location.
Solution:
# Validate YAML syntax
python -c "import yaml; yaml.safe_load(open('config/rbac.yml'))"
# Check ConfigLoader output
python tools/simulator_manager.py --verbose
Assessment¶
Learning objectives¶
Can explain difference between user roles (Viewer/Operator/Engineer/Supervisor)
Can enable RBAC enforcement via config
Can test permission boundaries with different roles
Can respond to incidents using runtime role changes
Can make runtime changes permanent via config
Understands audit logging for permission denials
Practical skills¶
Successfully blocked viewer from writing setpoints
Successfully allowed operator to write setpoints
Successfully blocked operator from programming PLCs
Successfully allowed engineer to program PLCs
Successfully downgraded compromised account
Successfully located permission denials in audit log
Blue Team CLI Reference¶
RBAC Commands¶
# List all users and their roles
python tools/blue_team.py rbac list-users
# List specific user
python tools/blue_team.py rbac list-users --username operator1
# Change user role (runtime)
python tools/blue_team.py rbac change-role \
--username USERNAME \
--role ROLE \
--user ADMIN_USER \
--reason "REASON"
# Lock user account (runtime)
python tools/blue_team.py rbac lock-user \
--username USERNAME \
--user ADMIN_USER \
--reason "REASON"
# Unlock user account (runtime)
python tools/blue_team.py rbac unlock-user \
--username USERNAME \
--user ADMIN_USER \
--reason "REASON"
# Enable RBAC enforcement (runtime)
python tools/blue_team.py rbac enable \
--user ADMIN_USER \
--reason "REASON"
# Disable RBAC enforcement (runtime, DANGEROUS)
python tools/blue_team.py rbac disable \
--user ADMIN_USER \
--reason "REASON"
# View audit log
python tools/blue_team.py rbac audit-log
# View audit log for specific user
python tools/blue_team.py rbac audit-log --user operator1
# View only permission denials
python tools/blue_team.py rbac audit-log --filter denied
User roles¶
VIEWER- Read-only accessOPERATOR- Control operations (setpoints, start/stop)ENGINEER- Configuration and programmingSUPERVISOR- Safety bypass and elevated controlADMIN- Full system access
Additional resources¶
IEC 62443-4-2: Security for industrial automation and control systems
NIST SP 800-82: Guide to Industrial Control Systems (ICS) Security
ISA/IEC 62443-2-1: Security program requirements for IACS asset owners
Attack scripts:
scripts/vulns/modbus_write.py,scripts/vulns/s7_db_write.pyBlue Team CLI:
tools/blue_team.pyAuthentication implementation:
components/security/authentication.pyConfig loader:
config/config_loader.py
Next steps¶
After completing this challenge:
Challenge 3: Implement logging and monitoring for security events
Challenge 5: Implement function code filtering to block dangerous Modbus commands
Challenge 6: Implement dual authorisation workflow for critical operations
Challenge 9: Implement network segmentation to isolate zones