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,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"
|