mirror of
https://github.com/cliffe/BreakEscape.git
synced 2026-02-21 11:18:08 +00:00
feat: Add mission metadata and CyBOK integration with JSON schema and example files
This commit is contained in:
550
planning_notes/mission_data/IMPLEMENTATION_PLAN.md
Normal file
550
planning_notes/mission_data/IMPLEMENTATION_PLAN.md
Normal file
@@ -0,0 +1,550 @@
|
||||
# Mission Metadata & CyBOK Integration Implementation Plan
|
||||
|
||||
## Overview
|
||||
|
||||
This plan implements mission metadata (`mission.json`) files for BreakEscape scenarios and adds CyBOK (Cyber Security Body of Knowledge) integration that works in both standalone and Hacktivity modes.
|
||||
|
||||
---
|
||||
|
||||
## Architecture Summary
|
||||
|
||||
### Current State
|
||||
- **Missions table**: `break_escape_missions` with columns: `name`, `display_name`, `description`, `published`, `difficulty_level`
|
||||
- **Seeds**: Loops through scenario directories, creates missions with fallback defaults
|
||||
- **Scenarios**: Each mission has a `scenario.json.erb` for per-instance randomisation
|
||||
|
||||
### Target State
|
||||
- **Missions table**: Add `secgen_scenario` (string), `collection` (string) columns
|
||||
- **CyBOK table**: New `break_escape_cyboks` table (polymorphic, matches Hacktivity schema)
|
||||
- **Metadata files**: New `mission.json` in each scenario directory
|
||||
- **Dual-mode CyBOK**: Use Hacktivity's `::Cybok` if available, fallback to `BreakEscape::Cybok`
|
||||
- **Seeds**: Read `mission.json`, update missions, sync CyBOK data to both tables when applicable
|
||||
|
||||
---
|
||||
|
||||
## Implementation TODO List
|
||||
|
||||
### Phase 1: Database Migrations
|
||||
|
||||
#### 1.1 Add missing columns to break_escape_missions
|
||||
**File**: `db/migrate/YYYYMMDDHHMMSS_add_metadata_to_break_escape_missions.rb`
|
||||
|
||||
```ruby
|
||||
class AddMetadataToBreakEscapeMissions < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
add_column :break_escape_missions, :secgen_scenario, :string
|
||||
add_column :break_escape_missions, :collection, :string, default: 'default'
|
||||
|
||||
add_index :break_escape_missions, :collection
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
#### 1.2 Create break_escape_cyboks table
|
||||
**File**: `db/migrate/YYYYMMDDHHMMSS_create_break_escape_cyboks.rb`
|
||||
|
||||
```ruby
|
||||
class CreateBreakEscapeCyboks < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
create_table :break_escape_cyboks do |t|
|
||||
t.string :ka # Knowledge Area code (e.g., "AC", "F", "WAM")
|
||||
t.string :topic # Topic within the KA
|
||||
t.string :keywords # Keywords as comma-separated string (matches Hacktivity)
|
||||
t.string :cybokable_type # Polymorphic type
|
||||
t.integer :cybokable_id # Polymorphic ID
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
add_index :break_escape_cyboks, :cybokable_id
|
||||
add_index :break_escape_cyboks, [:cybokable_type, :cybokable_id]
|
||||
add_index :break_escape_cyboks, :ka
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Phase 2: Models
|
||||
|
||||
#### 2.1 Create BreakEscape::Cybok model
|
||||
**File**: `app/models/break_escape/cybok.rb`
|
||||
|
||||
```ruby
|
||||
# frozen_string_literal: true
|
||||
module BreakEscape
|
||||
class Cybok < ApplicationRecord
|
||||
self.table_name = 'break_escape_cyboks'
|
||||
|
||||
belongs_to :cybokable, polymorphic: true
|
||||
|
||||
# Mirror Hacktivity's KA_CODES for consistency
|
||||
KA_CODES = {
|
||||
'IC' => 'Introduction to CyBOK',
|
||||
'FM' => 'Formal Methods',
|
||||
'RMG' => 'Risk Management & Governance',
|
||||
'LR' => 'Law & Regulation',
|
||||
'HF' => 'Human Factors',
|
||||
'POR' => 'Privacy & Online Rights',
|
||||
'MAT' => 'Malware & Attack Technologies',
|
||||
'AB' => 'Adversarial Behaviours',
|
||||
'SOIM' => 'Security Operations & Incident Management',
|
||||
'F' => 'Forensics',
|
||||
'C' => 'Cryptography',
|
||||
'AC' => 'Applied Cryptography',
|
||||
'OSV' => 'Operating Systems & Virtualisation Security',
|
||||
'DSS' => 'Distributed Systems Security',
|
||||
'AAA' => 'Authentication, Authorisation and Accountability',
|
||||
'SS' => 'Software Security',
|
||||
'WAM' => 'Web & Mobile Security',
|
||||
'SSL' => 'Secure Software Lifecycle',
|
||||
'NS' => 'Network Security',
|
||||
'HS' => 'Hardware Security',
|
||||
'CPS' => 'Cyber Physical Systems',
|
||||
'PLT' => 'Physical Layer and Telecommunications Security'
|
||||
}.freeze
|
||||
|
||||
CATEGORY_MAPPING = {
|
||||
'Introductory Concepts' => ['IC'],
|
||||
'Human, Organisational & Regulatory Aspects' => ['RMG', 'LR', 'HF', 'POR'],
|
||||
'Attacks & Defences' => ['MAT', 'AB', 'SOIM', 'F'],
|
||||
'Systems Security' => ['C', 'OSV', 'DSS', 'AAA', 'FM'],
|
||||
'Software and Platform Security' => ['SS', 'WAM', 'SSL'],
|
||||
'Infrastructure Security' => ['AC', 'NS', 'HS', 'CPS', 'PLT']
|
||||
}.freeze
|
||||
|
||||
def ka_full_name
|
||||
KA_CODES[ka] || 'Unknown KA'
|
||||
end
|
||||
|
||||
def ka_category
|
||||
CATEGORY_MAPPING.each do |category, kas|
|
||||
return category if kas.include?(ka)
|
||||
end
|
||||
'Unknown Category'
|
||||
end
|
||||
|
||||
# Parse keywords string back to array (matches Hacktivity behavior)
|
||||
def keywords_array
|
||||
return [] if keywords.blank?
|
||||
# Handle both array-coerced strings and plain comma-separated
|
||||
keywords.gsub(/[\[\]"]/, '').split(',').map(&:strip)
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
#### 2.2 Update BreakEscape::Mission model
|
||||
**File**: `app/models/break_escape/mission.rb`
|
||||
|
||||
Add CyBOK association and dual-mode helper:
|
||||
|
||||
```ruby
|
||||
module BreakEscape
|
||||
class Mission < ApplicationRecord
|
||||
self.table_name = 'break_escape_missions'
|
||||
|
||||
has_many :games, class_name: 'BreakEscape::Game', dependent: :destroy
|
||||
|
||||
# CyBOK associations - always use our table
|
||||
has_many :break_escape_cyboks,
|
||||
class_name: 'BreakEscape::Cybok',
|
||||
as: :cybokable,
|
||||
dependent: :destroy
|
||||
|
||||
# Also populate Hacktivity's cyboks table when available
|
||||
if defined?(::Cybok)
|
||||
has_many :cyboks, as: :cybokable, dependent: :destroy
|
||||
end
|
||||
|
||||
validates :name, presence: true, uniqueness: true
|
||||
validates :display_name, presence: true
|
||||
validates :difficulty_level, inclusion: { in: 1..5 }
|
||||
|
||||
scope :published, -> { where(published: true) }
|
||||
scope :by_collection, ->(collection) { where(collection: collection) }
|
||||
scope :collections, -> { distinct.pluck(:collection).compact }
|
||||
|
||||
# Path to scenario directory
|
||||
def scenario_path
|
||||
BreakEscape::Engine.root.join('scenarios', name)
|
||||
end
|
||||
|
||||
# Path to mission metadata file
|
||||
def mission_json_path
|
||||
scenario_path.join('mission.json')
|
||||
end
|
||||
|
||||
# Check if mission.json exists
|
||||
def has_mission_json?
|
||||
File.exist?(mission_json_path)
|
||||
end
|
||||
|
||||
# Load mission metadata from JSON file
|
||||
def load_mission_metadata
|
||||
return nil unless has_mission_json?
|
||||
JSON.parse(File.read(mission_json_path))
|
||||
rescue JSON::ParserError => e
|
||||
Rails.logger.error "Invalid mission.json for #{name}: #{e.message}"
|
||||
nil
|
||||
end
|
||||
|
||||
# Get all CyBOK entries (prefers Hacktivity's if available for reads)
|
||||
def all_cyboks
|
||||
if defined?(::Cybok) && respond_to?(:cyboks)
|
||||
cyboks
|
||||
else
|
||||
break_escape_cyboks
|
||||
end
|
||||
end
|
||||
|
||||
# ... existing methods (generate_scenario_data, ScenarioBinding) ...
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Phase 3: CyBOK Sync Service
|
||||
|
||||
#### 3.1 Create CyBOK sync service
|
||||
**File**: `app/services/break_escape/cybok_sync_service.rb`
|
||||
|
||||
```ruby
|
||||
# frozen_string_literal: true
|
||||
module BreakEscape
|
||||
class CybokSyncService
|
||||
# Sync CyBOK data from mission.json to database tables
|
||||
# Writes to both BreakEscape and Hacktivity tables when Hacktivity is present
|
||||
def self.sync_for_mission(mission, cybok_data)
|
||||
return if cybok_data.blank?
|
||||
|
||||
# Normalize input (handle both array and hash formats)
|
||||
cybok_entries = Array.wrap(cybok_data)
|
||||
|
||||
# Clear existing entries
|
||||
mission.break_escape_cyboks.destroy_all
|
||||
mission.cyboks.destroy_all if mission.respond_to?(:cyboks) && defined?(::Cybok)
|
||||
|
||||
cybok_entries.each do |entry|
|
||||
ka = entry['ka'] || entry[:ka]
|
||||
topic = entry['topic'] || entry[:topic]
|
||||
keywords = entry['keywords'] || entry[:keywords]
|
||||
|
||||
# Serialize keywords array to string (Hacktivity format)
|
||||
keywords_str = keywords.is_a?(Array) ? keywords.join(', ') : keywords.to_s
|
||||
|
||||
# Always write to BreakEscape table
|
||||
mission.break_escape_cyboks.create!(
|
||||
ka: ka,
|
||||
topic: topic,
|
||||
keywords: keywords_str
|
||||
)
|
||||
|
||||
# Also write to Hacktivity table if available
|
||||
if mission.respond_to?(:cyboks) && defined?(::Cybok)
|
||||
mission.cyboks.create!(
|
||||
ka: ka,
|
||||
topic: topic,
|
||||
keywords: keywords_str
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Check if Hacktivity mode is active
|
||||
def self.hacktivity_mode?
|
||||
defined?(::Cybok)
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Phase 4: Mission Metadata JSON Format
|
||||
|
||||
#### 4.1 mission.json schema
|
||||
**Location**: Each scenario directory (e.g., `scenarios/biometric_breach/mission.json`)
|
||||
|
||||
```json
|
||||
{
|
||||
"display_name": "Biometric Breach",
|
||||
"description": "Investigate a security breach using biometric forensics in a high-security research facility.",
|
||||
"difficulty_level": 3,
|
||||
"secgen_scenario": null,
|
||||
"collection": "security_investigations",
|
||||
"cybok": [
|
||||
{
|
||||
"ka": "AAA",
|
||||
"topic": "Authentication",
|
||||
"keywords": ["Biometric authentication", "Fingerprint analysis", "Identity verification"]
|
||||
},
|
||||
{
|
||||
"ka": "F",
|
||||
"topic": "Artifact Analysis",
|
||||
"keywords": ["Digital forensics", "Evidence collection"]
|
||||
},
|
||||
{
|
||||
"ka": "SOIM",
|
||||
"topic": "Security Operations",
|
||||
"keywords": ["Incident response", "Security monitoring"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.2 Example mission.json files to create
|
||||
|
||||
| Scenario | Collection | Difficulty | CyBOK KAs |
|
||||
|----------|------------|------------|-----------|
|
||||
| `biometric_breach` | security_investigations | 3 | AAA, F, SOIM |
|
||||
| `ceo_exfil` | data_exfiltration | 4 | AB, MAT, F |
|
||||
| `cybok_heist` | physical_security | 2 | C, AC, HF |
|
||||
| `scenario1-4` | testing | 1-2 | Various |
|
||||
| `npc-*` | testing | 1 | HF |
|
||||
| `test-*` | testing | 1 | (none) |
|
||||
|
||||
---
|
||||
|
||||
### Phase 5: Updated Seeds
|
||||
|
||||
#### 5.1 Update db/seeds.rb
|
||||
**File**: `db/seeds.rb`
|
||||
|
||||
```ruby
|
||||
puts "Creating/Updating BreakEscape missions..."
|
||||
|
||||
# List all scenario directories
|
||||
scenario_dirs = Dir.glob(BreakEscape::Engine.root.join('scenarios/*')).select { |f| File.directory?(f) }
|
||||
|
||||
# Directories to skip
|
||||
SKIP_DIRS = %w[common compiled ink].freeze
|
||||
|
||||
scenario_dirs.each do |dir|
|
||||
scenario_name = File.basename(dir)
|
||||
next if SKIP_DIRS.include?(scenario_name)
|
||||
|
||||
# Check for scenario.json.erb (required for valid mission)
|
||||
scenario_template = File.join(dir, 'scenario.json.erb')
|
||||
next unless File.exist?(scenario_template)
|
||||
|
||||
mission = BreakEscape::Mission.find_or_initialize_by(name: scenario_name)
|
||||
mission_json_path = File.join(dir, 'mission.json')
|
||||
|
||||
if File.exist?(mission_json_path)
|
||||
# Load metadata from mission.json
|
||||
begin
|
||||
metadata = JSON.parse(File.read(mission_json_path))
|
||||
|
||||
mission.display_name = metadata['display_name'] || scenario_name.titleize
|
||||
mission.description = metadata['description'] || "Play the #{scenario_name.titleize} scenario"
|
||||
mission.difficulty_level = metadata['difficulty_level'] || 3
|
||||
mission.secgen_scenario = metadata['secgen_scenario']
|
||||
mission.collection = metadata['collection'] || 'default'
|
||||
mission.published = true
|
||||
|
||||
if mission.save
|
||||
# Sync CyBOK data
|
||||
if metadata['cybok'].present?
|
||||
BreakEscape::CybokSyncService.sync_for_mission(mission, metadata['cybok'])
|
||||
cybok_count = mission.break_escape_cyboks.count
|
||||
puts " ✓ #{mission.new_record? ? 'Created' : 'Updated'}: #{mission.display_name} (#{cybok_count} CyBOK entries)"
|
||||
else
|
||||
puts " ✓ #{mission.new_record? ? 'Created' : 'Updated'}: #{mission.display_name}"
|
||||
end
|
||||
else
|
||||
puts " ✗ Failed: #{scenario_name} - #{mission.errors.full_messages.join(', ')}"
|
||||
end
|
||||
|
||||
rescue JSON::ParserError => e
|
||||
puts " ✗ Invalid mission.json for #{scenario_name}: #{e.message}"
|
||||
# Fall back to defaults
|
||||
apply_default_metadata(mission, scenario_name)
|
||||
end
|
||||
else
|
||||
# No mission.json - use defaults
|
||||
apply_default_metadata(mission, scenario_name)
|
||||
end
|
||||
end
|
||||
|
||||
def apply_default_metadata(mission, scenario_name)
|
||||
mission.display_name ||= scenario_name.titleize
|
||||
mission.description ||= "Play the #{scenario_name.titleize} scenario"
|
||||
mission.difficulty_level ||= 3
|
||||
mission.collection ||= infer_collection(scenario_name)
|
||||
mission.published = true
|
||||
|
||||
if mission.save
|
||||
puts " ✓ #{mission.new_record? ? 'Created' : 'Updated'} (defaults): #{mission.display_name}"
|
||||
else
|
||||
puts " ✗ Failed: #{scenario_name} - #{mission.errors.full_messages.join(', ')}"
|
||||
end
|
||||
end
|
||||
|
||||
def infer_collection(scenario_name)
|
||||
return 'testing' if scenario_name.start_with?('test', 'npc-', 'scenario')
|
||||
'default'
|
||||
end
|
||||
|
||||
puts "\nDone! #{BreakEscape::Mission.count} missions total."
|
||||
puts "Collections: #{BreakEscape::Mission.collections.join(', ')}"
|
||||
if BreakEscape::CybokSyncService.hacktivity_mode?
|
||||
puts "Hacktivity mode: CyBOK data synced to both tables"
|
||||
else
|
||||
puts "Standalone mode: CyBOK data in break_escape_cyboks only"
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Phase 6: File Creation Tasks
|
||||
|
||||
#### 6.1 Files to create
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `db/migrate/YYYYMMDDHHMMSS_add_metadata_to_break_escape_missions.rb` | Add secgen_scenario, collection columns |
|
||||
| `db/migrate/YYYYMMDDHHMMSS_create_break_escape_cyboks.rb` | Create CyBOK table |
|
||||
| `app/models/break_escape/cybok.rb` | CyBOK model with KA codes |
|
||||
| `app/services/break_escape/cybok_sync_service.rb` | Dual-mode CyBOK sync |
|
||||
| `scenarios/biometric_breach/mission.json` | Mission metadata |
|
||||
| `scenarios/ceo_exfil/mission.json` | Mission metadata |
|
||||
| `scenarios/cybok_heist/mission.json` | Mission metadata |
|
||||
| (other scenario directories) | Mission metadata files |
|
||||
|
||||
#### 6.2 Files to update
|
||||
|
||||
| File | Changes |
|
||||
|------|---------|
|
||||
| `app/models/break_escape/mission.rb` | Add CyBOK associations, metadata loading |
|
||||
| `db/seeds.rb` | Read mission.json, sync CyBOK |
|
||||
|
||||
---
|
||||
|
||||
## Detailed TODO Checklist
|
||||
|
||||
### Migrations
|
||||
- [ ] Create migration: `add_metadata_to_break_escape_missions`
|
||||
- [ ] Add `secgen_scenario` string column (nullable)
|
||||
- [ ] Add `collection` string column (default: 'default')
|
||||
- [ ] Add index on `collection`
|
||||
- [ ] Create migration: `create_break_escape_cyboks`
|
||||
- [ ] Columns: `ka`, `topic`, `keywords` (all strings)
|
||||
- [ ] Polymorphic columns: `cybokable_type`, `cybokable_id`
|
||||
- [ ] Indexes on `cybokable_id`, `[cybokable_type, cybokable_id]`, `ka`
|
||||
|
||||
### Models
|
||||
- [ ] Create `app/models/break_escape/cybok.rb`
|
||||
- [ ] Table name: `break_escape_cyboks`
|
||||
- [ ] Polymorphic belongs_to: `cybokable`
|
||||
- [ ] KA_CODES constant (copy from Hacktivity)
|
||||
- [ ] CATEGORY_MAPPING constant
|
||||
- [ ] `ka_full_name` method
|
||||
- [ ] `ka_category` method
|
||||
- [ ] `keywords_array` method (parse stored string)
|
||||
- [ ] Update `app/models/break_escape/mission.rb`
|
||||
- [ ] Add `has_many :break_escape_cyboks` association
|
||||
- [ ] Add conditional `has_many :cyboks` for Hacktivity mode
|
||||
- [ ] Add `scope :by_collection`
|
||||
- [ ] Add `scope :collections`
|
||||
- [ ] Add `mission_json_path` method
|
||||
- [ ] Add `has_mission_json?` method
|
||||
- [ ] Add `load_mission_metadata` method
|
||||
- [ ] Add `all_cyboks` method
|
||||
|
||||
### Services
|
||||
- [ ] Create `app/services/break_escape/cybok_sync_service.rb`
|
||||
- [ ] `sync_for_mission(mission, cybok_data)` class method
|
||||
- [ ] Handle array/hash input normalization
|
||||
- [ ] Write to both tables when Hacktivity present
|
||||
- [ ] `hacktivity_mode?` class method
|
||||
|
||||
### Seeds
|
||||
- [ ] Update `db/seeds.rb`
|
||||
- [ ] Skip compiled/common/ink directories
|
||||
- [ ] Check for scenario.json.erb presence
|
||||
- [ ] Load mission.json when present
|
||||
- [ ] Apply default values for missing fields
|
||||
- [ ] Call CybokSyncService for CyBOK data
|
||||
- [ ] Add `infer_collection` helper for test scenarios
|
||||
- [ ] Print summary with collection list and mode
|
||||
|
||||
### Mission JSON Files
|
||||
- [ ] Create `scenarios/biometric_breach/mission.json`
|
||||
- [ ] Create `scenarios/ceo_exfil/mission.json`
|
||||
- [ ] Create `scenarios/cybok_heist/mission.json`
|
||||
- [ ] Create mission.json for scenario1-4
|
||||
- [ ] Create mission.json for npc-* scenarios (collection: testing)
|
||||
- [ ] Create mission.json for test-* scenarios (collection: testing)
|
||||
|
||||
### Testing
|
||||
- [ ] Run migrations: `rails db:migrate`
|
||||
- [ ] Run seeds: `rails db:seed`
|
||||
- [ ] Verify CyBOK data in `break_escape_cyboks` table
|
||||
- [ ] Test in standalone mode
|
||||
- [ ] Test with Hacktivity integration (if available)
|
||||
|
||||
---
|
||||
|
||||
## Mode Detection Logic
|
||||
|
||||
```ruby
|
||||
# In any code needing mode detection:
|
||||
if defined?(::Cybok)
|
||||
# Hacktivity mode: Both tables exist
|
||||
# Write to both, read from ::Cybok (Hacktivity's table)
|
||||
else
|
||||
# Standalone mode: Only BreakEscape::Cybok available
|
||||
# Use break_escape_cyboks table exclusively
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Data Flow Diagram
|
||||
|
||||
```
|
||||
mission.json (static) scenario.json.erb (per-instance)
|
||||
│ │
|
||||
▼ ▼
|
||||
┌──────────────────┐ ┌──────────────────┐
|
||||
│ db:seed loads │ │ Game.create │
|
||||
│ metadata │ │ generates │
|
||||
│ + CyBOK data │ │ random values │
|
||||
└────────┬─────────┘ └────────┬─────────┘
|
||||
│ │
|
||||
▼ ▼
|
||||
┌──────────────────┐ ┌──────────────────┐
|
||||
│break_escape_ │ │ Game instance │
|
||||
│missions table │ │ (has scenario │
|
||||
│ │ │ JSON data) │
|
||||
│+ break_escape_ │ └──────────────────┘
|
||||
│cyboks table │
|
||||
│ │
|
||||
│(+ Hacktivity │
|
||||
│ cyboks table if │
|
||||
│ available) │
|
||||
└──────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Rollback Plan
|
||||
|
||||
If issues arise:
|
||||
|
||||
1. **Migrations**: Run `rails db:rollback` for each migration
|
||||
2. **Models**: Revert to previous versions from git
|
||||
3. **Seeds**: Old seeds work with new columns (uses defaults)
|
||||
4. **mission.json**: Not required - seeds fall back gracefully
|
||||
|
||||
---
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
1. **Admin UI**: Add mission metadata editing in admin panel
|
||||
2. **Collection filtering**: Add collection dropdown to mission index
|
||||
3. **CyBOK mapping view**: Show all missions grouped by KA
|
||||
4. **Validation**: Add JSON schema validation for mission.json
|
||||
5. **Import/Export**: Bulk import/export mission metadata
|
||||
150
planning_notes/mission_data/MISSION_JSON_SCHEMA.md
Normal file
150
planning_notes/mission_data/MISSION_JSON_SCHEMA.md
Normal file
@@ -0,0 +1,150 @@
|
||||
# Mission JSON Schema
|
||||
|
||||
## File Location
|
||||
Each scenario directory should contain a `mission.json` file:
|
||||
```
|
||||
scenarios/
|
||||
├── biometric_breach/
|
||||
│ ├── mission.json <-- Static mission metadata
|
||||
│ └── scenario.json.erb <-- Per-instance randomised scenario
|
||||
├── ceo_exfil/
|
||||
│ ├── mission.json
|
||||
│ └── scenario.json.erb
|
||||
└── ...
|
||||
```
|
||||
|
||||
## Schema Definition
|
||||
|
||||
```json
|
||||
{
|
||||
"display_name": "string (required)",
|
||||
"description": "string (required)",
|
||||
"difficulty_level": "integer 1-5 (required)",
|
||||
"secgen_scenario": "string or null (optional)",
|
||||
"collection": "string (required)",
|
||||
"cybok": "array of CyBOK entries (optional)"
|
||||
}
|
||||
```
|
||||
|
||||
## Field Descriptions
|
||||
|
||||
### display_name (required)
|
||||
Human-readable name for the mission. Displayed on index pages, mission cards, etc.
|
||||
- Example: `"Biometric Breach"`
|
||||
|
||||
### description (required)
|
||||
Brief description of the mission objectives and theme. Used for mission cards and detail views.
|
||||
- Example: `"Investigate a security breach at a high-security research facility..."`
|
||||
|
||||
### difficulty_level (required)
|
||||
Integer from 1-5 indicating mission difficulty:
|
||||
- `1` = Beginner/Tutorial
|
||||
- `2` = Easy
|
||||
- `3` = Medium
|
||||
- `4` = Hard
|
||||
- `5` = Expert
|
||||
|
||||
### secgen_scenario (optional)
|
||||
Path to a SecGen XML scenario file when the mission includes virtual machines.
|
||||
- Example: `"scenarios/labs/introducing_attacks/1_intro_linux.xml"`
|
||||
- Set to `null` if mission is game-only (no VMs)
|
||||
|
||||
### collection (required)
|
||||
Grouping category for filtering on mission index. Common values:
|
||||
- `"testing"` - Test scenarios, not for end users
|
||||
- `"security_investigations"` - Forensics and investigation focused
|
||||
- `"physical_security"` - Lock picking, safe cracking, physical bypass
|
||||
- `"data_exfiltration"` - Data theft and covert operations
|
||||
- `"network_security"` - Network-based challenges
|
||||
- `"default"` - Uncategorised missions
|
||||
|
||||
### cybok (optional)
|
||||
Array of CyBOK (Cyber Security Body of Knowledge) entries mapping the mission to educational topics.
|
||||
|
||||
Each entry:
|
||||
```json
|
||||
{
|
||||
"ka": "string (2-4 letter code)",
|
||||
"topic": "string (topic name)",
|
||||
"keywords": ["array", "of", "keywords"]
|
||||
}
|
||||
```
|
||||
|
||||
## CyBOK Knowledge Area Codes
|
||||
|
||||
| Code | Full Name |
|
||||
|------|-----------|
|
||||
| IC | Introduction to CyBOK |
|
||||
| FM | Formal Methods |
|
||||
| RMG | Risk Management & Governance |
|
||||
| LR | Law & Regulation |
|
||||
| HF | Human Factors |
|
||||
| POR | Privacy & Online Rights |
|
||||
| MAT | Malware & Attack Technologies |
|
||||
| AB | Adversarial Behaviours |
|
||||
| SOIM | Security Operations & Incident Management |
|
||||
| F | Forensics |
|
||||
| C | Cryptography |
|
||||
| AC | Applied Cryptography |
|
||||
| OSV | Operating Systems & Virtualisation Security |
|
||||
| DSS | Distributed Systems Security |
|
||||
| AAA | Authentication, Authorisation and Accountability |
|
||||
| SS | Software Security |
|
||||
| WAM | Web & Mobile Security |
|
||||
| SSL | Secure Software Lifecycle |
|
||||
| NS | Network Security |
|
||||
| HS | Hardware Security |
|
||||
| CPS | Cyber Physical Systems |
|
||||
| PLT | Physical Layer and Telecommunications Security |
|
||||
|
||||
## Complete Example
|
||||
|
||||
```json
|
||||
{
|
||||
"display_name": "Biometric Breach",
|
||||
"description": "Investigate a security breach at a high-security research facility. Use biometric forensics tools to identify the intruder, track their movements through the facility, and recover stolen research data before it leaves the building.",
|
||||
"difficulty_level": 3,
|
||||
"secgen_scenario": null,
|
||||
"collection": "security_investigations",
|
||||
"cybok": [
|
||||
{
|
||||
"ka": "AAA",
|
||||
"topic": "Authentication",
|
||||
"keywords": ["Biometric authentication", "Fingerprint analysis", "Identity verification"]
|
||||
},
|
||||
{
|
||||
"ka": "F",
|
||||
"topic": "Artifact Analysis",
|
||||
"keywords": ["Digital forensics", "Evidence collection", "Fingerprint forensics"]
|
||||
},
|
||||
{
|
||||
"ka": "SOIM",
|
||||
"topic": "Security Operations & Incident Management",
|
||||
"keywords": ["Incident response", "Security monitoring", "Access control investigation"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Minimal Example (Testing Scenario)
|
||||
|
||||
```json
|
||||
{
|
||||
"display_name": "NPC Patrol Test",
|
||||
"description": "Test scenario for NPC patrol behaviours",
|
||||
"difficulty_level": 1,
|
||||
"secgen_scenario": null,
|
||||
"collection": "testing"
|
||||
}
|
||||
```
|
||||
|
||||
## Defaults When mission.json is Missing
|
||||
|
||||
If no `mission.json` exists, seeds will apply these defaults:
|
||||
- `display_name`: Titleized directory name (e.g., "biometric_breach" → "Biometric Breach")
|
||||
- `description`: "Play the {display_name} scenario"
|
||||
- `difficulty_level`: 3
|
||||
- `secgen_scenario`: null
|
||||
- `collection`: Inferred from name prefix:
|
||||
- Starts with "test" or "npc-" or "scenario" → "testing"
|
||||
- Otherwise → "default"
|
||||
146
planning_notes/mission_data/QUICK_START_CHECKLIST.md
Normal file
146
planning_notes/mission_data/QUICK_START_CHECKLIST.md
Normal file
@@ -0,0 +1,146 @@
|
||||
# Quick Start Checklist
|
||||
|
||||
Use this checklist to implement the mission metadata feature. Reference `IMPLEMENTATION_PLAN.md` for detailed code.
|
||||
|
||||
## Pre-Implementation
|
||||
- [ ] Read and understand `IMPLEMENTATION_PLAN.md`
|
||||
- [ ] Review existing Mission model (`app/models/break_escape/mission.rb`)
|
||||
- [ ] Review current seeds (`db/seeds.rb`)
|
||||
- [ ] Confirm Hacktivity's Cybok model structure (if integrating)
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Database Migrations
|
||||
|
||||
### 1.1 Add columns to missions table
|
||||
```bash
|
||||
rails g migration AddMetadataToBreakEscapeMissions secgen_scenario:string collection:string
|
||||
```
|
||||
Then edit migration to add:
|
||||
- Default `'default'` for collection
|
||||
- Index on collection
|
||||
|
||||
Run: `rails db:migrate`
|
||||
|
||||
### 1.2 Create CyBOK table
|
||||
```bash
|
||||
rails g migration CreateBreakEscapeCyboks
|
||||
```
|
||||
Edit to match schema in plan (ka, topic, keywords, polymorphic columns).
|
||||
|
||||
Run: `rails db:migrate`
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Models
|
||||
|
||||
### 2.1 Create Cybok model
|
||||
- [ ] Create `app/models/break_escape/cybok.rb`
|
||||
- [ ] Add KA_CODES and CATEGORY_MAPPING constants
|
||||
- [ ] Add helper methods
|
||||
|
||||
### 2.2 Update Mission model
|
||||
- [ ] Add `has_many :break_escape_cyboks` association
|
||||
- [ ] Add conditional Hacktivity association
|
||||
- [ ] Add scopes: `by_collection`, `collections`
|
||||
- [ ] Add metadata loading methods
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Service Layer
|
||||
|
||||
### 3.1 Create sync service
|
||||
- [ ] Create `app/services/break_escape/cybok_sync_service.rb`
|
||||
- [ ] Implement `sync_for_mission` method
|
||||
- [ ] Handle dual-mode (standalone vs Hacktivity)
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: Seeds Update
|
||||
|
||||
### 4.1 Update seeds.rb
|
||||
- [ ] Add mission.json loading logic
|
||||
- [ ] Add CyBOK sync calls
|
||||
- [ ] Add fallback defaults
|
||||
- [ ] Add collection inference for test scenarios
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: Mission JSON Files
|
||||
|
||||
### Priority missions (have scenario content):
|
||||
- [ ] `scenarios/biometric_breach/mission.json`
|
||||
- [ ] `scenarios/ceo_exfil/mission.json`
|
||||
- [ ] `scenarios/cybok_heist/mission.json`
|
||||
|
||||
### Secondary (existing scenarios):
|
||||
- [ ] `scenarios/scenario1/mission.json` (collection: testing)
|
||||
- [ ] `scenarios/scenario2/mission.json` (collection: testing)
|
||||
- [ ] `scenarios/scenario3/mission.json` (collection: testing)
|
||||
- [ ] `scenarios/scenario4/mission.json` (collection: testing)
|
||||
|
||||
### Test scenarios (minimal metadata):
|
||||
- [ ] `scenarios/npc-*/mission.json` (collection: testing)
|
||||
- [ ] `scenarios/test-*/mission.json` (collection: testing)
|
||||
|
||||
---
|
||||
|
||||
## Phase 6: Testing
|
||||
|
||||
### Standalone mode testing
|
||||
```bash
|
||||
# Run migrations
|
||||
rails db:migrate
|
||||
|
||||
# Run seeds
|
||||
rails db:seed
|
||||
|
||||
# Verify in console
|
||||
rails c
|
||||
BreakEscape::Mission.count
|
||||
BreakEscape::Mission.first.break_escape_cyboks.count
|
||||
BreakEscape::Mission.collections
|
||||
```
|
||||
|
||||
### Hacktivity mode testing (if applicable)
|
||||
```bash
|
||||
rails c
|
||||
# Check both tables populated
|
||||
BreakEscape::Mission.first.cyboks.count
|
||||
::Cybok.where(cybokable_type: 'BreakEscape::Mission').count
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Verification Queries
|
||||
|
||||
```ruby
|
||||
# All missions with CyBOK data
|
||||
BreakEscape::Mission.joins(:break_escape_cyboks).distinct
|
||||
|
||||
# Missions by collection
|
||||
BreakEscape::Mission.by_collection('testing')
|
||||
BreakEscape::Mission.by_collection('security_investigations')
|
||||
|
||||
# All collections in use
|
||||
BreakEscape::Mission.collections
|
||||
|
||||
# CyBOK entries for a mission
|
||||
mission = BreakEscape::Mission.find_by(name: 'biometric_breach')
|
||||
mission.break_escape_cyboks.map { |c| "#{c.ka}: #{c.topic}" }
|
||||
|
||||
# Missions by KA code
|
||||
BreakEscape::Cybok.where(ka: 'AAA').map(&:cybokable).uniq
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Rollback Commands (if needed)
|
||||
|
||||
```bash
|
||||
# Rollback migrations
|
||||
rails db:rollback STEP=2
|
||||
|
||||
# Reseed with old data
|
||||
rails db:seed
|
||||
```
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"display_name": "Biometric Breach",
|
||||
"description": "Investigate a security breach at a high-security research facility. Use biometric forensics tools to identify the intruder, track their movements through the facility, and recover stolen research data before it leaves the building.",
|
||||
"difficulty_level": 3,
|
||||
"secgen_scenario": null,
|
||||
"collection": "security_investigations",
|
||||
"cybok": [
|
||||
{
|
||||
"ka": "AAA",
|
||||
"topic": "Authentication",
|
||||
"keywords": ["Biometric authentication", "Fingerprint analysis", "Identity verification", "Multi-factor authentication"]
|
||||
},
|
||||
{
|
||||
"ka": "F",
|
||||
"topic": "Artifact Analysis",
|
||||
"keywords": ["Digital forensics", "Evidence collection", "Fingerprint forensics"]
|
||||
},
|
||||
{
|
||||
"ka": "SOIM",
|
||||
"topic": "Security Operations & Incident Management",
|
||||
"keywords": ["Incident response", "Security monitoring", "Access control investigation"]
|
||||
},
|
||||
{
|
||||
"ka": "HF",
|
||||
"topic": "Human Factors",
|
||||
"keywords": ["Physical security bypass", "Social engineering awareness"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"display_name": "CEO Exfiltration",
|
||||
"description": "A corporate espionage scenario where you must navigate executive offices to extract sensitive data. Test your skills in data exfiltration and covert operations.",
|
||||
"difficulty_level": 4,
|
||||
"secgen_scenario": null,
|
||||
"collection": "data_exfiltration",
|
||||
"cybok": [
|
||||
{
|
||||
"ka": "AB",
|
||||
"topic": "Adversarial Behaviours",
|
||||
"keywords": ["Corporate espionage", "Data theft", "Covert operations"]
|
||||
},
|
||||
{
|
||||
"ka": "MAT",
|
||||
"topic": "Malware & Attack Technologies",
|
||||
"keywords": ["Data exfiltration techniques"]
|
||||
},
|
||||
{
|
||||
"ka": "F",
|
||||
"topic": "Forensics",
|
||||
"keywords": ["Anti-forensics", "Evidence handling"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"display_name": "CyBOK Heist",
|
||||
"description": "Recover the Professor's backup of the CyBOK LaTeX source files. Navigate through the department offices, solve puzzles, and crack the safe containing the precious backup HDD.",
|
||||
"difficulty_level": 2,
|
||||
"secgen_scenario": null,
|
||||
"collection": "physical_security",
|
||||
"cybok": [
|
||||
{
|
||||
"ka": "HF",
|
||||
"topic": "Human Factors",
|
||||
"keywords": ["Physical security", "Lock bypass", "Security awareness"]
|
||||
},
|
||||
{
|
||||
"ka": "C",
|
||||
"topic": "Cryptography",
|
||||
"keywords": ["Safe combinations", "Code breaking"]
|
||||
},
|
||||
{
|
||||
"ka": "AC",
|
||||
"topic": "Applied Cryptography",
|
||||
"keywords": ["Encoding", "Cipher solving"]
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user