cyber_trackr_live 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG-GEM.md +47 -0
- data/CODE_OF_CONDUCT.md +20 -0
- data/CONTRIBUTING.md +422 -0
- data/LICENSE.md +16 -0
- data/NOTICE.md +16 -0
- data/README-GEM.md +75 -0
- data/SECURITY.md +86 -0
- data/cyber_trackr_live.gemspec +56 -0
- data/examples/cyber_trackr_client.rb +208 -0
- data/examples/fetch-complete-stig +174 -0
- data/examples/fetch-stig-complete +67 -0
- data/examples/fetch-stig-direct +99 -0
- data/examples/use_helper.rb +50 -0
- data/lib/cyber_trackr_client/api/api_documentation_api.rb +79 -0
- data/lib/cyber_trackr_client/api/cci_api.rb +147 -0
- data/lib/cyber_trackr_client/api/documents_api.rb +276 -0
- data/lib/cyber_trackr_client/api/rmf_controls_api.rb +272 -0
- data/lib/cyber_trackr_client/api/scap_api.rb +276 -0
- data/lib/cyber_trackr_client/api_client.rb +437 -0
- data/lib/cyber_trackr_client/api_error.rb +58 -0
- data/lib/cyber_trackr_client/configuration.rb +400 -0
- data/lib/cyber_trackr_client/models/api_documentation.rb +238 -0
- data/lib/cyber_trackr_client/models/assessment_procedure.rb +321 -0
- data/lib/cyber_trackr_client/models/cci_detail.rb +391 -0
- data/lib/cyber_trackr_client/models/document_detail.rb +434 -0
- data/lib/cyber_trackr_client/models/document_version.rb +385 -0
- data/lib/cyber_trackr_client/models/error.rb +313 -0
- data/lib/cyber_trackr_client/models/requirement_detail.rb +580 -0
- data/lib/cyber_trackr_client/models/requirement_summary.rb +360 -0
- data/lib/cyber_trackr_client/models/rmf_control_detail.rb +436 -0
- data/lib/cyber_trackr_client/models/rmf_control_list.rb +241 -0
- data/lib/cyber_trackr_client/version.rb +15 -0
- data/lib/cyber_trackr_client.rb +54 -0
- data/lib/cyber_trackr_helper.rb +269 -0
- data/lib/rubocop/cop/cyber_trackr_api/README.md +81 -0
- data/openapi/openapi.yaml +798 -0
- metadata +271 -0
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# #Cyber Trackr API
|
4
|
+
#
|
5
|
+
# Complete OpenAPI 3.1.1 specification for cyber.trackr.live API. This API provides access to DISA STIGs, SRGs, RMF controls, CCIs, and SCAP data. ## DISA Cybersecurity Ecosystem Hierarchy ``` NIST RMF Controls (high-level policy framework) ↓ (decomposed into atomic, testable statements) CCIs (Control Correlation Identifiers - bridge policy to implementation) ↓ (grouped by technology class into generic requirements) SRGs (Security Requirements Guides - technology class \"what\" to do) ↓ (implemented as vendor-specific \"how\" to do it) STIGs (Security Technical Implementation Guides - vendor/product specific) ↓ (automated versions for scanning tools) SCAP (Security Content Automation Protocol documents) ``` ## Critical Relationships - **RMF Controls** contain assessment procedures that reference **CCIs** - **CCIs** map back to **RMF Controls** and forward to **STIG/SRG requirements** - **SRGs** define generic technology requirements that **STIGs** implement specifically - **V-IDs** can appear in both SRG and corresponding STIG (same requirement, different specificity) - **SV-IDs** are XCCDF rule identifiers with revision tracking across document releases
|
6
|
+
#
|
7
|
+
# The version of the OpenAPI document: 1.0.0
|
8
|
+
#
|
9
|
+
# Generated by: https://openapi-generator.tech
|
10
|
+
# Generator version: 7.14.0
|
11
|
+
#
|
12
|
+
|
13
|
+
# Common files
|
14
|
+
require 'cyber_trackr_client/api_client'
|
15
|
+
require 'cyber_trackr_client/api_error'
|
16
|
+
require 'cyber_trackr_client/version'
|
17
|
+
require 'cyber_trackr_client/configuration'
|
18
|
+
|
19
|
+
# Models
|
20
|
+
require 'cyber_trackr_client/models/api_documentation'
|
21
|
+
require 'cyber_trackr_client/models/assessment_procedure'
|
22
|
+
require 'cyber_trackr_client/models/cci_detail'
|
23
|
+
require 'cyber_trackr_client/models/document_detail'
|
24
|
+
require 'cyber_trackr_client/models/document_version'
|
25
|
+
require 'cyber_trackr_client/models/error'
|
26
|
+
require 'cyber_trackr_client/models/requirement_detail'
|
27
|
+
require 'cyber_trackr_client/models/requirement_summary'
|
28
|
+
require 'cyber_trackr_client/models/rmf_control_detail'
|
29
|
+
require 'cyber_trackr_client/models/rmf_control_list'
|
30
|
+
|
31
|
+
# APIs
|
32
|
+
require 'cyber_trackr_client/api/api_documentation_api'
|
33
|
+
require 'cyber_trackr_client/api/cci_api'
|
34
|
+
require 'cyber_trackr_client/api/documents_api'
|
35
|
+
require 'cyber_trackr_client/api/rmf_controls_api'
|
36
|
+
require 'cyber_trackr_client/api/scap_api'
|
37
|
+
|
38
|
+
module CyberTrackrClient
|
39
|
+
class << self
|
40
|
+
# Customize default settings for the SDK using block.
|
41
|
+
# CyberTrackrClient.configure do |config|
|
42
|
+
# config.username = "xxx"
|
43
|
+
# config.password = "xxx"
|
44
|
+
# end
|
45
|
+
# If no block given, return the default Configuration object.
|
46
|
+
def configure
|
47
|
+
if block_given?
|
48
|
+
yield(Configuration.default)
|
49
|
+
else
|
50
|
+
Configuration.default
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,269 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'cyber_trackr_client'
|
4
|
+
|
5
|
+
# Helper methods for the Cyber Trackr API
|
6
|
+
# Provides convenience methods on top of the generated client
|
7
|
+
module CyberTrackrHelper
|
8
|
+
class Client
|
9
|
+
attr_reader :api_client, :documents_api, :cci_api, :rmf_controls_api, :scap_api
|
10
|
+
|
11
|
+
def initialize(config = {})
|
12
|
+
# Configure the client
|
13
|
+
CyberTrackrClient.configure do |c|
|
14
|
+
c.host = config[:host] || 'cyber.trackr.live'
|
15
|
+
c.base_path = config[:base_path] || '/api'
|
16
|
+
c.debugging = config[:debugging] || false
|
17
|
+
c.timeout = config[:timeout] || 30
|
18
|
+
end
|
19
|
+
|
20
|
+
# Initialize API interfaces
|
21
|
+
@api_client = CyberTrackrClient::ApiClient.default
|
22
|
+
@documents_api = CyberTrackrClient::DocumentsApi.new(@api_client)
|
23
|
+
@cci_api = CyberTrackrClient::CCIApi.new(@api_client)
|
24
|
+
@rmf_controls_api = CyberTrackrClient::RMFControlsApi.new(@api_client)
|
25
|
+
@scap_api = CyberTrackrClient::SCAPApi.new(@api_client)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Fetch a complete STIG with all control details
|
29
|
+
# @param name [String] STIG name
|
30
|
+
# @param version [String] Version number
|
31
|
+
# @param release [String] Release number
|
32
|
+
# @param delay [Float] Delay between API calls in seconds (default: 0.1)
|
33
|
+
# @yield [current, total, vuln_id] Progress callback
|
34
|
+
# @return [Hash] Complete STIG with all control details
|
35
|
+
def fetch_complete_stig(name, version, release, delay: 0.1)
|
36
|
+
# Get the base document
|
37
|
+
doc = @documents_api.get_document(name, version, release)
|
38
|
+
|
39
|
+
# Convert to hash for easier manipulation
|
40
|
+
result = doc.to_hash
|
41
|
+
|
42
|
+
# Check if we have requirements to fetch
|
43
|
+
return result if result[:requirements].nil? || result[:requirements].empty?
|
44
|
+
|
45
|
+
total = result[:requirements].size
|
46
|
+
|
47
|
+
# Fetch detailed data for each requirement
|
48
|
+
index = 0
|
49
|
+
result[:requirements].each_key do |vuln_id|
|
50
|
+
# Progress callback
|
51
|
+
yield(index + 1, total, vuln_id) if block_given?
|
52
|
+
|
53
|
+
begin
|
54
|
+
# Fetch detailed control data
|
55
|
+
detailed = @documents_api.get_requirement(name, version, release, vuln_id)
|
56
|
+
|
57
|
+
# Replace summary with detailed data
|
58
|
+
result[:requirements][vuln_id] = detailed.to_hash
|
59
|
+
|
60
|
+
# Rate limiting
|
61
|
+
sleep delay if delay.positive? && index < total - 1
|
62
|
+
rescue StandardError => e
|
63
|
+
warn "Failed to fetch details for #{vuln_id}: #{e.message}"
|
64
|
+
end
|
65
|
+
|
66
|
+
index += 1
|
67
|
+
end
|
68
|
+
|
69
|
+
result
|
70
|
+
end
|
71
|
+
|
72
|
+
# List all STIGs (filters out SRGs)
|
73
|
+
# @return [Hash] Hash of STIG names to version arrays
|
74
|
+
def list_stigs
|
75
|
+
all_docs = @documents_api.list_all_documents
|
76
|
+
all_docs.reject { |name, _versions| is_srg?(name) }
|
77
|
+
end
|
78
|
+
|
79
|
+
# List all SRGs (filters out STIGs)
|
80
|
+
# @return [Hash] Hash of SRG names to version arrays
|
81
|
+
def list_srgs
|
82
|
+
all_docs = @documents_api.list_all_documents
|
83
|
+
all_docs.select { |name, _versions| is_srg?(name) }
|
84
|
+
end
|
85
|
+
|
86
|
+
# Check if a document name is an SRG
|
87
|
+
# @param name [String] Document name
|
88
|
+
# @return [Boolean] true if SRG, false if STIG
|
89
|
+
def is_srg?(name)
|
90
|
+
name_str = name.to_s
|
91
|
+
name_str.include?('Security_Requirements_Guide') ||
|
92
|
+
name_str.include?('(SRG)') ||
|
93
|
+
name_str.end_with?('SRG')
|
94
|
+
end
|
95
|
+
|
96
|
+
# Search for documents by keyword
|
97
|
+
# @param keyword [String] Search term
|
98
|
+
# @param type [Symbol] :all, :stig, or :srg
|
99
|
+
# @return [Hash] Matching documents
|
100
|
+
def search_documents(keyword, type: :all)
|
101
|
+
all_docs = @documents_api.list_all_documents
|
102
|
+
|
103
|
+
# Filter by type if requested
|
104
|
+
filtered = case type
|
105
|
+
when :stig
|
106
|
+
all_docs.reject { |name, _| is_srg?(name) }
|
107
|
+
when :srg
|
108
|
+
all_docs.select { |name, _| is_srg?(name) }
|
109
|
+
else
|
110
|
+
all_docs
|
111
|
+
end
|
112
|
+
|
113
|
+
# Search by keyword
|
114
|
+
filtered.select { |name, _| name.to_s.downcase.include?(keyword.downcase) }
|
115
|
+
end
|
116
|
+
|
117
|
+
# Get the latest version of a document
|
118
|
+
# @param name [String] Document name
|
119
|
+
# @return [Hash, nil] Latest version info or nil if not found
|
120
|
+
def get_latest_version(name)
|
121
|
+
all_docs = @documents_api.list_all_documents
|
122
|
+
# Handle both string and symbol keys
|
123
|
+
versions = all_docs[name] || all_docs[name.to_sym]
|
124
|
+
|
125
|
+
return nil if versions.nil? || versions.empty?
|
126
|
+
|
127
|
+
# Sort by version and release, return the latest
|
128
|
+
# Handle both hash and object formats
|
129
|
+
versions.max_by do |v|
|
130
|
+
if v.respond_to?(:version)
|
131
|
+
[v.version.to_i, v.release.to_f]
|
132
|
+
else
|
133
|
+
[v['version'].to_i, v['release'].to_f]
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# Fetch all controls for a specific severity
|
139
|
+
# @param name [String] STIG name
|
140
|
+
# @param version [String] Version number
|
141
|
+
# @param release [String] Release number
|
142
|
+
# @param severity [String] Severity level (low, medium, high)
|
143
|
+
# @return [Array<Hash>] Controls matching the severity
|
144
|
+
def fetch_controls_by_severity(name, version, release, severity)
|
145
|
+
doc = @documents_api.get_document(name, version, release)
|
146
|
+
|
147
|
+
doc.requirements.values.select { |req| req.severity&.downcase == severity.downcase }
|
148
|
+
end
|
149
|
+
|
150
|
+
# Get CCIs for a specific RMF control
|
151
|
+
# @param control [String] RMF control ID (e.g., "AC-1")
|
152
|
+
# @param revision [Integer] RMF revision (4 or 5)
|
153
|
+
# @return [Array<String>] CCI IDs that map to this control
|
154
|
+
def get_ccis_for_rmf_control(control, revision = 5)
|
155
|
+
# Get all CCIs
|
156
|
+
all_ccis = @cci_api.list_ccis
|
157
|
+
|
158
|
+
# Filter CCIs that reference this control
|
159
|
+
matching_ccis = []
|
160
|
+
|
161
|
+
all_ccis.each_key do |cci_id|
|
162
|
+
# Get detailed CCI info to check RMF mapping
|
163
|
+
|
164
|
+
detailed = @cci_api.get_cci_details(cci_id)
|
165
|
+
|
166
|
+
# Check if this CCI maps to our control
|
167
|
+
if detailed.assessment_procedures&.any? do |ap|
|
168
|
+
ap['control_identifier']&.upcase == control.upcase &&
|
169
|
+
ap['nist_control_family'] == "NIST-800-53-R#{revision}"
|
170
|
+
end
|
171
|
+
matching_ccis << cci_id
|
172
|
+
end
|
173
|
+
rescue StandardError => e
|
174
|
+
warn "Error fetching CCI #{cci_id}: #{e.message}"
|
175
|
+
end
|
176
|
+
|
177
|
+
matching_ccis
|
178
|
+
end
|
179
|
+
|
180
|
+
# Batch download multiple STIGs
|
181
|
+
# @param stig_list [Array<Hash>] Array of hashes with :name, :version, :release keys
|
182
|
+
# @param output_dir [String] Directory to save files
|
183
|
+
# @param delay [Float] Delay between downloads
|
184
|
+
# @yield [index, total, name] Progress callback
|
185
|
+
def batch_download_stigs(stig_list, output_dir, delay: 1.0)
|
186
|
+
require 'fileutils'
|
187
|
+
require 'json'
|
188
|
+
|
189
|
+
FileUtils.mkdir_p(output_dir)
|
190
|
+
|
191
|
+
stig_list.each_with_index do |stig_info, index|
|
192
|
+
name = stig_info[:name]
|
193
|
+
version = stig_info[:version]
|
194
|
+
release = stig_info[:release]
|
195
|
+
|
196
|
+
yield(index + 1, stig_list.size, name) if block_given?
|
197
|
+
|
198
|
+
begin
|
199
|
+
# Fetch complete STIG
|
200
|
+
complete_stig = fetch_complete_stig(name, version, release) do |curr, total, vuln|
|
201
|
+
puts " Fetching control #{curr}/#{total}: #{vuln}"
|
202
|
+
end
|
203
|
+
|
204
|
+
# Save to file
|
205
|
+
filename = "#{name}_v#{version}r#{release}.json"
|
206
|
+
filepath = File.join(output_dir, filename)
|
207
|
+
|
208
|
+
File.write(filepath, JSON.pretty_generate(complete_stig))
|
209
|
+
puts "Saved: #{filepath}"
|
210
|
+
|
211
|
+
# Rate limiting between STIGs
|
212
|
+
sleep delay if delay.positive? && index < stig_list.size - 1
|
213
|
+
rescue StandardError => e
|
214
|
+
warn "Failed to download #{name}: #{e.message}"
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
# Generate a compliance summary for a STIG
|
220
|
+
# @param name [String] STIG name
|
221
|
+
# @param version [String] Version number
|
222
|
+
# @param release [String] Release number
|
223
|
+
# @return [Hash] Summary with counts by severity
|
224
|
+
def generate_compliance_summary(name, version, release)
|
225
|
+
doc = @documents_api.get_document(name, version, release)
|
226
|
+
|
227
|
+
summary = {
|
228
|
+
total: doc.requirements.size,
|
229
|
+
by_severity: {
|
230
|
+
high: 0,
|
231
|
+
medium: 0,
|
232
|
+
low: 0
|
233
|
+
}
|
234
|
+
}
|
235
|
+
|
236
|
+
doc.requirements.each_value do |req|
|
237
|
+
severity = req.severity&.downcase&.to_sym
|
238
|
+
summary[:by_severity][severity] += 1 if summary[:by_severity].key?(severity)
|
239
|
+
end
|
240
|
+
|
241
|
+
summary
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
# Example usage:
|
247
|
+
if __FILE__ == $PROGRAM_NAME
|
248
|
+
client = CyberTrackrHelper::Client.new
|
249
|
+
|
250
|
+
# List all STIGs
|
251
|
+
stigs = client.list_stigs
|
252
|
+
puts "Found #{stigs.size} STIGs"
|
253
|
+
|
254
|
+
# Search for Juniper STIGs
|
255
|
+
juniper_stigs = client.search_documents('juniper', type: :stig)
|
256
|
+
puts "\nJuniper STIGs:"
|
257
|
+
juniper_stigs.each_key do |name|
|
258
|
+
latest = client.get_latest_version(name)
|
259
|
+
puts " #{name}: v#{latest['version']}r#{latest['release']}"
|
260
|
+
end
|
261
|
+
|
262
|
+
# Fetch a complete STIG with progress
|
263
|
+
puts "\nFetching complete STIG..."
|
264
|
+
complete = client.fetch_complete_stig('Juniper_SRX_Services_Gateway_ALG', '3', '3') do |curr, total, vuln|
|
265
|
+
puts "Progress: #{curr}/#{total} - #{vuln}"
|
266
|
+
end
|
267
|
+
|
268
|
+
puts "Fetched #{complete[:requirements].size} complete controls"
|
269
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# RuboCop Custom Cop Solution for Content-Type Issue
|
2
|
+
|
3
|
+
## Overview
|
4
|
+
We successfully implemented a RuboCop custom cop to automatically fix the Content-Type header issue in the generated OpenAPI client. The cyber.trackr.live API incorrectly returns `text/html` for individual requirement endpoints even though the response body is valid JSON.
|
5
|
+
|
6
|
+
## Solution Components
|
7
|
+
|
8
|
+
### 1. Custom RuboCop Cop
|
9
|
+
**File**: `lib/rubocop/cop/cyber_trackr_api/content_type_fix.rb`
|
10
|
+
|
11
|
+
The cop:
|
12
|
+
- Detects the specific pattern in the `deserialize` method where Content-Type validation happens
|
13
|
+
- Adds a workaround that allows HTML content-type when the body starts with `{` or `[` (JSON)
|
14
|
+
- Uses AST pattern matching to find the exact location
|
15
|
+
- Applies the fix automatically with `--autocorrect`
|
16
|
+
|
17
|
+
### 2. RuboCop Configuration
|
18
|
+
**File**: `.rubocop_post_generate.yml`
|
19
|
+
|
20
|
+
```yaml
|
21
|
+
require:
|
22
|
+
- ./lib/rubocop/cop/cyber_trackr_api/content_type_fix.rb
|
23
|
+
|
24
|
+
CyberTrackrApi/ContentTypeFix:
|
25
|
+
Enabled: true
|
26
|
+
Include:
|
27
|
+
- 'generated-client/lib/cyber_trackr_client/api_client.rb'
|
28
|
+
```
|
29
|
+
|
30
|
+
### 3. Post-Generation Script
|
31
|
+
**File**: `scripts/post_generate_fix.rb`
|
32
|
+
|
33
|
+
Runs RuboCop with the custom cop after client generation to apply the fix.
|
34
|
+
|
35
|
+
### 4. Generation Script
|
36
|
+
**File**: `scripts/generate_client.sh`
|
37
|
+
|
38
|
+
Updated to automatically run post-generation fixes after generating the client.
|
39
|
+
|
40
|
+
## The Applied Fix
|
41
|
+
|
42
|
+
The cop transforms this code:
|
43
|
+
```ruby
|
44
|
+
fail "Content-Type is not supported: #{content_type}" unless json_mime?(content_type)
|
45
|
+
```
|
46
|
+
|
47
|
+
Into this:
|
48
|
+
```ruby
|
49
|
+
# Handle text/html responses that contain JSON (API bug)
|
50
|
+
# TODO: Remove this workaround when cyber.trackr.live fixes Content-Type headers
|
51
|
+
if content_type.include?('text/html') && body.strip.start_with?('{', '[')
|
52
|
+
# Skip the normal content-type check - we know it's JSON despite wrong header
|
53
|
+
else
|
54
|
+
fail "Content-Type is not supported: #{content_type}" unless json_mime?(content_type)
|
55
|
+
end
|
56
|
+
```
|
57
|
+
|
58
|
+
## How It Works
|
59
|
+
|
60
|
+
1. When `generate_client.sh` runs, it generates the OpenAPI client
|
61
|
+
2. The post-generation script runs RuboCop with our custom cop
|
62
|
+
3. The cop finds the Content-Type validation in `api_client.rb`
|
63
|
+
4. It applies the workaround automatically
|
64
|
+
5. The client is ready to use with the fix in place
|
65
|
+
|
66
|
+
## Testing
|
67
|
+
|
68
|
+
The fix has been tested and verified:
|
69
|
+
- ✅ Pattern matching works correctly
|
70
|
+
- ✅ Cop detects and fixes the target code
|
71
|
+
- ✅ Generated client handles HTML responses with JSON bodies
|
72
|
+
- ✅ No duplicate JSON parsing or other side effects
|
73
|
+
|
74
|
+
## Future Maintenance
|
75
|
+
|
76
|
+
When cyber.trackr.live fixes their Content-Type headers:
|
77
|
+
1. Remove the custom cop files
|
78
|
+
2. Remove the post-generation script call from `generate_client.sh`
|
79
|
+
3. Regenerate the client without the workaround
|
80
|
+
|
81
|
+
The fix is clearly marked with a TODO comment in the generated code for easy identification.
|