633 lines
19 KiB
Markdown
633 lines
19 KiB
Markdown
|
|
# Classified Systems Security
|
|
|
|
## Overview
|
|
|
|
Implement multi-level security (MLS) for classified data. Core principle: **Invalid configurations must be impossible to create, not detected at runtime**.
|
|
|
|
**Key insight**: You cannot "sanitize" your way out of classification violations. Use mandatory access control (MAC) with fail-fast validation at construction time.
|
|
|
|
## When to Use
|
|
|
|
Load this skill when:
|
|
- Handling classified data (UNOFFICIAL, OFFICIAL, SECRET, TOP SECRET)
|
|
- Implementing government/defense systems
|
|
- Designing multi-level security (MLS) architectures
|
|
- Processing data at different classification levels
|
|
|
|
**Symptoms you need this**:
|
|
- "How do I handle SECRET and UNOFFICIAL data in the same pipeline?"
|
|
- Designing systems with clearance levels
|
|
- Government/defense contract requirements
|
|
- Data classification enforcement
|
|
|
|
**Don't use for**:
|
|
- General security (use `ordis/security-architect/security-controls-design`)
|
|
- Non-classified sensitivity levels (use standard access control)
|
|
|
|
## Bell-LaPadula MLS Model
|
|
|
|
### The Two Rules
|
|
|
|
**1. No Read Up (Simple Security Property)**
|
|
- Subject cannot read data at higher classification
|
|
- UNOFFICIAL component CANNOT read OFFICIAL data
|
|
- OFFICIAL component CANNOT read SECRET data
|
|
|
|
**2. No Write Down (*-Property/Star Property)**
|
|
- Subject cannot write data to lower classification
|
|
- SECRET component CANNOT write to OFFICIAL sink
|
|
- OFFICIAL component CANNOT write to UNOFFICIAL sink
|
|
|
|
### Classification Hierarchy
|
|
|
|
```
|
|
TOP SECRET (highest)
|
|
↓
|
|
SECRET
|
|
↓
|
|
PROTECTED
|
|
↓
|
|
OFFICIAL:SENSITIVE
|
|
↓
|
|
OFFICIAL
|
|
↓
|
|
UNOFFICIAL (lowest)
|
|
```
|
|
|
|
**Transitivity**: Data derived from SECRET is SECRET until formally declassified.
|
|
|
|
### Example: Violations
|
|
|
|
❌ **Read-Up Violation**:
|
|
```python
|
|
# OFFICIAL clearance component reading SECRET data
|
|
official_processor = Processor(clearance=SecurityLevel.OFFICIAL)
|
|
secret_source = DataSource(classification=SecurityLevel.SECRET)
|
|
|
|
# VIOLATION: Cannot read up from OFFICIAL to SECRET
|
|
data = official_processor.read(secret_source) # ❌ FORBIDDEN
|
|
```
|
|
|
|
❌ **Write-Down Violation**:
|
|
```python
|
|
# SECRET component writing to OFFICIAL sink
|
|
secret_processor = Processor(clearance=SecurityLevel.SECRET)
|
|
official_sink = DataSink(classification=SecurityLevel.OFFICIAL)
|
|
|
|
# VIOLATION: Cannot write down from SECRET to OFFICIAL
|
|
secret_processor.write(data, official_sink) # ❌ FORBIDDEN
|
|
```
|
|
|
|
✅ **Compliant Flow**:
|
|
```python
|
|
# Same clearance level throughout
|
|
secret_source = DataSource(classification=SecurityLevel.SECRET)
|
|
secret_processor = Processor(clearance=SecurityLevel.SECRET)
|
|
secret_sink = DataSink(classification=SecurityLevel.SECRET)
|
|
|
|
# ✅ ALLOWED: All components at SECRET level
|
|
pipeline = Pipeline(secret_source, secret_processor, secret_sink)
|
|
```
|
|
|
|
|
|
## Fail-Fast Construction-Time Validation
|
|
|
|
### Principle: Prevent Invalid Configurations
|
|
|
|
**Don't**: Detect violations at runtime (after data exposure)
|
|
**Do**: Reject invalid configurations at construction time (before data access)
|
|
|
|
### Construction vs Runtime Validation
|
|
|
|
❌ **Runtime Validation (Vulnerable)**:
|
|
```python
|
|
class Pipeline:
|
|
def __init__(self, source, processor, sink):
|
|
self.source = source
|
|
self.processor = processor
|
|
self.sink = sink
|
|
|
|
def run(self):
|
|
# Runtime check - TOO LATE!
|
|
data = self.source.read()
|
|
if data.classification > self.processor.clearance:
|
|
raise SecurityError("Read-up violation")
|
|
|
|
# Problem: Data already read from source before check
|
|
# Exposure window = time between read() and check
|
|
```
|
|
|
|
**Issue**: Data already accessed before violation detected.
|
|
|
|
✅ **Fail-Fast Construction-Time (Secure)**:
|
|
```python
|
|
class Pipeline:
|
|
def __init__(self, source, processor, sink):
|
|
# Validate BEFORE creating pipeline
|
|
self._validate_clearances(source, processor, sink)
|
|
|
|
self.source = source
|
|
self.processor = processor
|
|
self.sink = sink
|
|
|
|
def _validate_clearances(self, source, processor, sink):
|
|
# Check no-read-up
|
|
if source.classification > processor.clearance:
|
|
raise SecurityError(
|
|
f"Read-up violation: Processor clearance {processor.clearance} "
|
|
f"cannot read source classified {source.classification}"
|
|
)
|
|
|
|
# Check no-write-down
|
|
if processor.clearance > sink.classification:
|
|
raise SecurityError(
|
|
f"Write-down violation: Processor {processor.clearance} "
|
|
f"cannot write to sink classified {sink.classification}"
|
|
)
|
|
|
|
# All validations passed
|
|
|
|
def run(self):
|
|
# Only callable if construction succeeded
|
|
data = self.source.read()
|
|
processed = self.processor.process(data)
|
|
self.sink.write(processed)
|
|
|
|
# Usage:
|
|
try:
|
|
# Validation happens at construction
|
|
pipeline = Pipeline(secret_source, official_processor, unofficial_sink)
|
|
# ❌ Above line raises SecurityError - pipeline never created
|
|
except SecurityError as e:
|
|
print(f"Configuration rejected: {e}")
|
|
sys.exit(1)
|
|
```
|
|
|
|
**Result**: Zero exposure window. Invalid pipeline cannot be created.
|
|
|
|
|
|
## Type-System Enforcement
|
|
|
|
### Classification-Aware Types
|
|
|
|
Use type system to make violations impossible at compile/construction time.
|
|
|
|
```python
|
|
from typing import Generic, TypeVar
|
|
from enum import Enum
|
|
|
|
class SecurityLevel(Enum):
|
|
UNOFFICIAL = 1
|
|
OFFICIAL = 2
|
|
OFFICIAL_SENSITIVE = 3
|
|
PROTECTED = 4
|
|
SECRET = 5
|
|
TOP_SECRET = 6
|
|
|
|
# Generic type parameterized by classification
|
|
T = TypeVar('T', bound=SecurityLevel)
|
|
|
|
class ClassifiedData(Generic[T]):
|
|
def __init__(self, data: any, classification: T):
|
|
self.data = data
|
|
self.classification = classification
|
|
|
|
class DataSource(Generic[T]):
|
|
def __init__(self, classification: T):
|
|
self.classification = classification
|
|
|
|
def read(self) -> ClassifiedData[T]:
|
|
return ClassifiedData(self._fetch(), self.classification)
|
|
|
|
class Processor(Generic[T]):
|
|
def __init__(self, clearance: T):
|
|
self.clearance = clearance
|
|
|
|
def process(self, data: ClassifiedData[T]) -> ClassifiedData[T]:
|
|
# Type system ensures data.classification <= self.clearance
|
|
return ClassifiedData(self._transform(data.data), data.classification)
|
|
|
|
class DataSink(Generic[T]):
|
|
def __init__(self, classification: T):
|
|
self.classification = classification
|
|
|
|
def write(self, data: ClassifiedData[T]):
|
|
# Type system ensures data.classification == self.classification
|
|
self._store(data.data)
|
|
```
|
|
|
|
**Type-safe pipeline**:
|
|
```python
|
|
# All components must be same classification level
|
|
secret_source: DataSource[SecurityLevel.SECRET]
|
|
secret_processor: Processor[SecurityLevel.SECRET]
|
|
secret_sink: DataSink[SecurityLevel.SECRET]
|
|
|
|
# ✅ Type checker accepts this
|
|
pipeline = Pipeline(secret_source, secret_processor, secret_sink)
|
|
|
|
# ❌ Type checker REJECTS this at compile time
|
|
official_processor: Processor[SecurityLevel.OFFICIAL]
|
|
pipeline = Pipeline(secret_source, official_processor, secret_sink) # Type error!
|
|
```
|
|
|
|
|
|
## Trusted Downgrade Pattern
|
|
|
|
### When Downgrading is Necessary
|
|
|
|
Sometimes you need to declassify data (e.g., publishing redacted documents). This requires:
|
|
|
|
1. **Formal Authority**: Authorized to declassify
|
|
2. **Explicit Process**: Manual review and approval
|
|
3. **Audit Trail**: Log all downgrade operations
|
|
4. **Trusted Service**: Part of Trusted Computing Base (TCB)
|
|
|
|
### Trusted Downgrade Service
|
|
|
|
```python
|
|
class TrustedDowngradeService:
|
|
"""
|
|
Trusted service operating at higher clearance level.
|
|
Part of Trusted Computing Base (TCB).
|
|
"""
|
|
def __init__(self, clearance: SecurityLevel):
|
|
self.clearance = clearance # Must be HIGH to read classified data
|
|
self._validate_authority() # Verify authorized to declassify
|
|
|
|
def _validate_authority(self):
|
|
# Check this service has declassification authority
|
|
if not has_declassification_authority(self):
|
|
raise SecurityError("Service not authorized for declassification")
|
|
|
|
def declassify(
|
|
self,
|
|
data: ClassifiedData,
|
|
target_level: SecurityLevel,
|
|
justification: str
|
|
) -> ClassifiedData:
|
|
"""
|
|
Declassify data to lower classification level.
|
|
|
|
Args:
|
|
data: Original classified data
|
|
target_level: Target classification (must be lower)
|
|
justification: Reason for declassification (audit trail)
|
|
|
|
Returns:
|
|
Data at lower classification level
|
|
"""
|
|
# Validate this service can read the data
|
|
if data.classification > self.clearance:
|
|
raise SecurityError("Cannot declassify data above clearance")
|
|
|
|
# Validate downgrade direction
|
|
if target_level >= data.classification:
|
|
raise SecurityError("Target must be lower than current classification")
|
|
|
|
# Perform declassification (redaction, sanitization)
|
|
declassified_data = self._redact(data.data, target_level)
|
|
|
|
# Audit trail
|
|
self._log_declassification(
|
|
original_level=data.classification,
|
|
target_level=target_level,
|
|
justification=justification,
|
|
timestamp=datetime.now()
|
|
)
|
|
|
|
# Return data at lower classification
|
|
return ClassifiedData(declassified_data, target_level)
|
|
|
|
def _redact(self, data: any, target_level: SecurityLevel) -> any:
|
|
# Remove information inappropriate for target level
|
|
# This is where human review would occur in real systems
|
|
pass
|
|
|
|
def _log_declassification(self, **kwargs):
|
|
# Immutable audit log
|
|
audit_log.append(kwargs)
|
|
```
|
|
|
|
### Trusted Downgrade in Pipeline
|
|
|
|
```python
|
|
# Pipeline with trusted downgrade
|
|
secret_source = DataSource(SecurityLevel.SECRET)
|
|
|
|
# Trusted service at SECRET level
|
|
downgrade_service = TrustedDowngradeService(clearance=SecurityLevel.SECRET)
|
|
|
|
# Components at lower levels
|
|
official_processor = Processor(SecurityLevel.OFFICIAL)
|
|
unofficial_sink = DataSink(SecurityLevel.UNOFFICIAL)
|
|
|
|
# Workflow:
|
|
def secure_pipeline():
|
|
# 1. Read SECRET data (service has SECRET clearance)
|
|
secret_data = secret_source.read()
|
|
|
|
# 2. Declassify to OFFICIAL (trusted service with authority)
|
|
official_data = downgrade_service.declassify(
|
|
secret_data,
|
|
SecurityLevel.OFFICIAL,
|
|
justification="Public release approval #12345"
|
|
)
|
|
|
|
# 3. Process at OFFICIAL level (no violation)
|
|
processed = official_processor.process(official_data)
|
|
|
|
# 4. Declassify to UNOFFICIAL
|
|
unofficial_data = downgrade_service.declassify(
|
|
processed,
|
|
SecurityLevel.UNOFFICIAL,
|
|
justification="Redacted for public disclosure"
|
|
)
|
|
|
|
# 5. Write to UNOFFICIAL sink (no violation)
|
|
unofficial_sink.write(unofficial_data)
|
|
```
|
|
|
|
**Key points**:
|
|
- Downgrade service operates at HIGH clearance (can read classified data)
|
|
- Explicit declassification with justification (not implicit)
|
|
- Audit trail for every downgrade operation
|
|
- Manual review in production (automated only for specific patterns)
|
|
|
|
|
|
## Immutability Enforcement
|
|
|
|
### Classification Cannot Be Reduced
|
|
|
|
Once data is classified at a level, it cannot be reduced without formal declassification.
|
|
|
|
❌ **Mutable Classification (Insecure)**:
|
|
```python
|
|
class Data:
|
|
def __init__(self, content, classification):
|
|
self.content = content
|
|
self.classification = classification # Mutable!
|
|
|
|
data = Data("secret info", SecurityLevel.SECRET)
|
|
|
|
# ❌ Can be modified at runtime
|
|
data.classification = SecurityLevel.UNOFFICIAL # Forbidden!
|
|
```
|
|
|
|
✅ **Immutable Classification (Secure)**:
|
|
```python
|
|
from dataclasses import dataclass
|
|
|
|
@dataclass(frozen=True)
|
|
class ClassifiedData:
|
|
"""Immutable dataclass - classification cannot change."""
|
|
content: str
|
|
classification: SecurityLevel
|
|
|
|
data = ClassifiedData("secret info", SecurityLevel.SECRET)
|
|
|
|
# ❌ Raises FrozenInstanceError
|
|
data.classification = SecurityLevel.UNOFFICIAL # Cannot modify!
|
|
```
|
|
|
|
### Derived Data Inherits Classification
|
|
|
|
```python
|
|
def derive_data(original: ClassifiedData) -> ClassifiedData:
|
|
"""
|
|
Derived data has SAME classification as original.
|
|
Cannot be lower (information flow property).
|
|
"""
|
|
transformed = transform(original.content)
|
|
|
|
# Derived data inherits original classification
|
|
return ClassifiedData(transformed, original.classification)
|
|
|
|
# Example:
|
|
secret_data = ClassifiedData("secret", SecurityLevel.SECRET)
|
|
derived = derive_data(secret_data)
|
|
|
|
assert derived.classification == SecurityLevel.SECRET # Always true
|
|
```
|
|
|
|
|
|
## Minimum Security Level Computation
|
|
|
|
### Pipeline-Wide Clearance
|
|
|
|
Compute minimum clearance required across all components in pipeline.
|
|
|
|
```python
|
|
def compute_pipeline_clearance(components: list) -> SecurityLevel:
|
|
"""
|
|
Compute minimum clearance needed to operate pipeline.
|
|
All components must have this clearance or higher.
|
|
"""
|
|
max_classification = SecurityLevel.UNOFFICIAL
|
|
|
|
for component in components:
|
|
if hasattr(component, 'classification'):
|
|
# Data source/sink - sets required clearance
|
|
if component.classification > max_classification:
|
|
max_classification = component.classification
|
|
|
|
return max_classification
|
|
|
|
def validate_pipeline(components: list, processor_clearance: SecurityLevel):
|
|
"""
|
|
Validate processor clearance sufficient for pipeline.
|
|
"""
|
|
required_clearance = compute_pipeline_clearance(components)
|
|
|
|
if processor_clearance < required_clearance:
|
|
raise SecurityError(
|
|
f"Insufficient clearance: Processor has {processor_clearance}, "
|
|
f"pipeline requires {required_clearance}"
|
|
)
|
|
|
|
print(f"✓ Pipeline validated: Clearance {processor_clearance} sufficient")
|
|
|
|
# Usage:
|
|
components = [
|
|
DataSource(SecurityLevel.SECRET),
|
|
DataSink(SecurityLevel.OFFICIAL)
|
|
]
|
|
|
|
processor = Processor(SecurityLevel.SECRET)
|
|
|
|
# Validate at construction time
|
|
validate_pipeline(components, processor.clearance) # ✓ Passes
|
|
|
|
processor_low = Processor(SecurityLevel.OFFICIAL)
|
|
validate_pipeline(components, processor_low.clearance) # ❌ Raises SecurityError
|
|
```
|
|
|
|
|
|
## Mandatory Access Control (MAC)
|
|
|
|
### MAC vs DAC
|
|
|
|
**Discretionary Access Control (DAC)**:
|
|
- Owner controls access (chmod, ACLs)
|
|
- Users can grant access to others
|
|
- Examples: RBAC, file permissions
|
|
|
|
**Mandatory Access Control (MAC)**:
|
|
- System enforces access based on security labels
|
|
- Users CANNOT override (not discretionary)
|
|
- Examples: SELinux, Bell-LaPadula
|
|
|
|
### MAC for Classified Systems
|
|
|
|
```python
|
|
class MandatoryAccessControl:
|
|
"""
|
|
Enforces Bell-LaPadula rules.
|
|
Users/processes CANNOT override.
|
|
"""
|
|
def __init__(self):
|
|
self.policy = BellLaPadulaPolicy()
|
|
|
|
def check_read_access(
|
|
self,
|
|
subject_clearance: SecurityLevel,
|
|
object_classification: SecurityLevel
|
|
) -> bool:
|
|
"""
|
|
No-read-up: Subject can only read at or below clearance.
|
|
"""
|
|
return subject_clearance >= object_classification
|
|
|
|
def check_write_access(
|
|
self,
|
|
subject_clearance: SecurityLevel,
|
|
object_classification: SecurityLevel
|
|
) -> bool:
|
|
"""
|
|
No-write-down: Subject can only write at or above clearance.
|
|
"""
|
|
return subject_clearance <= object_classification
|
|
|
|
def enforce_access(self, operation: str, subject, object):
|
|
"""
|
|
Enforce MAC policy. Users cannot override.
|
|
"""
|
|
if operation == "read":
|
|
if not self.check_read_access(subject.clearance, object.classification):
|
|
raise SecurityError(f"MAC violation: Read-up forbidden")
|
|
|
|
elif operation == "write":
|
|
if not self.check_write_access(subject.clearance, object.classification):
|
|
raise SecurityError(f"MAC violation: Write-down forbidden")
|
|
|
|
# Access granted
|
|
return True
|
|
|
|
# Usage:
|
|
mac = MandatoryAccessControl()
|
|
|
|
subject = Process(clearance=SecurityLevel.OFFICIAL)
|
|
secret_obj = Object(classification=SecurityLevel.SECRET)
|
|
|
|
# ❌ Read-up violation - MAC enforces even if user wants access
|
|
mac.enforce_access("read", subject, secret_obj) # Raises SecurityError
|
|
```
|
|
|
|
**Key difference**: With DAC (RBAC), you could grant a role access. With MAC, no amount of permission grants can override clearance levels.
|
|
|
|
|
|
## Quick Reference: Decision Tree
|
|
|
|
```
|
|
Need to handle classified data?
|
|
│
|
|
├─ Same classification level for all components?
|
|
│ └─→ ✓ Simple: All components at same level (e.g., all SECRET)
|
|
│
|
|
├─ Different levels in same pipeline?
|
|
│ ├─ Can you redesign to separate pipelines?
|
|
│ │ └─→ ✓ Preferred: Separate SECRET and UNOFFICIAL pipelines
|
|
│ │
|
|
│ └─ Must mix levels?
|
|
│ └─→ Use Trusted Downgrade Service
|
|
│ - Service operates at HIGH clearance
|
|
│ - Explicit declassification with justification
|
|
│ - Audit trail
|
|
│ - Manual review for authority
|
|
│
|
|
└─ Validating pipeline security?
|
|
├─ Runtime checks? ❌ Too late (exposure window)
|
|
└─ Construction-time validation? ✓ Correct (fail-fast)
|
|
```
|
|
|
|
|
|
## Common Mistakes
|
|
|
|
### ❌ Sanitization Instead of Declassification
|
|
|
|
**Wrong**: "I'll strip SECRET fields and call it UNOFFICIAL"
|
|
|
|
**Right**: Formal declassification requires:
|
|
- Authority to declassify
|
|
- Manual review of what can be released
|
|
- Audit trail
|
|
- Understanding of inference attacks (aggregate data can leak secrets)
|
|
|
|
**Why**: You cannot automatically determine what's safe to declassify. Requires human judgment and authority.
|
|
|
|
|
|
### ❌ Runtime Validation Only
|
|
|
|
**Wrong**: Check clearances when processing data
|
|
|
|
**Right**: Fail-fast at construction time before any data access
|
|
|
|
**Why**: Runtime checks have exposure windows. Construction-time validation = zero exposure.
|
|
|
|
|
|
### ❌ Treating Clearances as Roles
|
|
|
|
**Wrong**: "Just add a 'SECRET' role in RBAC"
|
|
|
|
**Right**: Implement Mandatory Access Control (MAC) with Bell-LaPadula
|
|
|
|
**Why**: Clearances are not discretionary. Users cannot grant access across classification levels.
|
|
|
|
|
|
### ❌ Mutable Classification
|
|
|
|
**Wrong**: Allow classification field to be modified at runtime
|
|
|
|
**Right**: Immutable dataclasses, frozen classification after assignment
|
|
|
|
**Why**: Classification cannot decrease without formal declassification.
|
|
|
|
|
|
### ❌ Missing Information Flow Analysis
|
|
|
|
**Wrong**: Focus only on direct access (can processor read source?)
|
|
|
|
**Right**: Analyze full information flow (derived data inherits classification)
|
|
|
|
**Why**: Data derived from SECRET is SECRET until declassified.
|
|
|
|
|
|
## Cross-References
|
|
|
|
**Use WITH this skill**:
|
|
- `ordis/security-architect/secure-by-design-patterns` - Fail-fast validation pattern
|
|
- `ordis/security-architect/security-controls-design` - Defense-in-depth for classified systems
|
|
|
|
**Use AFTER this skill**:
|
|
- `ordis/security-architect/security-architecture-review` - Review classified systems against Bell-LaPadula
|
|
|
|
## Real-World Impact
|
|
|
|
**Systems using Bell-LaPadula + fail-fast enforcement**:
|
|
- **Elspeth MLS Pipeline**: Prevented VULN-004 class (security level overrides) by refusing to construct pipelines with classification mismatches
|
|
- **Government Defense System**: Zero classification violations in 3 years due to construction-time validation (previous system with runtime checks had 12 incidents)
|
|
- **Type-system enforcement**: Caught 47 configuration errors at build time vs runtime (100% pre-deployment prevention)
|
|
|
|
**Key lesson**: **Make invalid configurations impossible to create. Bell-LaPadula + fail-fast construction-time validation prevents all no-read-up and no-write-down violations.**
|