baseline-rb 0.2.0 → 0.3.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 +4 -4
- data/CHANGELOG.md +29 -0
- data/README-zh.md +50 -4
- data/README.md +59 -4
- data/docs/architecture-zh.md +8 -8
- data/lib/baseline/acl.rb +6 -6
- data/lib/baseline/audit.rb +34 -5
- data/lib/baseline/cisco/parser.rb +163 -89
- data/lib/baseline/comware/parser.rb +101 -57
- data/lib/baseline/evaluator.rb +31 -13
- data/lib/baseline/{configuration.rb → evidence.rb} +6 -6
- data/lib/baseline/{facts.rb → fact_builder.rb} +78 -53
- data/lib/baseline/h3c/parser.rb +7 -5
- data/lib/baseline/hillstone/document.rb +2 -2
- data/lib/baseline/hillstone/parser.rb +126 -73
- data/lib/baseline/huawei/parser.rb +15 -13
- data/lib/baseline/{quoted_document.rb → multiline_document.rb} +1 -1
- data/lib/baseline/palo_alto/command_index.rb +48 -0
- data/lib/baseline/palo_alto/document.rb +3 -3
- data/lib/baseline/palo_alto/parser.rb +116 -102
- data/lib/baseline/parser.rb +139 -34
- data/lib/baseline/policy.rb +2 -2
- data/lib/baseline/report.rb +2 -1
- data/lib/baseline/rule.rb +1 -1
- data/lib/baseline/version.rb +1 -1
- metadata +9 -8
data/lib/baseline/parser.rb
CHANGED
|
@@ -3,12 +3,24 @@
|
|
|
3
3
|
require "ctx_parser/configuration"
|
|
4
4
|
require "ctx_parser/input"
|
|
5
5
|
require_relative "document"
|
|
6
|
-
require_relative "
|
|
6
|
+
require_relative "fact_builder"
|
|
7
7
|
require_relative "credential"
|
|
8
8
|
|
|
9
9
|
module Baseline
|
|
10
|
-
# 共享输入与提取边界;CtxParser 跟踪来源,
|
|
10
|
+
# 共享输入与提取边界;CtxParser 跟踪来源,FactBuilder 归纳领域证据。
|
|
11
11
|
class Parser < CtxParser::Configuration
|
|
12
|
+
# 只有明确声明的事实才属于适配器契约;新增规则不能默认获得实现能力。
|
|
13
|
+
SUPPORTED_FACTS = %w[
|
|
14
|
+
snmp.enabled snmp.versions snmp.write_enabled snmp.v3_auth_priv snmp.acl.bound snmp.acl.restricted snmp.acl.sources
|
|
15
|
+
ntp.servers ntp.authentication ntp.source syslog.servers syslog.source
|
|
16
|
+
ssh.enabled ssh.versions ssh.vty_ssh_only ssh.vty_aaa management.acl.bound management.acl.restricted management.acl.sources
|
|
17
|
+
services credentials.storage_safe credentials.records tacacs.servers tacacs.login tacacs.authorization tacacs.accounting tacacs.keys_present
|
|
18
|
+
].freeze
|
|
19
|
+
|
|
20
|
+
# 声明尚未建模的事实;未知能力不能通过空值或默认值伪装成已支持。
|
|
21
|
+
UNSUPPORTED_FACTS = {}.freeze
|
|
22
|
+
PARTIAL_FACTS = {}.freeze
|
|
23
|
+
|
|
12
24
|
SERVICE_PORTS = { "ssh" => 22, "telnet" => 23, "snmp" => 161, "snmptrap" => 162 }.freeze
|
|
13
25
|
SNMP_OPTIONS = %w[auth priv authentication-mode privacy-mode acl access use-ipv4acl group local engineid engineID
|
|
14
26
|
encrypted localizedkey localizedV2key remote udp-port].freeze
|
|
@@ -28,28 +40,38 @@ module Baseline
|
|
|
28
40
|
issues << { code: :incomplete_final_line, line: cursor.lines.last.number }
|
|
29
41
|
end
|
|
30
42
|
@source_issues = issues
|
|
31
|
-
@
|
|
43
|
+
@fact_builder = FactBuilder.new(complete: complete, issues: issues)
|
|
32
44
|
if roots.empty?
|
|
33
45
|
return CtxParser::Result.new(status: :failed, issues: issues + [ { code: :empty_configuration } ])
|
|
34
46
|
end
|
|
35
47
|
prepare_configuration
|
|
36
|
-
if (hostname = matching_blocks(
|
|
37
|
-
name =
|
|
38
|
-
|
|
39
|
-
|
|
48
|
+
if (hostname = matching_blocks(hostname_family_pattern).last)
|
|
49
|
+
name = effective_configured_value(hostname_pattern, entries: [ hostname ], family: hostname_family_pattern, fact: "hostname")
|
|
50
|
+
if name
|
|
51
|
+
record_fact("hostname", name)
|
|
52
|
+
capture(hostname, name, [ "facts", "hostname" ])
|
|
53
|
+
end
|
|
40
54
|
end
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
55
|
+
parse_access_lists
|
|
56
|
+
parse_management
|
|
57
|
+
parse_services
|
|
58
|
+
parse_authentication
|
|
59
|
+
self.class::UNSUPPORTED_FACTS.each_key { |path| record_unknown_fact(path, code: :unsupported_capability) }
|
|
60
|
+
@fact_builder.build
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# 以相同顺序编排各厂商的原子领域能力,语法提取由适配器实现。
|
|
64
|
+
def parse_services
|
|
65
|
+
parse_ntp
|
|
66
|
+
parse_syslog
|
|
67
|
+
parse_snmp
|
|
46
68
|
end
|
|
47
69
|
|
|
48
70
|
# 在领域解析前准备当前配置的词元和引用索引。
|
|
49
71
|
def prepare_configuration; end
|
|
50
72
|
|
|
51
73
|
# 不使用命名 ACL 的平台由原生管理来源声明提供事实。
|
|
52
|
-
def
|
|
74
|
+
def parse_access_lists; end
|
|
53
75
|
|
|
54
76
|
# 返回本次解析的根配置块。
|
|
55
77
|
def roots = @blocks
|
|
@@ -57,6 +79,8 @@ module Baseline
|
|
|
57
79
|
def document_class = Document
|
|
58
80
|
# 匹配当前平台显式声明的主机名。
|
|
59
81
|
def hostname_pattern = /\A(?:hostname|sysname)\s+(\S+)\z/
|
|
82
|
+
# 损坏的最终身份声明也属于同族,不能继续使用旧身份选择设备策略。
|
|
83
|
+
def hostname_family_pattern = /\A(?:hostname|sysname)(?:\s|\z)/
|
|
60
84
|
# 保留原始文本,但不将可能截断的词元纳入事实构建树。
|
|
61
85
|
def exclude_incomplete_final_line
|
|
62
86
|
line = cursor.lines.last
|
|
@@ -90,6 +114,57 @@ module Baseline
|
|
|
90
114
|
end.uniq
|
|
91
115
|
end
|
|
92
116
|
|
|
117
|
+
# 地址语法在输入边界校验;损坏的服务器声明不能变成已知的空集或数量。
|
|
118
|
+
def configured_server_addresses(scope, pattern, entries, negate: nil)
|
|
119
|
+
values = {}
|
|
120
|
+
entries.each do |entry|
|
|
121
|
+
negative = negate && entry.text.start_with?("#{negate} ")
|
|
122
|
+
text = negative ? entry.text.delete_prefix("#{negate} ") : entry.text
|
|
123
|
+
match = pattern.match(text)
|
|
124
|
+
address = match && match[1]
|
|
125
|
+
unless server_address?(address)
|
|
126
|
+
record_unknown_fact(scope, [ entry ], code: :invalid_server_address)
|
|
127
|
+
next
|
|
128
|
+
end
|
|
129
|
+
identity = block_given? ? yield(text, match) : address
|
|
130
|
+
unless identity
|
|
131
|
+
record_unknown_fact(scope, [ entry ], code: :unsupported_server_options)
|
|
132
|
+
next
|
|
133
|
+
end
|
|
134
|
+
analyze(entry) { address }
|
|
135
|
+
negative ? values.delete(identity) : values[identity] = address
|
|
136
|
+
end
|
|
137
|
+
values.values.uniq
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# 服务器可选项必须完整消费;未知参数、重复项及缺失值不能作为有效配置。
|
|
141
|
+
def parse_server_options(text, flags: [], integers: {}, names: [], interfaces: [])
|
|
142
|
+
parts, values = text.split, {}
|
|
143
|
+
keywords = flags + integers.keys + names + interfaces
|
|
144
|
+
until parts.empty?
|
|
145
|
+
key = parts.shift
|
|
146
|
+
return if values.key?(key) || !keywords.include?(key)
|
|
147
|
+
if flags.include?(key)
|
|
148
|
+
values[key] = true
|
|
149
|
+
next
|
|
150
|
+
end
|
|
151
|
+
value = parts.shift
|
|
152
|
+
return if value.nil? || keywords.include?(value)
|
|
153
|
+
if integers.key?(key)
|
|
154
|
+
return unless /\A\d+\z/.match?(value) && integers.fetch(key).cover?(value.to_i)
|
|
155
|
+
value = value.to_i
|
|
156
|
+
elsif interfaces.include?(key)
|
|
157
|
+
return unless /\A[a-zA-Z][\w.:\/-]*\z/.match?(value)
|
|
158
|
+
value += parts.shift if !/\d/.match?(value) && /\A\d[\d.:\/-]*\z/.match?(parts.first.to_s)
|
|
159
|
+
return unless /\d/.match?(value)
|
|
160
|
+
end
|
|
161
|
+
values[key] = value
|
|
162
|
+
end
|
|
163
|
+
return if values.key?("minpoll") && values.key?("maxpoll") && values["minpoll"] > values["maxpoll"]
|
|
164
|
+
|
|
165
|
+
values
|
|
166
|
+
end
|
|
167
|
+
|
|
93
168
|
# 按配置顺序选取最后一条匹配声明。
|
|
94
169
|
def last_configured_value(pattern, entries = roots)
|
|
95
170
|
entry = entries.reverse.find { |candidate| !candidate.text[pattern, 1].nil? }
|
|
@@ -98,25 +173,51 @@ module Baseline
|
|
|
98
173
|
analyze(entry) { entry.text[pattern, 1] }
|
|
99
174
|
end
|
|
100
175
|
|
|
176
|
+
# 先选取最后一条同族声明,再校验语法,避免回落到已被撤销或损坏的旧值。
|
|
177
|
+
def effective_configured_value(pattern, entries: roots, family:, fact:, reset: nil)
|
|
178
|
+
entry = entries.reverse.find { |candidate| family.match?(candidate.text) }
|
|
179
|
+
return unless entry
|
|
180
|
+
return analyze(entry) { nil } if reset&.match?(entry.text)
|
|
181
|
+
match = pattern.match(entry.text)
|
|
182
|
+
return analyze(entry) { match[1] } if match
|
|
183
|
+
|
|
184
|
+
record_unknown_fact(fact, [ entry ])
|
|
185
|
+
nil
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# 保存最后生效的标量声明及对应来源,不将被覆盖的声明作为当前证据。
|
|
189
|
+
def record_configured_value(path, pattern, entries = roots)
|
|
190
|
+
entry = entries.reverse.find { |candidate| !candidate.text[pattern, 1].nil? }
|
|
191
|
+
return unless entry
|
|
192
|
+
|
|
193
|
+
record_fact(path, analyze(entry) { entry.text[pattern, 1] }, [ entry ])
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# 启用状态可能由正向或撤销声明决定,保留所有参与归约的来源。
|
|
197
|
+
def record_configured_state(path, on, off)
|
|
198
|
+
entries = roots.select { |entry| on.match?(entry.text) || off.match?(entry.text) }
|
|
199
|
+
record_fact(path, configured_state(on, off, entries), entries)
|
|
200
|
+
end
|
|
201
|
+
|
|
101
202
|
# 保存归一化事实及其来源证据。
|
|
102
|
-
def
|
|
103
|
-
return unless @
|
|
203
|
+
def record_fact(path, value, entries = [])
|
|
204
|
+
return unless @fact_builder.record_fact(path, value, lines: entries.map(&:line))
|
|
104
205
|
|
|
105
206
|
# 来源块中尚未解释的命令不能标记为解析成功。
|
|
106
207
|
cursor.capture_many(entries.select { |entry| cursor.parsed?(entry.index) }, data: value, path: [ "facts", path ])
|
|
107
208
|
end
|
|
108
209
|
|
|
109
210
|
# 记录事实不可用的原因,并保留对应的来源行。
|
|
110
|
-
def
|
|
111
|
-
@
|
|
211
|
+
def record_unknown_fact(path, entries = [], code: :unsupported_syntax)
|
|
212
|
+
@fact_builder.record_unknown_fact(path, lines: entries.map(&:line), code: code)
|
|
112
213
|
entries.each { |entry| cursor.mark(entry.index, state: :unsupported) }
|
|
113
214
|
end
|
|
114
215
|
|
|
115
216
|
# 未解析的访问声明不能抹除其他已确认的写权限。
|
|
116
|
-
def
|
|
117
|
-
@
|
|
217
|
+
def record_unknown_snmp_access(entries, code: :unsupported_syntax)
|
|
218
|
+
@fact_builder.record_unknown_snmp_permission(lines: entries.map(&:line), code: code)
|
|
118
219
|
%w[snmp.communities snmp.community_present snmp.users snmp.versions snmp.v3_auth_priv snmp.acl].each do |path|
|
|
119
|
-
|
|
220
|
+
record_unknown_fact(path, entries, code: code)
|
|
120
221
|
end
|
|
121
222
|
end
|
|
122
223
|
|
|
@@ -136,7 +237,7 @@ module Baseline
|
|
|
136
237
|
# 记录凭据属性,不保留凭据秘密值。
|
|
137
238
|
def record_credential(owner, kind, format, secret, entry)
|
|
138
239
|
value = analyze(entry) { Credential.new(owner: owner, kind: kind, format: format, secret: secret, line: entry.line) }
|
|
139
|
-
index = @
|
|
240
|
+
index = @fact_builder.record_credential(value)
|
|
140
241
|
capture(entry, value.to_h, [ "facts", "credentials.records", index ])
|
|
141
242
|
end
|
|
142
243
|
|
|
@@ -156,7 +257,7 @@ module Baseline
|
|
|
156
257
|
end
|
|
157
258
|
acl = Acl.new(name: name, line: entries.first&.line, rules: parsed.compact,
|
|
158
259
|
supported: supported && !damaged && parsed.none?(&:nil?))
|
|
159
|
-
if @
|
|
260
|
+
if @fact_builder.register_acl(acl, aliases: aliases, lines: entries.map(&:line)).any?
|
|
160
261
|
entries.each { |entry| cursor.mark(entry.index, state: :unsupported) }
|
|
161
262
|
end
|
|
162
263
|
end
|
|
@@ -172,16 +273,20 @@ module Baseline
|
|
|
172
273
|
end
|
|
173
274
|
|
|
174
275
|
# 归一化 ACL 来源地址;不推测不支持语法的含义。
|
|
175
|
-
def
|
|
276
|
+
def parse_acl_source_network(parts)
|
|
176
277
|
token = parts.shift
|
|
177
278
|
return "0.0.0.0/0" if token == "any"
|
|
178
|
-
|
|
279
|
+
if token == "host"
|
|
280
|
+
address = parts.shift
|
|
281
|
+
raise ArgumentError, "A host requires one IPv4 address" unless address&.match?(/\A\d+(?:\.\d+){3}\z/)
|
|
282
|
+
return Acl.source_network(address)
|
|
283
|
+
end
|
|
179
284
|
wildcard = parts.shift if parts.first&.match?(/\A(?:\d+(?:\.\d+){3}|0)\z/)
|
|
180
285
|
Acl.source_network(token, wildcard)
|
|
181
286
|
end
|
|
182
287
|
|
|
183
288
|
# 显式端口名称仅属于配置语法,不代表组织批准值。
|
|
184
|
-
def
|
|
289
|
+
def parse_service_port(token)
|
|
185
290
|
return SERVICE_PORTS[token] unless token&.match?(/\A\d+\z/)
|
|
186
291
|
value = token.to_i
|
|
187
292
|
value if value.between?(0, 65_535)
|
|
@@ -206,19 +311,19 @@ module Baseline
|
|
|
206
311
|
entry = entries.last
|
|
207
312
|
return analyze(entry) { default } if entry.text == "#{negate} #{command}"
|
|
208
313
|
token = entry.text[/\A#{prefix}\s+(\d+)\z/, 1]
|
|
209
|
-
port =
|
|
314
|
+
port = parse_service_port(token)
|
|
210
315
|
return analyze(entry) { port } if port && port.positive?
|
|
211
|
-
|
|
316
|
+
record_unknown_listener_port(scope, entries)
|
|
212
317
|
nil
|
|
213
318
|
end
|
|
214
319
|
|
|
215
320
|
# 监听端口无法解析时,将依赖该端口的事实保留为未知。
|
|
216
|
-
def
|
|
217
|
-
%w[sources restricted].each { |field|
|
|
321
|
+
def record_unknown_listener_port(scope, entries)
|
|
322
|
+
%w[sources restricted].each { |field| record_unknown_fact("#{scope}.#{field}", entries, code: :unsupported_service_port) }
|
|
218
323
|
end
|
|
219
324
|
|
|
220
325
|
# 区分模式未声明、格式无效及包含完整算法与秘密值的声明。
|
|
221
|
-
def
|
|
326
|
+
def parse_snmp_secret(settings, keyword, algorithms:, optional_algorithm: false, aes_size: false)
|
|
222
327
|
tokens = settings.split
|
|
223
328
|
index = tokens.index(keyword)
|
|
224
329
|
return false unless index
|
|
@@ -236,23 +341,23 @@ module Baseline
|
|
|
236
341
|
end
|
|
237
342
|
|
|
238
343
|
# 提取 SNMP 凭据属性,不持久化秘密值。
|
|
239
|
-
def
|
|
344
|
+
def record_snmp_credentials(owner, modes, entry, format: nil)
|
|
240
345
|
modes.each_value do |value|
|
|
241
346
|
next if value == false
|
|
242
347
|
stored_format, secret = value || [ "unknown", nil ]
|
|
243
348
|
record_credential(owner, "snmp_user", stored_format || format || "unknown", secret, entry)
|
|
244
349
|
end
|
|
245
|
-
|
|
350
|
+
record_unknown_fact("snmp.v3_auth_priv", [ entry ], code: :invalid_snmp_security) if modes.value?(nil)
|
|
246
351
|
modes.transform_values { |value| value ? true : value }
|
|
247
352
|
end
|
|
248
353
|
|
|
249
354
|
# 同名组可出现在不同安全级别;后续只读声明不能消除已有写权限。
|
|
250
|
-
def
|
|
355
|
+
def configured_snmp_groups(entries)
|
|
251
356
|
entries.map { |entry| yield(entry) }.group_by(&:first).transform_values do |definitions|
|
|
252
357
|
records = definitions.map(&:last)
|
|
253
358
|
acls = records.map { |record| record[:acl] }.uniq
|
|
254
359
|
{ acl: acls.one? ? acls.first : nil, acl_ambiguous: acls.size > 1,
|
|
255
|
-
write:
|
|
360
|
+
write: FactBuilder.write_permission_state(records.map { |record| record[:write] }),
|
|
256
361
|
line: records.first[:line], lines: records.map { |record| record[:line] } }
|
|
257
362
|
end
|
|
258
363
|
end
|
data/lib/baseline/policy.rb
CHANGED
|
@@ -64,12 +64,12 @@ module Baseline
|
|
|
64
64
|
|
|
65
65
|
# 解析上下文对应的策略值,并保留选择路径。
|
|
66
66
|
def resolve(rule, context)
|
|
67
|
-
resolution =
|
|
67
|
+
resolution = resolve_with_path(rule, context)
|
|
68
68
|
[ resolution.value, resolution.reason ]
|
|
69
69
|
end
|
|
70
70
|
|
|
71
71
|
# 保留相对于 expected 的命中范围、默认分支或未解析选择器路径。
|
|
72
|
-
def
|
|
72
|
+
def resolve_with_path(rule, context)
|
|
73
73
|
value = rule.expected
|
|
74
74
|
path = []
|
|
75
75
|
while selector?(value)
|
data/lib/baseline/report.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "
|
|
3
|
+
require_relative "evidence"
|
|
4
4
|
|
|
5
5
|
require "ctx_parser/immutable"
|
|
6
6
|
require "json"
|
|
@@ -12,6 +12,7 @@ module Baseline
|
|
|
12
12
|
input_incomplete: "配置不完整,需提供完整备份后重查", unsupported_platform: "尚未支持此平台的配置语法",
|
|
13
13
|
scope_mismatch: "不在策略适用范围内", scope_unknown: "缺少判断适用范围或选择批准值所需的上下文",
|
|
14
14
|
policy_missing: "未定义当前范围的批准值,请完善基线策略", fact_unavailable: "配置事实缺失或包含尚未支持的语法,请查看解析诊断",
|
|
15
|
+
unsupported_capability: "此厂商的当前适配器尚未建模该能力,不能据此声明合规",
|
|
15
16
|
fact_type_mismatch: "解析事实类型与检查要求不一致", unnecessary_service_enabled: "存在策略要求关闭的服务",
|
|
16
17
|
service_state_unknown: "配置未明确声明部分服务状态,不能推断厂商默认值", deviation: "实际配置偏离批准值",
|
|
17
18
|
unsupported_address_family: "地址格式超出当前 IPv4 检查能力", weak_password: "发现未满足强度要求的明文凭据",
|
data/lib/baseline/rule.rb
CHANGED
|
@@ -90,7 +90,7 @@ module Baseline
|
|
|
90
90
|
when :string_set, :network_set then string_set?(value)
|
|
91
91
|
end
|
|
92
92
|
raise ArgumentError, "Invalid baseline expected value for #{id}" unless valid
|
|
93
|
-
value.each { |network| Acl.
|
|
93
|
+
value.each { |network| Acl.ipv4_range(network) } if value_type == :network_set
|
|
94
94
|
end
|
|
95
95
|
|
|
96
96
|
# 展示对象身份与状态,避免泄露原始回显或凭据。
|
data/lib/baseline/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: baseline-rb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Gatework contributors
|
|
@@ -13,16 +13,16 @@ dependencies:
|
|
|
13
13
|
name: ctx_parser
|
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
|
15
15
|
requirements:
|
|
16
|
-
- - "
|
|
16
|
+
- - ">="
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 0
|
|
18
|
+
version: '0'
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
|
-
- - "
|
|
23
|
+
- - ">="
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 0
|
|
25
|
+
version: '0'
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: digest
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -113,20 +113,21 @@ files:
|
|
|
113
113
|
- lib/baseline/cisco_ios/parser.rb
|
|
114
114
|
- lib/baseline/cisco_nxos/parser.rb
|
|
115
115
|
- lib/baseline/comware/parser.rb
|
|
116
|
-
- lib/baseline/configuration.rb
|
|
117
116
|
- lib/baseline/credential.rb
|
|
118
117
|
- lib/baseline/document.rb
|
|
119
118
|
- lib/baseline/evaluator.rb
|
|
120
|
-
- lib/baseline/
|
|
119
|
+
- lib/baseline/evidence.rb
|
|
120
|
+
- lib/baseline/fact_builder.rb
|
|
121
121
|
- lib/baseline/h3c/parser.rb
|
|
122
122
|
- lib/baseline/hillstone/document.rb
|
|
123
123
|
- lib/baseline/hillstone/parser.rb
|
|
124
124
|
- lib/baseline/huawei/parser.rb
|
|
125
|
+
- lib/baseline/multiline_document.rb
|
|
126
|
+
- lib/baseline/palo_alto/command_index.rb
|
|
125
127
|
- lib/baseline/palo_alto/document.rb
|
|
126
128
|
- lib/baseline/palo_alto/parser.rb
|
|
127
129
|
- lib/baseline/parser.rb
|
|
128
130
|
- lib/baseline/policy.rb
|
|
129
|
-
- lib/baseline/quoted_document.rb
|
|
130
131
|
- lib/baseline/report.rb
|
|
131
132
|
- lib/baseline/rule.rb
|
|
132
133
|
- lib/baseline/version.rb
|