Skip to content

Commit c8be36b

Browse files
akofinkdLobatog
authored andcommitted
Add an SSG module for interacting with SSG (#12)
Policies and Rules come from the SCAP Security Guide, generated from https://github.com/ComplianceAsCode/content. This adds rake tasks to download and unarchive released versions of SSG for a given application, and a shortcut rake task for all the RHEL SSG content. Signed-off-by: Andrew Kofink <akofink@redhat.com>
1 parent 191f7fc commit c8be36b

File tree

10 files changed

+226
-0
lines changed

10 files changed

+226
-0
lines changed

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,17 @@ parser.rule_results # [#<OpenscapParser::RuleResult:0x00005576e8022f60 @id="xccd
3535
# and more!
3636
```
3737

38+
### Fetching SCAP Security Guide Content
39+
40+
This gem includes a rake task to sync content from the [ComplianceAsCode project](https://github.com/ComplianceAsCode/content). The following examples show how to download and exract datastream files from the released versions:
41+
42+
```sh
43+
rake ssg:sync DATASTREAMS=latest:fedora # fetch and extract the latest fedora datastream
44+
rake ssg:sync DATASTREAMS=v0.1.45:fedora,v0.1.45:firefox # fetch and extract tag v0.1.45 for fedora and firefox datastreams
45+
rake ssg:sync_rhel # fetch and extract the latest released versions of the RHEL 6, 7, and 8 datastreams
46+
```
47+
48+
An SSG version will be downloaded only once, even if it is specified multiple times for multiple datastreams.
3849

3950
## Development
4051

Rakefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
require "bundler/gem_tasks"
22
require "rake/testtask"
33

4+
import "./lib/tasks/ssg.rake"
5+
46
Rake::TestTask.new(:test) do |t|
57
t.libs << "test"
68
t.libs << "lib"

lib/ssg.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
require 'ssg/downloader'
2+
require 'ssg/unarchiver'
3+
4+
module Ssg
5+
end

lib/ssg/downloader.rb

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# frozen_string_literal: true
2+
3+
require 'json'
4+
require 'net/http'
5+
6+
module Ssg
7+
# Downloads SCAP datastreams from the SCAP Security Guide
8+
# https://github.com/ComplianceAsCode/content
9+
class Downloader
10+
RELEASES_API = 'https://api.github.com/repos'\
11+
'/ComplianceAsCode/content/releases/'
12+
SSG_DS_REGEX = /scap-security-guide-(\d+\.)+zip$/
13+
14+
def initialize(version = 'latest')
15+
@release_uri = URI(
16+
"#{RELEASES_API}#{'tags/' unless version[/^latest$/]}#{version}"
17+
)
18+
end
19+
20+
def self.download!(versions = [])
21+
versions.uniq.map do |version|
22+
[version, new(version).fetch_datastream_file]
23+
end.to_h
24+
end
25+
26+
def fetch_datastream_file
27+
puts "Fetching #{datastream_filename}"
28+
get_chunked(datastream_uri)
29+
30+
datastream_filename
31+
end
32+
33+
private
34+
35+
def datastream_uri
36+
@datastream_uri ||= URI(
37+
download_urls.find { |url| url[SSG_DS_REGEX] }
38+
)
39+
end
40+
41+
def download_urls
42+
get_json(@release_uri).dig('assets').map do |asset|
43+
asset.dig('browser_download_url')
44+
end
45+
end
46+
47+
def fetch(request, &block)
48+
Net::HTTP.start(
49+
request.uri.host, request.uri.port,
50+
use_ssl: request.uri.scheme['https']
51+
) do |http|
52+
check_response(http.request(request, &block), &block)
53+
end
54+
end
55+
56+
def get(uri, &block)
57+
fetch(Net::HTTP::Get.new(uri), &block)
58+
end
59+
60+
def head(uri, &block)
61+
fetch(Net::HTTP::Head.new(uri), &block)
62+
end
63+
64+
def check_response(response, &block)
65+
case response
66+
when Net::HTTPSuccess
67+
response
68+
when Net::HTTPRedirection
69+
get(URI(response['location']), &block)
70+
else
71+
response.value
72+
end
73+
end
74+
75+
def get_chunked(uri, filename: datastream_filename)
76+
head(uri) do |response|
77+
next unless Net::HTTPSuccess === response
78+
open(filename, 'wb') do |file|
79+
response.read_body do |chunk|
80+
file.write(chunk)
81+
end
82+
end
83+
end
84+
end
85+
86+
def datastream_filename
87+
datastream_uri.path.split('/').last[SSG_DS_REGEX]
88+
end
89+
90+
def get_json(uri)
91+
JSON.parse(get(uri).body)
92+
end
93+
end
94+
end

lib/ssg/unarchiver.rb

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
module Ssg
2+
class Unarchiver
3+
UNZIP_CMD = ['unzip', '-o']
4+
5+
def initialize(ds_zip_filename, datastreams)
6+
@ds_zip_filename = ds_zip_filename
7+
@datastreams = datastreams
8+
end
9+
10+
def self.unarchive!(ds_zip_filenames, datastreams)
11+
ds_zip_filenames.map do |version, ds_zip_filename|
12+
new(ds_zip_filename, [datastreams[version]].flatten).datastream_files
13+
end
14+
end
15+
16+
def datastream_files
17+
datastream_filenames if system(
18+
*UNZIP_CMD, @ds_zip_filename, *datastream_filenames
19+
)
20+
end
21+
22+
private
23+
24+
def datastream_filenames
25+
@datastreams.map do |datastream|
26+
"#{datastream_dir}/ssg-#{datastream}-ds.xml"
27+
end
28+
end
29+
30+
def datastream_dir
31+
@ds_zip_filename.split('.')[0...-1].join('.')
32+
end
33+
end
34+
end

lib/tasks/ssg.rake

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
desc 'Import or update SCAP datastreams from the SCAP Security Guide'
2+
namespace :ssg do
3+
desc 'Import or update SCAP datastreams for RHEL 6, 7, and 8'
4+
task :sync_rhel do |task|
5+
RHEL_SSG_VERSIONS = (
6+
'v0.1.28:rhel6,'\
7+
'v0.1.43:rhel7,'\
8+
'v0.1.42:rhel8'
9+
)
10+
11+
ENV['DATASTREAMS'] = RHEL_SSG_VERSIONS
12+
Rake::Task['ssg:sync'].invoke
13+
end
14+
15+
desc 'Import or update SCAP datastreams, '\
16+
'provided as a comma separated list: '\
17+
'`rake ssg:sync DATASTREAMS=v0.1.43:rhel7,latest:fedora`'
18+
task :sync do |task|
19+
DATASTREAMS = ENV.fetch('DATASTREAMS', '').split(',')
20+
.inject({}) do |datastreams, arg|
21+
version, datastream = arg.split(':')
22+
datastreams[version] = (datastreams[version] || []).push(datastream)
23+
24+
datastreams
25+
end
26+
27+
require 'ssg'
28+
29+
ds_zip_filenames = Ssg::Downloader.download!(DATASTREAMS.keys)
30+
Ssg::Unarchiver.unarchive!(ds_zip_filenames, DATASTREAMS)
31+
end
32+
end

openscap_parser.gemspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ Gem::Specification.new do |spec|
4040
spec.add_development_dependency "bundler", "~> 2.0"
4141
spec.add_development_dependency "rake", "~> 10.0"
4242
spec.add_development_dependency "minitest", "~> 5.0"
43+
spec.add_development_dependency "mocha", "~> 1.0"
4344
spec.add_development_dependency "shoulda-context"
4445
spec.add_development_dependency "pry"
4546
spec.add_development_dependency "pry-byebug"

test/ssg/downloader_test.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# frozen_string_literal: true
2+
3+
require 'test_helper'
4+
require 'ssg/downloader'
5+
6+
module Ssg
7+
class DownloaderTest < MiniTest::Test
8+
context 'fetch_datastream_file' do
9+
test 'returns the fetched file' do
10+
FILE = 'scap-security-guide-0.0.0.zip'
11+
uri = URI("https://example.com/#{FILE}")
12+
downloader = Downloader.new
13+
downloader.expects(:datastream_uri).
14+
at_least_once.returns(uri)
15+
downloader.expects(:get_chunked).with uri
16+
17+
assert_equal FILE, downloader.fetch_datastream_file
18+
end
19+
end
20+
end
21+
end

test/ssg/unarchiver_test.rb

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# frozen_string_literal: true
2+
3+
require 'test_helper'
4+
require 'ssg/unarchiver'
5+
6+
module Ssg
7+
class UnarchiverTest < MiniTest::Test
8+
context 'datastream_files' do
9+
test 'properly shells out to unzip' do
10+
ZIP_FILE = 'scap-security-guide-0.0.0.zip'
11+
DATASTREAMS = ['rhel6']
12+
FILES = []
13+
unarchiver = Unarchiver.new(ZIP_FILE, DATASTREAMS)
14+
unarchiver.expects(:system).with(
15+
"unzip", "-o",
16+
"scap-security-guide-0.0.0.zip",
17+
"scap-security-guide-0.0.0/ssg-rhel6-ds.xml"
18+
).returns(true)
19+
20+
assert_equal ['scap-security-guide-0.0.0/ssg-rhel6-ds.xml'],
21+
unarchiver.datastream_files
22+
end
23+
end
24+
end
25+
end

test/test_helper.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
require "minitest/autorun"
66
require 'shoulda-context'
7+
require 'mocha/minitest'
78

89
def test(name, &block)
910
test_name = "test_#{name.gsub(/\s+/, '_')}".to_sym

0 commit comments

Comments
 (0)