abide_dev_utils 0.5.2 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -1
- data/.rubocop.yml +1 -1
- data/CODEOWNERS +1 -0
- data/abide_dev_utils.gemspec +9 -7
- data/itests.rb +138 -0
- data/lib/abide_dev_utils/cli/comply.rb +38 -20
- data/lib/abide_dev_utils/cli/puppet.rb +136 -11
- data/lib/abide_dev_utils/cli/xccdf.rb +62 -7
- data/lib/abide_dev_utils/comply.rb +446 -78
- data/lib/abide_dev_utils/errors/comply.rb +17 -0
- data/lib/abide_dev_utils/errors/gcloud.rb +27 -0
- data/lib/abide_dev_utils/errors/general.rb +9 -0
- data/lib/abide_dev_utils/errors/ppt.rb +12 -0
- data/lib/abide_dev_utils/errors/xccdf.rb +12 -0
- data/lib/abide_dev_utils/errors.rb +2 -0
- data/lib/abide_dev_utils/gcloud.rb +22 -0
- data/lib/abide_dev_utils/mixins.rb +16 -0
- data/lib/abide_dev_utils/output.rb +7 -3
- data/lib/abide_dev_utils/ppt/api.rb +219 -0
- data/lib/abide_dev_utils/ppt/class_utils.rb +184 -0
- data/lib/abide_dev_utils/ppt/coverage.rb +2 -3
- data/lib/abide_dev_utils/ppt/score_module.rb +162 -0
- data/lib/abide_dev_utils/ppt.rb +138 -49
- data/lib/abide_dev_utils/validate.rb +5 -1
- data/lib/abide_dev_utils/version.rb +1 -1
- data/lib/abide_dev_utils/xccdf.rb +628 -8
- data/lib/abide_dev_utils.rb +1 -0
- metadata +51 -16
- data/lib/abide_dev_utils/utils/general.rb +0 -9
- data/lib/abide_dev_utils/xccdf/cis/hiera.rb +0 -163
- data/lib/abide_dev_utils/xccdf/cis.rb +0 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 66f347b83cd235bff425a1300f86095cd212550c2948438bec76dded1f367f2c
|
4
|
+
data.tar.gz: c4c2ea76f96d88d8bcb89aedeaea795deafc8641addcb729cfba89a3eb383ade
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6cc7f7c3a93bab84f56c00a43ecbace096eb82db3363cdd2a43e41a16da13467c0d612f081cfec30c908563c1e5977e19d913f81362b06db8a03f12236c661f7
|
7
|
+
data.tar.gz: ed7c670cce6b50b14eb9c82f4a3020078bf7f03e15ef4f687ff1925c6505d48accfd9211e1c0ff6e2bbbf68d9664e4bf22242fb01624c223b939f4545889c804
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
data/CODEOWNERS
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
* @puppetlabs/abide-team
|
data/abide_dev_utils.gemspec
CHANGED
@@ -7,14 +7,14 @@ require "abide_dev_utils/version"
|
|
7
7
|
Gem::Specification.new do |spec|
|
8
8
|
spec.name = "abide_dev_utils"
|
9
9
|
spec.version = AbideDevUtils::VERSION
|
10
|
-
spec.authors = ["
|
11
|
-
spec.email = ["
|
10
|
+
spec.authors = ["abide-team"]
|
11
|
+
spec.email = ["abide-team@puppet.com"]
|
12
12
|
|
13
|
-
spec.summary = "Helper utilities for developing
|
14
|
-
spec.description = "Provides a CLI with helpful utilities for developing
|
15
|
-
spec.homepage = "https://github.com/
|
13
|
+
spec.summary = "Helper utilities for developing compliance Puppet code"
|
14
|
+
spec.description = "Provides a CLI with helpful utilities for developing compliance Puppet code"
|
15
|
+
spec.homepage = "https://github.com/puppetlabs/abide_dev_utils"
|
16
16
|
spec.license = "MIT"
|
17
|
-
spec.required_ruby_version = Gem::Requirement.new(">= 2.
|
17
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.7.0")
|
18
18
|
|
19
19
|
# spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
|
20
20
|
|
@@ -34,10 +34,12 @@ Gem::Specification.new do |spec|
|
|
34
34
|
# Prod dependencies
|
35
35
|
spec.add_dependency 'nokogiri', '~> 1.11'
|
36
36
|
spec.add_dependency 'cmdparse', '~> 3.0'
|
37
|
-
spec.add_dependency 'puppet', '>= 6.
|
37
|
+
spec.add_dependency 'puppet', '>= 6.23'
|
38
38
|
spec.add_dependency 'jira-ruby', '~> 2.1'
|
39
39
|
spec.add_dependency 'ruby-progressbar', '~> 1.11'
|
40
40
|
spec.add_dependency 'selenium-webdriver', '~> 4.0.0.beta4'
|
41
|
+
spec.add_dependency 'google-cloud-storage', '~> 1.34'
|
42
|
+
spec.add_dependency 'hashdiff', '~> 1.0'
|
41
43
|
|
42
44
|
# Dev dependencies
|
43
45
|
spec.add_development_dependency 'bundler'
|
data/itests.rb
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'json'
|
5
|
+
require 'yaml'
|
6
|
+
require 'abide_dev_utils/comply'
|
7
|
+
require 'abide_dev_utils/ppt/api'
|
8
|
+
|
9
|
+
OS_BENCHMARK_MAP = {
|
10
|
+
'centos-7' => 'CIS_CentOS_Linux_7_Benchmark_v3.1.1-xccdf.xml',
|
11
|
+
'centos-8' => 'CIS_CentOS_Linux_8_Benchmark_v1.0.1-xccdf.xml',
|
12
|
+
'rhel-7' => 'CIS_Red_Hat_Enterprise_Linux_7_Benchmark_v3.1.1-xccdf.xml',
|
13
|
+
'rhel-8' => 'CIS_Red_Hat_Enterprise_Linux_8_Benchmark_v1.0.1-xccdf.xml',
|
14
|
+
'serv-2016' => 'CIS_Microsoft_Windows_Server_2016_RTM_(Release_1607)_Benchmark_v1.3.0-xccdf.xml',
|
15
|
+
'serv-2019' => 'CIS_Microsoft_Windows_Server_2019_Benchmark_v1.2.1-xccdf.xml'
|
16
|
+
}
|
17
|
+
EL_PROFILE_1_SERVER = 'xccdf_org.cisecurity.benchmarks_profile_Level_1_-_Server'
|
18
|
+
WIN_PROFILE_1_MS = 'xccdf_org.cisecurity.benchmarks_profile_Level_1_-_Member_Server'
|
19
|
+
NIX_SCAN_HASH = {
|
20
|
+
'nix-centos-7.c.team-sse.internal' => {
|
21
|
+
'benchmark' => OS_BENCHMARK_MAP['centos-7'],
|
22
|
+
'profile' => EL_PROFILE_1_SERVER
|
23
|
+
},
|
24
|
+
'nix-centos-8.c.team-sse.internal' => {
|
25
|
+
'benchmark' => OS_BENCHMARK_MAP['centos-8'],
|
26
|
+
'profile' => EL_PROFILE_1_SERVER
|
27
|
+
},
|
28
|
+
'nix-rhel-7.c.team-sse.internal' => {
|
29
|
+
'benchmark' => OS_BENCHMARK_MAP['rhel-7'],
|
30
|
+
'profile' => EL_PROFILE_1_SERVER
|
31
|
+
},
|
32
|
+
'nix-rhel-8.c.team-sse.internal' => {
|
33
|
+
'benchmark' => OS_BENCHMARK_MAP['rhel-8'],
|
34
|
+
'profile' => EL_PROFILE_1_SERVER
|
35
|
+
}
|
36
|
+
}.freeze
|
37
|
+
WIN_SCAN_HASH = {
|
38
|
+
'win-server-2016.c.team-sse.internal' => {
|
39
|
+
'benchmark' => OS_BENCHMARK_MAP['serv-2016'],
|
40
|
+
'profile' => EL_PROFILE_1_SERVER
|
41
|
+
},
|
42
|
+
'win-serv-2019.c.team-sse.internal' => {
|
43
|
+
'benchmark' => OS_BENCHMARK_MAP['serv-2019'],
|
44
|
+
'profile' => WIN_PROFILE_1_MS
|
45
|
+
}
|
46
|
+
}.freeze
|
47
|
+
|
48
|
+
scan_hash = ENV['ABIDE_OS'] == 'nix' ? NIX_SCAN_HASH : WIN_SCAN_HASH
|
49
|
+
node_group_name = ENV['ABIDE_OS'] == 'nix' ? 'CEM Linux Nodes' : 'CEM Windows Nodes'
|
50
|
+
|
51
|
+
puts 'Creating client...'
|
52
|
+
client = AbideDevUtils::Ppt::ApiClient.new(ENV['PUPPET_HOST'], auth_token: ENV['PE_ACCESS_TOKEN'])
|
53
|
+
puts 'Starting code deploy...'
|
54
|
+
code_manager_deploy = client.post_codemanager_deploys('environments' => ['production'], 'wait' => true)
|
55
|
+
raise 'Code manager deployment failed!' unless code_manager_deploy['status'] == 'complete'
|
56
|
+
|
57
|
+
puts 'Code deploy successful...'
|
58
|
+
puts 'Gathering node group ID...'
|
59
|
+
node_groups = client.get_classifier1_groups
|
60
|
+
node_group_id = nil
|
61
|
+
node_groups.each { |x| node_group_id = x['id'] if x['name'] == node_group_name }
|
62
|
+
raise 'Failed to find requested node group!' if node_group_id.nil?
|
63
|
+
|
64
|
+
puts 'Running Puppet on nodes...'
|
65
|
+
puppet_run = client.post_orchestrator_command_deploy('environment' => 'production', 'scope' => { 'node_group' => node_group_id })
|
66
|
+
puts "Started job #{puppet_run['job']['name']}..."
|
67
|
+
timeout = 0
|
68
|
+
run_complete = false
|
69
|
+
until run_complete || timeout >= 30
|
70
|
+
puts "Waiting on job #{puppet_run['job']['name']} to complete..."
|
71
|
+
status = client.get_orchestrator_jobs(puppet_run['job']['name'])
|
72
|
+
case status['state']
|
73
|
+
when 'failed'
|
74
|
+
raise "Job #{puppet_run['job']['name']} finished with failures!"
|
75
|
+
when 'finished'
|
76
|
+
run_complete = true
|
77
|
+
break
|
78
|
+
else
|
79
|
+
timeout += 1
|
80
|
+
sleep(10)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
raise 'Job timed out waiting for completion' unless run_complete
|
84
|
+
|
85
|
+
puts 'Starting node scans...'
|
86
|
+
scan_job = client.post_orchestrator_command_task(
|
87
|
+
'environment' => 'production',
|
88
|
+
'task' => 'comply::ciscat_scan',
|
89
|
+
'params' => {
|
90
|
+
'comply_port' => '443',
|
91
|
+
'comply_server' => ENV['COMPLY_FQDN'],
|
92
|
+
'ssl_verify_mode' => 'none',
|
93
|
+
'scan_type' => 'desired',
|
94
|
+
'scan_hash' => JSON.generate(scan_hash)
|
95
|
+
},
|
96
|
+
'scope' => {
|
97
|
+
'node_group' => node_group_id
|
98
|
+
}
|
99
|
+
)
|
100
|
+
puts "Started scan #{scan_job['job']['name']}..."
|
101
|
+
timeout = 0
|
102
|
+
scan_complete = false
|
103
|
+
until scan_complete || timeout >= 30
|
104
|
+
puts "Waiting on scan #{scan_job['job']['name']} to complete..."
|
105
|
+
status = client.get_orchestrator_jobs(scan_job['job']['name'])
|
106
|
+
case status['state']
|
107
|
+
when 'failed'
|
108
|
+
raise "Task #{scan_job['job']['name']} finished with failures!"
|
109
|
+
when 'finished'
|
110
|
+
scan_complete = true
|
111
|
+
break
|
112
|
+
else
|
113
|
+
timeout += 1
|
114
|
+
sleep(10)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
raise 'Job timed out waiting for completion' unless scan_complete
|
118
|
+
|
119
|
+
puts 'Collecting scan report from Comply...'
|
120
|
+
onlylist = scan_hash.keys
|
121
|
+
scan_report = AbideDevUtils::Comply.build_report("https://#{ENV['COMPLY_FQDN']}", ENV['COMPLY_PASSWORD'], nil, onlylist: onlylist)
|
122
|
+
puts 'Saving report to nix_report.yaml...'
|
123
|
+
File.open('nix_report.yaml', 'w') { |f| f.write(scan_report.to_yaml) }
|
124
|
+
|
125
|
+
puts 'Comparing current report to last report...'
|
126
|
+
opts = {
|
127
|
+
report_name: 'nix_report.yaml',
|
128
|
+
remote_storage: 'gcloud',
|
129
|
+
upload: true
|
130
|
+
}
|
131
|
+
result = AbideDevUtils::Comply.compare_reports(File.expand_path('./nix_report.yaml'), 'nix_report.yaml', opts)
|
132
|
+
if result
|
133
|
+
puts 'Success!'
|
134
|
+
exit(0)
|
135
|
+
else
|
136
|
+
puts 'Failure!'
|
137
|
+
exit(1)
|
138
|
+
end
|
@@ -12,6 +12,7 @@ module Abide
|
|
12
12
|
def initialize
|
13
13
|
super(CMD_NAME, CMD_SHORT, CMD_LONG, takes_commands: true)
|
14
14
|
add_command(ComplyReportCommand.new)
|
15
|
+
add_command(ComplyCompareReportCommand.new)
|
15
16
|
end
|
16
17
|
end
|
17
18
|
|
@@ -28,6 +29,10 @@ module Abide
|
|
28
29
|
LONGCMD
|
29
30
|
CMD_COMPLY_URL = 'The URL (including https://) of Puppet Comply'
|
30
31
|
CMD_COMPLY_PASSWORD = 'The password for Puppet Comply'
|
32
|
+
OPT_TIMEOUT_DESC = <<~EOTO
|
33
|
+
The number of seconds you would like requests to wait before timing out. Defaults
|
34
|
+
to 10 seconds.
|
35
|
+
EOTO
|
31
36
|
OPT_STATUS_DESC = <<~EODESC
|
32
37
|
A comma-separated list of check statuses to ONLY include in the report.
|
33
38
|
Valid statuses are: pass, fail, error, notapplicable, notchecked, unknown, informational
|
@@ -50,18 +55,21 @@ module Abide
|
|
50
55
|
options.on('-u [USERNAME]', '--username [USERNAME]', 'The username for Comply (defaults to comply)') do |u|
|
51
56
|
@data[:username] = u
|
52
57
|
end
|
53
|
-
options.on('-
|
54
|
-
|
55
|
-
status_array&.map! { |i| i == 'notchecked' ? 'not checked' : i }
|
56
|
-
@data[:status] = status_array
|
58
|
+
options.on('-t [SECONDS]', '--timeout [SECONDS]', OPT_TIMEOUT_DESC) do |t|
|
59
|
+
@data[:timeout] = t
|
57
60
|
end
|
58
|
-
options.on('-
|
59
|
-
|
60
|
-
|
61
|
+
options.on('-s [X,Y,Z]', '--status [X,Y,Z]',
|
62
|
+
%w[pass fail error notapplicable notchecked unknown informational],
|
63
|
+
Array,
|
64
|
+
OPT_STATUS_DESC) do |s|
|
65
|
+
s&.map! { |i| i == 'notchecked' ? 'not checked' : i }
|
66
|
+
@data[:status] = s
|
61
67
|
end
|
62
|
-
options.on('
|
63
|
-
|
64
|
-
|
68
|
+
options.on('--only [X,Y,Z]', Array, OPT_ONLY_NODES) do |o|
|
69
|
+
@data[:onlylist] = o
|
70
|
+
end
|
71
|
+
options.on('--ignore [X,Y,Z]', Array, OPT_IGNORE_NODES) do |i|
|
72
|
+
@data[:ignorelist] = i
|
65
73
|
end
|
66
74
|
end
|
67
75
|
|
@@ -79,19 +87,29 @@ module Abide
|
|
79
87
|
conf = config_section('comply')
|
80
88
|
comply_url = conf.fetch(:url) if comply_url.nil?
|
81
89
|
comply_password = comply_password.nil? ? conf.fetch(:password, Abide::CLI::PROMPT.password) : comply_password
|
82
|
-
|
83
|
-
status = @data.fetch(:status, nil).nil? ? conf.fetch(:status, nil) : @data[:status]
|
84
|
-
ignorelist = @data.fetch(:ignore, nil).nil? ? conf.fetch(:ignore, nil) : @data[:ignore]
|
85
|
-
onlylist = @data.fetch(:only, nil).nil? ? conf.fetch(:only, nil) : @data[:only]
|
86
|
-
report = AbideDevUtils::Comply.scan_report(comply_url,
|
87
|
-
comply_password,
|
88
|
-
username: username,
|
89
|
-
status: status,
|
90
|
-
ignorelist: ignorelist,
|
91
|
-
onlylist: onlylist)
|
90
|
+
report = AbideDevUtils::Comply.build_report(comply_url, comply_password, conf, **@data)
|
92
91
|
outfile = @data.fetch(:file, nil).nil? ? conf.fetch(:report_path, 'comply_scan_report.yaml') : @data[:file]
|
93
92
|
Abide::CLI::OUTPUT.yaml(report, file: outfile)
|
94
93
|
end
|
95
94
|
end
|
95
|
+
|
96
|
+
class ComplyCompareReportCommand < AbideCommand
|
97
|
+
CMD_NAME = 'compare-report'
|
98
|
+
CMD_SHORT = 'Compare two Comply reports and get the differences.'
|
99
|
+
CMD_LONG = 'Compare two Comply reports and get the differences. Report A is compared to report B, showing what changes it would take for A to equal B.'
|
100
|
+
CMD_REPORT_A = 'The current Comply report yaml file'
|
101
|
+
CMD_REPORT_B = 'The old Comply report yaml file name or full path'
|
102
|
+
def initialize
|
103
|
+
super(CMD_NAME, CMD_SHORT, CMD_LONG, takes_commands: false)
|
104
|
+
argument_desc(REPORT_A: CMD_REPORT_A, REPORT_B: CMD_REPORT_B)
|
105
|
+
options.on('-u', '--upload-new', 'If you want to upload the new scan report') { @data[:upload] = true }
|
106
|
+
options.on('-s [STORAGE]', '--remote-storage [STORAGE]', 'Remote storage to upload the report to. (Only supports "gcloud")') { |x| @data[:remote_storage] = x }
|
107
|
+
options.on('-r [NAME]', '--name [NAME]', 'The name to upload the report as') { |x| @data[:report_name] = x }
|
108
|
+
end
|
109
|
+
|
110
|
+
def execute(report_a, report_b)
|
111
|
+
AbideDevUtils::Comply.compare_reports(report_a, report_b, @data)
|
112
|
+
end
|
113
|
+
end
|
96
114
|
end
|
97
115
|
end
|
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'abide_dev_utils/cli/abstract'
|
4
|
+
require 'abide_dev_utils/output'
|
5
|
+
require 'abide_dev_utils/ppt'
|
4
6
|
|
5
7
|
module Abide
|
6
8
|
module CLI
|
@@ -12,6 +14,10 @@ module Abide
|
|
12
14
|
super(CMD_NAME, CMD_SHORT, CMD_LONG, takes_commands: true)
|
13
15
|
add_command(PuppetCoverageCommand.new)
|
14
16
|
add_command(PuppetNewCommand.new)
|
17
|
+
add_command(PuppetRenameCommand.new)
|
18
|
+
add_command(PuppetFixClassNamesCommand.new)
|
19
|
+
add_command(PuppetAuditClassNamesCommand.new)
|
20
|
+
add_command(PuppetAddCISCommentCommand.new)
|
15
21
|
end
|
16
22
|
end
|
17
23
|
|
@@ -38,12 +44,12 @@ module Abide
|
|
38
44
|
end
|
39
45
|
|
40
46
|
def execute(class_dir, hiera_file)
|
41
|
-
require 'abide_dev_utils/ppt'
|
47
|
+
require 'abide_dev_utils/ppt/coverage'
|
42
48
|
Abide::CLI::VALIDATE.directory(class_dir)
|
43
49
|
Abide::CLI::VALIDATE.file(hiera_file)
|
44
|
-
coverage = AbideDevUtils::Ppt
|
50
|
+
coverage = AbideDevUtils::Ppt.generate_coverage_report(class_dir, hiera_file, @data[:profile])
|
45
51
|
coverage.each do |k, v|
|
46
|
-
next if
|
52
|
+
next if k.match?(/classes|benchmark/)
|
47
53
|
|
48
54
|
Abide::CLI::OUTPUT.simple("#{k} coverage: #{v[:coverage]}%")
|
49
55
|
end
|
@@ -100,14 +106,133 @@ module Abide
|
|
100
106
|
end
|
101
107
|
|
102
108
|
def execute(type, name)
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
109
|
+
AbideDevUtils::Ppt.build_new_object(type, name, @data)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
class PuppetRenameCommand < AbideCommand
|
114
|
+
CMD_NAME = 'rename'
|
115
|
+
CMD_SHORT = 'Renames a Puppet class'
|
116
|
+
CMD_LONG = 'Renames a Puppet class. It does this by renaming the file and also the class name in the file. This command can also move class files based on the new class name.'
|
117
|
+
CMD_FROM_ARG = 'The current full class name'
|
118
|
+
CMD_TO_ARG = 'The new full class name'
|
119
|
+
def initialize
|
120
|
+
super(CMD_NAME, CMD_SHORT, CMD_LONG, takes_commands: false)
|
121
|
+
argument_desc(FROM: CMD_FROM_ARG, TO: CMD_TO_ARG)
|
122
|
+
options.on(
|
123
|
+
'-d',
|
124
|
+
'--declaration-only',
|
125
|
+
'Will not rename the class file, only the class declaration in the file'
|
126
|
+
) { @data[:declaration_only] = true }
|
127
|
+
options.on(
|
128
|
+
'-t',
|
129
|
+
'--declaration-in-to-file',
|
130
|
+
'Use the path derived from the TO class name as the existing file path when renaming class declaration'
|
131
|
+
) { @data[:declaration_in_to_file] = true }
|
132
|
+
options.on(
|
133
|
+
'-f',
|
134
|
+
'--force',
|
135
|
+
'Forces file move operations'
|
136
|
+
) { @data[:force] = true }
|
137
|
+
options.on(
|
138
|
+
'-v',
|
139
|
+
'--verbose',
|
140
|
+
'Sets verbose mode on file operations'
|
141
|
+
) { @data[:verbose] = true }
|
142
|
+
end
|
143
|
+
|
144
|
+
def execute(from, to)
|
145
|
+
AbideDevUtils::Ppt.rename_puppet_class(from, to, **@data)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
class PuppetFixClassNamesCommand < AbideCommand
|
150
|
+
CMD_NAME = 'fix-class-names'
|
151
|
+
CMD_SHORT = 'Fixes Puppet class names that are mismatched'
|
152
|
+
CMD_LONG = 'Fixes Puppet class names that are mismatched'
|
153
|
+
CMD_MODE_ARG = '"file" or "class". If "file", the file names will be changed to match their class declarations. If "class", the class declarations will be changed to match the file names.'
|
154
|
+
CMD_DIR_ARG = 'The directory containing the Puppet class files'
|
155
|
+
def initialize
|
156
|
+
super(CMD_NAME, CMD_SHORT, CMD_LONG, takes_commands: false)
|
157
|
+
argument_desc(MODE: CMD_MODE_ARG, DIR: CMD_DIR_ARG)
|
158
|
+
options.on(
|
159
|
+
'-f',
|
160
|
+
'--force',
|
161
|
+
'Forces file move operations'
|
162
|
+
) { @data[:force] = true }
|
163
|
+
options.on(
|
164
|
+
'-v',
|
165
|
+
'--verbose',
|
166
|
+
'Sets verbose mode on file operations'
|
167
|
+
) { @data[:verbose] = true }
|
168
|
+
end
|
169
|
+
|
170
|
+
def execute(mode, dir)
|
171
|
+
case mode
|
172
|
+
when /^f.*/
|
173
|
+
AbideDevUtils::Ppt.fix_class_names_file_rename(dir, **@data)
|
174
|
+
when /^c.*/
|
175
|
+
AbideDevUtils::Ppt.fix_class_names_class_rename(dir, **@data)
|
176
|
+
else
|
177
|
+
raise ::ArgumentError, "Invalid mode. Mode:#{mode}"
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
class PuppetAuditClassNamesCommand < AbideCommand
|
183
|
+
CMD_NAME = 'audit-class-names'
|
184
|
+
CMD_SHORT = 'Finds Puppet classes in a directory that have names that do not match their path'
|
185
|
+
CMD_LONG = 'Finds Puppet classes in a directory that have names that do not match their path. This is helpful because class names that do not match their path structure break Puppet autoloading.'
|
186
|
+
CMD_DIR_ARG = 'The directory containing the Puppet class files'
|
187
|
+
def initialize
|
188
|
+
super(CMD_NAME, CMD_SHORT, CMD_LONG, takes_commands: false)
|
189
|
+
argument_desc(DIR: CMD_DIR_ARG)
|
190
|
+
options.on('-o [FILE]', '--out-file [FILE]', 'Save results to a file') { |f| @data[:file] = f }
|
191
|
+
options.on('-q', '--quiet', 'Do not print results to console') { @data[:quiet] = true }
|
192
|
+
end
|
193
|
+
|
194
|
+
def execute(dir)
|
195
|
+
if @data.fetch(:quiet, false) && !@data.key?(:file)
|
196
|
+
AbideDevUtils::Output.simple('ERROR: Specifying --quiet without --out-file is useless.', stream: $stderr)
|
197
|
+
exit 1
|
198
|
+
end
|
199
|
+
|
200
|
+
AbideDevUtils::Ppt.audit_class_names(dir, **@data)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
class PuppetAddCISCommentCommand < AbideCommand
|
205
|
+
CMD_NAME = 'add-cis-comment'
|
206
|
+
CMD_SHORT = 'Adds the CIS recommendation name to the top of a .pp file'
|
207
|
+
CMD_LONG = 'Adds the CIS recommendation name to the top of a .pp file. Finds CIS recommendation by pattern-matching the class name against XCCDF recommendations.'
|
208
|
+
CMD_PATH_ARG = 'Path to a .pp file or to a directory containing .pp files'
|
209
|
+
CMD_XCCDF_ARG = 'Path to XCCDF file to source recommendation names from'
|
210
|
+
def initialize
|
211
|
+
super(CMD_NAME, CMD_SHORT, CMD_LONG, takes_commands: false)
|
212
|
+
argument_desc(PATH: CMD_PATH_ARG, XCCDF: CMD_XCCDF_ARG)
|
213
|
+
options.on('-N', '--number-format', 'Matches based on number-formatted control class names') { @data[:number_format] = true }
|
214
|
+
end
|
215
|
+
|
216
|
+
def execute(path, xccdf)
|
217
|
+
AbideDevUtils::Ppt.add_cis_comment(path, xccdf, number_format: @data.fetch(:number_format, false))
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
class PuppetScoreModuleCommand < AbideCommand
|
222
|
+
CMD_NAME = 'score'
|
223
|
+
CMD_SHORT = 'Scores a Puppet module just like Puppet Forge'
|
224
|
+
CMD_LONG = 'Scores a Puppet module just like Puppet Forge. This is a useful quality-check before publishing a module.'
|
225
|
+
def initialize
|
226
|
+
super(CMD_NAME, CMD_SHORT, CMD_LONG, takes_commands: false)
|
227
|
+
options.on('-o [PATH]', '--outfile [PATH]', 'Save results to a file') { |x| @data[:outfile] = x }
|
228
|
+
options.on('-q', '--quiet', FalseClass, 'Do not print results to console') { |x| @data[:quiet] = x }
|
229
|
+
options.on('-c', '--checks', Array, 'Comma-separated list of individual checks to run. Defaults to running all checks.') { |x| @data[:check] = x }
|
230
|
+
options.on('-m [PATH]', '--module [PATH]', 'Path to a Puppet module to score. Defaults to using the current directory.') { |x| @data[:module] = x }
|
231
|
+
end
|
232
|
+
|
233
|
+
def execute
|
234
|
+
module_path = @data.fetch(:module, nil)
|
235
|
+
AbideDevUtils::Ppt.score_module(module_path, **@data)
|
111
236
|
end
|
112
237
|
end
|
113
238
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'abide_dev_utils/cli/abstract'
|
3
4
|
require 'abide_dev_utils/xccdf'
|
4
5
|
|
5
6
|
module Abide
|
@@ -14,11 +15,48 @@ module Abide
|
|
14
15
|
long_desc(CMD_LONG)
|
15
16
|
add_command(CmdParse::HelpCommand.new, default: true)
|
16
17
|
add_command(XccdfToHieraCommand.new)
|
18
|
+
add_command(XccdfDiffCommand.new)
|
19
|
+
add_command(XccdfGenMapCommand.new)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class XccdfGenMapCommand < CmdParse::Command
|
24
|
+
CMD_NAME = 'gen-map'
|
25
|
+
CMD_SHORT = 'Generates mappings from XCCDF files'
|
26
|
+
CMD_LONG = 'Generates mappings for CEM modules from 1 or more XCCDF files as YAML'
|
27
|
+
def initialize
|
28
|
+
super(CMD_NAME, takes_commands: false)
|
29
|
+
short_desc(CMD_SHORT)
|
30
|
+
long_desc(CMD_LONG)
|
31
|
+
options.on('-b [TYPE]', '--benchmark-type [TYPE]', 'XCCDF Benchmark type CIS by default') { |b| @data[:type] = b }
|
32
|
+
options.on('-d [DIR]', '--files-output-directory [DIR]', 'Directory to save files data/mappings by default') { |d| @data[:dir] = d }
|
33
|
+
options.on('-q', '--quiet', 'Show no output in the terminal') { @data[:quiet] = true }
|
34
|
+
options.on('-p [PREFIX]', '--parent-key-prefix [PREFIX]', 'A prefix to append to the parent key') do |p|
|
35
|
+
@data[:parent_key_prefix] = p
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def execute(xccdf_file)
|
40
|
+
if @data[:quiet] && !@data[:dir]
|
41
|
+
AbideDevUtils::Output.simple("I don\'t know how to quietly output to the console\n¯\\_(ツ)_/¯")
|
42
|
+
exit 1
|
43
|
+
end
|
44
|
+
@data[:console] = true if @data[:dir].nil?
|
45
|
+
@data[:type] = 'cis' if @data[:type].nil?
|
46
|
+
@data[:parent_key_prefix] = '' if @data[:parent_key_prefix].nil?
|
47
|
+
hfile = AbideDevUtils::XCCDF.gen_map(xccdf_file, **@data)
|
48
|
+
mapping_dir = File.dirname(hfile.keys[0]) unless @data[:dir].nil?
|
49
|
+
AbideDevUtils::Output.simple("Creating directory #{mapping_dir}") unless @data[:quiet] || @data[:console] || @data[:dir].nil?
|
50
|
+
FileUtils.mkdir_p(mapping_dir) unless @data[:console] || @data[:dir].nil?
|
51
|
+
hfile.each do |key, val|
|
52
|
+
file_path = @data[:dir].nil? ? nil : key
|
53
|
+
AbideDevUtils::Output.yaml(val, console: @data[:console], file: file_path)
|
54
|
+
end
|
17
55
|
end
|
18
56
|
end
|
19
57
|
|
20
58
|
class XccdfToHieraCommand < CmdParse::Command
|
21
|
-
CMD_NAME = '
|
59
|
+
CMD_NAME = 'to-hiera'
|
22
60
|
CMD_SHORT = 'Generates control coverage report'
|
23
61
|
CMD_LONG = 'Generates report of valid Puppet classes that match with Hiera controls'
|
24
62
|
def initialize
|
@@ -37,15 +75,32 @@ module Abide
|
|
37
75
|
|
38
76
|
def execute(xccdf_file)
|
39
77
|
@data[:type] = 'cis' if @data[:type].nil?
|
40
|
-
|
41
|
-
|
78
|
+
hfile = AbideDevUtils::XCCDF.to_hiera(xccdf_file, @data)
|
79
|
+
AbideDevUtils::Output.yaml(hfile, console: @data[:file].nil?, file: @data[:file])
|
42
80
|
end
|
81
|
+
end
|
43
82
|
|
44
|
-
|
83
|
+
class XccdfDiffCommand < AbideCommand
|
84
|
+
CMD_NAME = 'diff'
|
85
|
+
CMD_SHORT = 'Generates a diff report between two XCCDF files'
|
86
|
+
CMD_LONG = 'Generates a diff report between two XCCDF files'
|
87
|
+
CMD_FILE1_ARG = 'path to first XCCDF file'
|
88
|
+
CMD_FILE2_ARG = 'path to second XCCDF file'
|
89
|
+
def initialize
|
90
|
+
super(CMD_NAME, CMD_SHORT, CMD_LONG, takes_commands: false)
|
91
|
+
argument_desc(FILE1: CMD_FILE1_ARG, FILE2: CMD_FILE2_ARG)
|
92
|
+
options.on('-o [PATH]', '--out-file', 'Save the report as a yaml file') { |x| @data[:outfile] = x }
|
93
|
+
options.on('-p [PROFILE]', '--profile', 'Only diff and specific profile in the benchmarks') do |x|
|
94
|
+
@data[:profile] = x
|
95
|
+
end
|
96
|
+
options.on('-q', '--quiet', 'Show no output in the terminal') { @data[:quiet] = false }
|
97
|
+
options.on('--no-diff-profiles', 'Do not diff the profiles in the XCCDF files') { @data[:diff_profiles] = false }
|
98
|
+
options.on('--no-diff-controls', 'Do not diff the controls in the XCCDF files') { @data[:diff_controls] = false }
|
99
|
+
end
|
45
100
|
|
46
|
-
def
|
47
|
-
|
48
|
-
|
101
|
+
def execute(file1, file2)
|
102
|
+
diffreport = AbideDevUtils::XCCDF.diff(file1, file2, @data)
|
103
|
+
AbideDevUtils::Output.yaml(diffreport, console: @data.fetch(:quiet, true), file: @data.fetch(:outfile, nil))
|
49
104
|
end
|
50
105
|
end
|
51
106
|
end
|