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.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG-GEM.md +47 -0
  3. data/CODE_OF_CONDUCT.md +20 -0
  4. data/CONTRIBUTING.md +422 -0
  5. data/LICENSE.md +16 -0
  6. data/NOTICE.md +16 -0
  7. data/README-GEM.md +75 -0
  8. data/SECURITY.md +86 -0
  9. data/cyber_trackr_live.gemspec +56 -0
  10. data/examples/cyber_trackr_client.rb +208 -0
  11. data/examples/fetch-complete-stig +174 -0
  12. data/examples/fetch-stig-complete +67 -0
  13. data/examples/fetch-stig-direct +99 -0
  14. data/examples/use_helper.rb +50 -0
  15. data/lib/cyber_trackr_client/api/api_documentation_api.rb +79 -0
  16. data/lib/cyber_trackr_client/api/cci_api.rb +147 -0
  17. data/lib/cyber_trackr_client/api/documents_api.rb +276 -0
  18. data/lib/cyber_trackr_client/api/rmf_controls_api.rb +272 -0
  19. data/lib/cyber_trackr_client/api/scap_api.rb +276 -0
  20. data/lib/cyber_trackr_client/api_client.rb +437 -0
  21. data/lib/cyber_trackr_client/api_error.rb +58 -0
  22. data/lib/cyber_trackr_client/configuration.rb +400 -0
  23. data/lib/cyber_trackr_client/models/api_documentation.rb +238 -0
  24. data/lib/cyber_trackr_client/models/assessment_procedure.rb +321 -0
  25. data/lib/cyber_trackr_client/models/cci_detail.rb +391 -0
  26. data/lib/cyber_trackr_client/models/document_detail.rb +434 -0
  27. data/lib/cyber_trackr_client/models/document_version.rb +385 -0
  28. data/lib/cyber_trackr_client/models/error.rb +313 -0
  29. data/lib/cyber_trackr_client/models/requirement_detail.rb +580 -0
  30. data/lib/cyber_trackr_client/models/requirement_summary.rb +360 -0
  31. data/lib/cyber_trackr_client/models/rmf_control_detail.rb +436 -0
  32. data/lib/cyber_trackr_client/models/rmf_control_list.rb +241 -0
  33. data/lib/cyber_trackr_client/version.rb +15 -0
  34. data/lib/cyber_trackr_client.rb +54 -0
  35. data/lib/cyber_trackr_helper.rb +269 -0
  36. data/lib/rubocop/cop/cyber_trackr_api/README.md +81 -0
  37. data/openapi/openapi.yaml +798 -0
  38. 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.