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,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'cyber_trackr_client/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'cyber_trackr_live'
9
+ spec.version = CyberTrackrClient::VERSION
10
+ spec.authors = ['MITRE Corporation']
11
+ spec.email = ['saf@mitre.org']
12
+ spec.summary = 'OpenAPI specification and Ruby client for cyber.trackr.live API'
13
+ spec.description = 'Provides OpenAPI 3.1.1 specification and Ruby client for accessing DISA STIGs, SRGs, RMF controls, CCIs, and SCAP data via the cyber.trackr.live API'
14
+ spec.homepage = 'https://github.com/mitre/cyber-trackr-live'
15
+ spec.license = 'Apache-2.0'
16
+
17
+ spec.metadata = {
18
+ 'bug_tracker_uri' => 'https://github.com/mitre/cyber-trackr-live/issues',
19
+ 'changelog_uri' => 'https://github.com/mitre/cyber-trackr-live/blob/main/CHANGELOG-GEM.md',
20
+ 'documentation_uri' => 'https://mitre.github.io/cyber-trackr-live/',
21
+ 'homepage_uri' => 'https://github.com/mitre/cyber-trackr-live',
22
+ 'source_code_uri' => 'https://github.com/mitre/cyber-trackr-live',
23
+ 'rubygems_mfa_required' => 'true'
24
+ }
25
+
26
+ spec.files = %w[
27
+ README-GEM.md cyber_trackr_live.gemspec LICENSE.md NOTICE.md CHANGELOG-GEM.md
28
+ CODE_OF_CONDUCT.md CONTRIBUTING.md SECURITY.md
29
+ ] + Dir.glob('lib/**/*', File::FNM_DOTMATCH).reject { |f| File.directory?(f) } +
30
+ Dir.glob('openapi/**/*', File::FNM_DOTMATCH).reject { |f| File.directory?(f) } +
31
+ Dir.glob('examples/**/*', File::FNM_DOTMATCH).reject { |f| File.directory?(f) }
32
+
33
+ spec.require_paths = ['lib']
34
+ spec.required_ruby_version = '>= 3.2.0'
35
+
36
+ # Use the gem-specific README for RubyGems.org
37
+ spec.extra_rdoc_files = ['README-GEM.md']
38
+ spec.rdoc_options = ['--main', 'README-GEM.md']
39
+
40
+ # Runtime dependencies
41
+ spec.add_dependency 'faraday', '~> 2.0'
42
+ spec.add_dependency 'faraday-follow_redirects', '~> 0.3'
43
+ spec.add_dependency 'faraday-multipart', '~> 1.0'
44
+ spec.add_dependency 'marcel', '~> 1.0'
45
+ spec.add_dependency 'yard', '~> 0.9'
46
+
47
+ # Development dependencies
48
+ spec.add_development_dependency 'bundler', '~> 2.0'
49
+ spec.add_development_dependency 'bundler-audit', '~> 0.9'
50
+ spec.add_development_dependency 'minitest', '~> 5.0'
51
+ spec.add_development_dependency 'rake', '~> 13.0'
52
+ spec.add_development_dependency 'rubocop', '~> 1.50'
53
+ spec.add_development_dependency 'rubocop-ast', '~> 1.28'
54
+ spec.add_development_dependency 'simplecov', '~> 0.22'
55
+ spec.add_development_dependency 'webmock', '~> 3.0'
56
+ end
@@ -0,0 +1,208 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+ require 'uri'
5
+ require 'json'
6
+
7
+ module InspecXccdfMapper
8
+ # Client for cyber.trackr.live API
9
+ # Based on the OpenAPI specification in docs/cyber-trackr-openapi.yaml
10
+ class CyberTrackrClient
11
+ BASE_URL = 'https://cyber.trackr.live/api'
12
+ DEFAULT_TIMEOUT = 30
13
+ RATE_LIMIT_DELAY = 0.1 # 100ms between requests
14
+
15
+ attr_reader :rate_limit_delay, :timeout
16
+
17
+ def initialize(rate_limit_delay: RATE_LIMIT_DELAY, timeout: DEFAULT_TIMEOUT)
18
+ @rate_limit_delay = rate_limit_delay
19
+ @timeout = timeout
20
+ @last_request_time = Time.now - rate_limit_delay
21
+ end
22
+
23
+ # Get API documentation
24
+ def api_info
25
+ get('/')
26
+ end
27
+
28
+ # List all STIGs and SRGs (mixed in API)
29
+ # Returns hash with STIG/SRG names as keys, version arrays as values
30
+ def list_all_documents
31
+ get('/stig')
32
+ end
33
+
34
+ # List only STIGs (filters out SRGs)
35
+ def list_stigs
36
+ all_docs = list_all_documents
37
+ all_docs.reject { |name, _| is_srg?(name) }
38
+ end
39
+
40
+ # List only SRGs (filters out STIGs)
41
+ def list_srgs
42
+ all_docs = list_all_documents
43
+ all_docs.select { |name, _| is_srg?(name) }
44
+ end
45
+
46
+ # Get STIG details with all requirements
47
+ def get_stig(title, version, release)
48
+ get("/stig/#{encode_path(title)}/#{version}/#{release}")
49
+ end
50
+
51
+ # Get detailed control information
52
+ def get_control(title, version, release, vuln_id)
53
+ get("/stig/#{encode_path(title)}/#{version}/#{release}/#{vuln_id}")
54
+ end
55
+
56
+ # List RMF controls
57
+ def list_rmf_controls(revision = 5)
58
+ get("/rmf/#{revision}")
59
+ end
60
+
61
+ # Get RMF control details
62
+ def get_rmf_control(revision, control)
63
+ get("/rmf/#{revision}/#{control}")
64
+ end
65
+
66
+ # List CCIs
67
+ def list_ccis
68
+ get('/cci')
69
+ end
70
+
71
+ # Get CCI details
72
+ def get_cci(item)
73
+ get("/cci/#{item}")
74
+ end
75
+
76
+ # Fetch complete STIG with all control details
77
+ def fetch_complete_stig(title, version, release, &block)
78
+ stig = get_stig(title, version, release)
79
+ return nil unless stig && stig['requirements']
80
+
81
+ total = stig['requirements'].size
82
+ completed_requirements = {}
83
+
84
+ stig['requirements'].each_with_index do |(vuln_id, summary), index|
85
+ yield(index + 1, total, vuln_id) if block_given?
86
+
87
+ begin
88
+ control_details = get_control(title, version, release, vuln_id)
89
+ completed_requirements[vuln_id] = summary.merge(control_details)
90
+ rescue => e
91
+ # Keep original summary if fetch fails
92
+ completed_requirements[vuln_id] = summary.merge('_error' => e.message)
93
+ end
94
+ end
95
+
96
+ stig.merge('requirements' => completed_requirements)
97
+ end
98
+
99
+ private
100
+
101
+ def get(path)
102
+ enforce_rate_limit
103
+
104
+ uri = URI.join(BASE_URL, path)
105
+
106
+ response = Net::HTTP.start(uri.host, uri.port,
107
+ use_ssl: true,
108
+ read_timeout: timeout,
109
+ open_timeout: timeout) do |http|
110
+ request = Net::HTTP::Get.new(uri)
111
+ request['Accept'] = 'application/json'
112
+ request['User-Agent'] = 'InSpec-XCCDF-Mapper/1.0'
113
+ http.request(request)
114
+ end
115
+
116
+ case response.code
117
+ when '200'
118
+ JSON.parse(response.body)
119
+ when '404'
120
+ raise NotFoundError, "Resource not found: #{path}"
121
+ when '500'
122
+ error = JSON.parse(response.body) rescue {}
123
+ raise ServerError, error['detail'] || "Server error: #{response.code}"
124
+ else
125
+ raise ApiError, "HTTP #{response.code}: #{response.message}"
126
+ end
127
+ rescue Net::ReadTimeout, Net::OpenTimeout
128
+ raise TimeoutError, "Request timed out after #{timeout} seconds"
129
+ end
130
+
131
+ def enforce_rate_limit
132
+ elapsed = Time.now - @last_request_time
133
+ sleep_time = rate_limit_delay - elapsed
134
+ sleep(sleep_time) if sleep_time > 0
135
+ @last_request_time = Time.now
136
+ end
137
+
138
+ def encode_path(component)
139
+ # URL encode but keep forward slashes
140
+ component.gsub(' ', '_').gsub('/', '_')
141
+ end
142
+
143
+ # Determine if a document name is an SRG vs STIG
144
+ def is_srg?(document_name)
145
+ srg_patterns = [
146
+ /Security.*Requirements.*Guide/i,
147
+ /\(SRG\)/,
148
+ /_SRG$/,
149
+ # Specific known SRG patterns
150
+ /^Application_Layer_Gateway.*Security_Requirements_Guide/,
151
+ /^Application_Server_Security_Requirements_Guide/,
152
+ /^Database_Security_Requirements_Guide/,
153
+ /^Network_Device_Management_Security_Requirements_Guide/,
154
+ /^Operating_System_Security_Requirements_Guide/,
155
+ /^AAA.*Security_Requirements_Guide/,
156
+ /^Central_Log_Server_Security_Requirements_Guide/,
157
+ /^DNS_Server_Security_Requirements_Guide/,
158
+ /^Email_Services_Security_Requirements_Guide/,
159
+ /^General_Purpose_Operating_System_Security_Requirements_Guide/,
160
+ /^Multifunction_Device_or_Network_Function_Virtualization_Security_Requirements_Guide/,
161
+ /^Network_Infrastructure_Security_Requirements_Guide/,
162
+ /^Traditional_Security_Requirements_Guide/,
163
+ /^VPN_Gateway_Security_Requirements_Guide/,
164
+ /^Web_Server_Security_Requirements_Guide/
165
+ ]
166
+
167
+ srg_patterns.any? { |pattern| document_name.match?(pattern) }
168
+ end
169
+
170
+ # Classify a requirement as technology-generic (SRG) vs vendor-specific (STIG)
171
+ def is_generic_requirement?(control_detail)
172
+ # SRG requirements have SRG- group patterns and generic language
173
+ group = control_detail['group']
174
+ check_text = control_detail['check-text'] || ''
175
+ fix_text = control_detail['fix-text'] || ''
176
+
177
+ return true if group&.start_with?('SRG-')
178
+
179
+ # Generic language patterns (no vendor-specific commands)
180
+ generic_patterns = [
181
+ /configure the \w+ to/i,
182
+ /verify the \w+ is configured/i,
183
+ /the \w+ must be configured/i
184
+ ]
185
+
186
+ vendor_specific_patterns = [
187
+ /show configuration/i,
188
+ /set \w+/i,
189
+ /\$ \w+/, # Shell commands
190
+ />\s*\w+/, # CLI prompts
191
+ /\/etc\//, # File paths
192
+ /systemctl/,
193
+ /grep/
194
+ ]
195
+
196
+ has_generic = generic_patterns.any? { |p| check_text.match?(p) || fix_text.match?(p) }
197
+ has_vendor_specific = vendor_specific_patterns.any? { |p| check_text.match?(p) || fix_text.match?(p) }
198
+
199
+ has_generic && !has_vendor_specific
200
+ end
201
+
202
+ # Custom error classes
203
+ class ApiError < StandardError; end
204
+ class NotFoundError < ApiError; end
205
+ class ServerError < ApiError; end
206
+ class TimeoutError < ApiError; end
207
+ end
208
+ end
@@ -0,0 +1,174 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'json'
6
+ require 'net/http'
7
+ require 'uri'
8
+ require 'fileutils'
9
+ require 'optparse'
10
+ require 'logger'
11
+
12
+ # Script to fetch complete STIG data with all control details
13
+ class CompleteStigFetcher
14
+ BASE_URL = 'https://cyber.trackr.live'
15
+ RATE_LIMIT_DELAY = 0.1 # 100ms between requests
16
+
17
+ def initialize(logger: Logger.new($stdout))
18
+ @logger = logger
19
+ end
20
+
21
+ def fetch_complete_stig(input_file, output_file = nil)
22
+ @logger.info "Loading STIG from: #{input_file}"
23
+
24
+ # Load the base STIG file
25
+ stig_data = JSON.parse(File.read(input_file))
26
+
27
+ # Determine output filename
28
+ output_file ||= input_file.sub('.json', '_complete.json')
29
+
30
+ # Check if we have requirements to process
31
+ unless stig_data['requirements']
32
+ @logger.warn "No requirements found in STIG file"
33
+ return
34
+ end
35
+
36
+ @logger.info "Found #{stig_data['requirements'].size} controls to fetch"
37
+
38
+ # Fetch detailed data for each control
39
+ completed_controls = []
40
+ failed_controls = []
41
+
42
+ stig_data['requirements'].each_with_index do |control, index|
43
+ @logger.info "[#{index + 1}/#{stig_data['requirements'].size}] Fetching control #{control['id']}"
44
+
45
+ begin
46
+ # Fetch detailed control data
47
+ detailed_control = fetch_control_details(control)
48
+
49
+ # Merge the detailed data with the base control
50
+ completed_control = control.merge(detailed_control)
51
+ completed_controls << completed_control
52
+
53
+ # Rate limiting
54
+ sleep RATE_LIMIT_DELAY
55
+ rescue => e
56
+ @logger.error "Failed to fetch control #{control['id']}: #{e.message}"
57
+ failed_controls << control['id']
58
+ # Keep the original control data even if fetch fails
59
+ completed_controls << control
60
+ end
61
+ end
62
+
63
+ # Update the STIG data with complete controls
64
+ complete_stig = stig_data.dup
65
+ complete_stig['requirements'] = completed_controls
66
+ complete_stig['_metadata'] = {
67
+ 'fetched_at' => Time.now.utc.iso8601,
68
+ 'total_controls' => stig_data['requirements'].size,
69
+ 'successfully_fetched' => stig_data['requirements'].size - failed_controls.size,
70
+ 'failed_controls' => failed_controls
71
+ }
72
+
73
+ # Write the complete STIG file
74
+ @logger.info "Writing complete STIG to: #{output_file}"
75
+ File.write(output_file, JSON.pretty_generate(complete_stig))
76
+
77
+ # Summary
78
+ @logger.info "Complete! Fetched #{stig_data['requirements'].size - failed_controls.size}/#{stig_data['requirements'].size} controls"
79
+ @logger.warn "Failed to fetch #{failed_controls.size} controls: #{failed_controls.join(', ')}" unless failed_controls.empty?
80
+
81
+ output_file
82
+ end
83
+
84
+ private
85
+
86
+ def fetch_control_details(control)
87
+ return {} unless control['link']
88
+
89
+ uri = URI.join(BASE_URL, control['link'])
90
+
91
+ response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
92
+ request = Net::HTTP::Get.new(uri)
93
+ request['Accept'] = 'application/json'
94
+ http.request(request)
95
+ end
96
+
97
+ if response.code == '200'
98
+ control_data = JSON.parse(response.body)
99
+
100
+ # Map the actual API response fields to our expected structure
101
+ {
102
+ 'title' => control_data['requirement-title'],
103
+ 'description' => control_data['requirement-description'],
104
+ 'check_text' => control_data['check-text'],
105
+ 'fix_text' => control_data['fix-text'],
106
+ 'severity' => control_data['severity'],
107
+ 'version' => control_data['version'],
108
+ 'rule' => control_data['rule'],
109
+ 'group' => control_data['group'],
110
+ 'check_id' => control_data['check-id'],
111
+ 'fix_id' => control_data['fix-id'],
112
+ 'identifiers' => control_data['identifiers'],
113
+ 'mitigation_statement' => control_data['mitigation-statement']
114
+ }.compact
115
+ else
116
+ raise "HTTP #{response.code}: #{response.message}"
117
+ end
118
+ end
119
+ end
120
+
121
+ # CLI interface
122
+ if __FILE__ == $0
123
+ options = {
124
+ output: nil,
125
+ verbose: false
126
+ }
127
+
128
+ OptionParser.new do |opts|
129
+ opts.banner = "Usage: fetch-complete-stig [options] <stig_file>"
130
+
131
+ opts.on("-o", "--output FILE", "Output file (default: <input>_complete.json)") do |o|
132
+ options[:output] = o
133
+ end
134
+
135
+ opts.on("-v", "--verbose", "Verbose output") do
136
+ options[:verbose] = true
137
+ end
138
+
139
+ opts.on("-h", "--help", "Show this help") do
140
+ puts opts
141
+ exit
142
+ end
143
+ end.parse!
144
+
145
+ if ARGV.empty?
146
+ puts "Error: Please specify a STIG JSON file"
147
+ puts "Usage: fetch-complete-stig <stig_file>"
148
+ exit 1
149
+ end
150
+
151
+ input_file = ARGV[0]
152
+
153
+ unless File.exist?(input_file)
154
+ puts "Error: File not found: #{input_file}"
155
+ exit 1
156
+ end
157
+
158
+ # Set up logger
159
+ logger = Logger.new($stdout)
160
+ logger.level = options[:verbose] ? Logger::DEBUG : Logger::INFO
161
+ logger.formatter = proc do |severity, datetime, progname, msg|
162
+ "#{severity}: #{msg}\n"
163
+ end
164
+
165
+ # Fetch the complete STIG
166
+ fetcher = CompleteStigFetcher.new(logger: logger)
167
+ begin
168
+ output_file = fetcher.fetch_complete_stig(input_file, options[:output])
169
+ puts "\nComplete STIG saved to: #{output_file}"
170
+ rescue => e
171
+ logger.error "Failed to fetch complete STIG: #{e.message}"
172
+ exit 1
173
+ end
174
+ end
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'json'
6
+ require 'optparse'
7
+ require_relative 'cyber_trackr_client'
8
+
9
+ # Script to fetch complete STIG data directly from API
10
+ options = {}
11
+ OptionParser.new do |opts|
12
+ opts.banner = "Usage: fetch-stig-complete [options]"
13
+
14
+ opts.on("-n", "--name NAME", "STIG name (e.g., 'Juniper_SRX_Services_Gateway_ALG')") do |n|
15
+ options[:name] = n
16
+ end
17
+
18
+ opts.on("-v", "--version VERSION", "STIG version (e.g., '3')") do |v|
19
+ options[:version] = v
20
+ end
21
+
22
+ opts.on("-r", "--release RELEASE", "STIG release (e.g., '3')") do |r|
23
+ options[:release] = r
24
+ end
25
+
26
+ opts.on("-o", "--output FILE", "Output file (default: NAME_v{VERSION}r{RELEASE}_complete.json)") do |o|
27
+ options[:output] = o
28
+ end
29
+
30
+ opts.on("-h", "--help", "Show this help message") do
31
+ puts opts
32
+ exit
33
+ end
34
+ end.parse!
35
+
36
+ # Validate required options
37
+ unless options[:name] && options[:version] && options[:release]
38
+ puts "Error: --name, --version, and --release are required"
39
+ puts "Example: ./fetch-stig-complete -n Juniper_SRX_Services_Gateway_ALG -v 3 -r 3"
40
+ exit 1
41
+ end
42
+
43
+ # Set default output filename
44
+ options[:output] ||= "#{options[:name]}_v#{options[:version]}r#{options[:release]}_complete.json"
45
+
46
+ # Create client instance
47
+ client = InspecXccdfMapper::CyberTrackrClient.new
48
+
49
+ puts "Fetching complete STIG: #{options[:name]} v#{options[:version]}r#{options[:release]}"
50
+ puts "This will make multiple API calls and may take a minute..."
51
+
52
+ begin
53
+ # Fetch complete STIG with all control details
54
+ complete_stig = client.fetch_complete_stig(options[:name], options[:version], options[:release]) do |current, total, vuln_id|
55
+ print "\rFetching control #{current}/#{total}: #{vuln_id} "
56
+ end
57
+
58
+ puts "\nSuccessfully fetched #{complete_stig['requirements'].size} controls"
59
+
60
+ # Write to output file
61
+ File.write(options[:output], JSON.pretty_generate(complete_stig))
62
+ puts "Saved complete STIG to: #{options[:output]}"
63
+
64
+ rescue => e
65
+ puts "\nError: #{e.message}"
66
+ exit 1
67
+ end
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'json'
6
+ require 'optparse'
7
+ require_relative '../generated-client/lib/cyber_trackr_client'
8
+
9
+ # Script to fetch complete STIG data directly from API using generated client
10
+ options = {}
11
+ OptionParser.new do |opts|
12
+ opts.banner = "Usage: fetch-stig-direct [options]"
13
+
14
+ opts.on("-n", "--name NAME", "STIG name (e.g., 'Juniper_SRX_Services_Gateway_ALG')") do |n|
15
+ options[:name] = n
16
+ end
17
+
18
+ opts.on("-v", "--version VERSION", "STIG version (e.g., '3')") do |v|
19
+ options[:version] = v
20
+ end
21
+
22
+ opts.on("-r", "--release RELEASE", "STIG release (e.g., '3')") do |r|
23
+ options[:release] = r
24
+ end
25
+
26
+ opts.on("-o", "--output FILE", "Output file (default: NAME_VERSION_RELEASE_complete.json)") do |o|
27
+ options[:output] = o
28
+ end
29
+
30
+ opts.on("-h", "--help", "Show this help message") do
31
+ puts opts
32
+ exit
33
+ end
34
+ end.parse!
35
+
36
+ # Validate required options
37
+ unless options[:name] && options[:version] && options[:release]
38
+ puts "Error: --name, --version, and --release are required"
39
+ puts "Example: ./fetch-stig-direct -n Juniper_SRX_Services_Gateway_ALG -v 3 -r 3"
40
+ exit 1
41
+ end
42
+
43
+ # Configure client
44
+ CyberTrackrClient.configure do |config|
45
+ config.host = 'cyber.trackr.live'
46
+ config.base_path = '/api'
47
+ end
48
+
49
+ # Create API instance
50
+ api = CyberTrackrClient::STIGSRGApi.new
51
+
52
+ # Set default output filename
53
+ options[:output] ||= "#{options[:name]}_#{options[:version]}_#{options[:release]}_complete.json"
54
+
55
+ puts "Fetching STIG: #{options[:name]} v#{options[:version]}r#{options[:release]}"
56
+
57
+ begin
58
+ # Fetch the STIG with all requirements
59
+ stig = api.stig_title_version_release_get(options[:name], options[:version], options[:release])
60
+
61
+ # Check if we got requirements
62
+ if stig.requirements.nil? || stig.requirements.empty?
63
+ puts "Warning: No requirements found in STIG"
64
+ else
65
+ puts "Found #{stig.requirements.size} requirements"
66
+
67
+ # Fetch detailed data for each requirement
68
+ stig.requirements.each_with_index do |req, index|
69
+ print "\rFetching control details: #{index + 1}/#{stig.requirements.size}"
70
+
71
+ begin
72
+ # Fetch detailed control data
73
+ detailed = api.stig_title_version_release_vuln_get(
74
+ options[:name],
75
+ options[:version],
76
+ options[:release],
77
+ req.id
78
+ )
79
+
80
+ # Update the requirement with detailed data
81
+ # (The detailed response should already include all fields)
82
+
83
+ # Small delay to be respectful to the API
84
+ sleep 0.1
85
+ rescue => e
86
+ puts "\nWarning: Failed to fetch details for #{req.id}: #{e.message}"
87
+ end
88
+ end
89
+ puts "\nCompleted fetching all control details"
90
+ end
91
+
92
+ # Write to output file
93
+ File.write(options[:output], JSON.pretty_generate(stig.to_hash))
94
+ puts "Saved complete STIG to: #{options[:output]}"
95
+
96
+ rescue => e
97
+ puts "Error: #{e.message}"
98
+ exit 1
99
+ end
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative '../cyber_trackr_helper'
5
+
6
+ # Example: Using the Cyber Trackr Helper
7
+ client = CyberTrackrHelper::Client.new
8
+
9
+ # 1. Search for specific STIGs
10
+ puts "=== Searching for Juniper STIGs ==="
11
+ juniper_docs = client.search_documents('juniper')
12
+ juniper_docs.each do |name, versions|
13
+ type = client.is_srg?(name) ? 'SRG' : 'STIG'
14
+ puts "#{name} (#{type}): #{versions.size} versions"
15
+ end
16
+
17
+ # 2. Get the latest version
18
+ puts "\n=== Latest Juniper ALG STIG ==="
19
+ latest = client.get_latest_version('Juniper_SRX_Services_Gateway_ALG')
20
+ if latest
21
+ puts "Latest version: v#{latest['version']}r#{latest['release']} (#{latest['release_date']})"
22
+ end
23
+
24
+ # 3. Generate compliance summary
25
+ puts "\n=== Compliance Summary ==="
26
+ summary = client.generate_compliance_summary('Juniper_SRX_Services_Gateway_ALG', '3', '3')
27
+ puts "Total controls: #{summary[:total]}"
28
+ puts "By severity:"
29
+ summary[:by_severity].each do |severity, count|
30
+ puts " #{severity.capitalize}: #{count}"
31
+ end
32
+
33
+ # 4. Fetch controls by severity
34
+ puts "\n=== High Severity Controls ==="
35
+ high_controls = client.fetch_controls_by_severity('Juniper_SRX_Services_Gateway_ALG', '3', '3', 'high')
36
+ high_controls.each do |control|
37
+ puts "#{control.id}: #{control.requirement_title[0..60]}..."
38
+ end
39
+
40
+ # 5. Download complete STIG with progress
41
+ puts "\n=== Downloading Complete STIG ==="
42
+ complete_stig = client.fetch_complete_stig('Juniper_SRX_Services_Gateway_ALG', '3', '3') do |current, total, vuln_id|
43
+ print "\rFetching control #{current}/#{total}: #{vuln_id} "
44
+ end
45
+ puts "\nDownloaded #{complete_stig[:requirements].size} complete controls"
46
+
47
+ # Save to file
48
+ require 'json'
49
+ File.write('juniper_alg_complete.json', JSON.pretty_generate(complete_stig))
50
+ puts "Saved to: juniper_alg_complete.json"