baseline-rb 0.2.0 → 0.2.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bd8f1c556c5d833d33eba1f4d54c9a8ce7dea4ee1f95eef35bf6065c135f55f8
4
- data.tar.gz: c7ed62903830c46fc8dcf1abb781cc2a5fdf3542e46b77e2b946cd5d637234ea
3
+ metadata.gz: 2386adb307215040a989dccfcba29d403bd31f28d400cbb6ba9f34215c13e7bb
4
+ data.tar.gz: 975bc0b9e0136528a8af240d21fe623068ec334c3f444746ce84dcbc25e097e6
5
5
  SHA512:
6
- metadata.gz: 1f762bc4f539ec4c8793138b300b5b218173e9334e0499cff4c18f5ffe4a89c5cc0cc6c0f51dc1bcb2482f36197b695428dfc7841559a1e392a29d9aa6629d7f
7
- data.tar.gz: 7de1e45690c5c3dd8369aee56b9af939670866ebbef859c434a6efc75617808d2658398c4c6ec570437ea0da6ff731d4ba5c33e842274f6d22f84fc27b7909f4
6
+ metadata.gz: 81a940f8b6b82df076c2c1f541ee6b5f7e95154095ca3ccffc2b70580d129388d5ec40c6c6f9a926c30f813c31d5cc39f669f2c7f487dda94718954707e5f5f9
7
+ data.tar.gz: cc66c2181c786de9341533ae65caf8214d0e8d28d40628c7b22b9161e3189174144a38cb58f8599dd2b0848cc511b3ebb31a5634e807b957ef074201eee1624e
data/CHANGELOG.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  Changes to the public contract are recorded here. Versions follow Semantic Versioning.
4
4
 
5
+ ## 0.2.1 (2026-10-17)
6
+
7
+ - Reject duplicate normalized context keys before policy selection; preserve source lines and cursor captures for scalar NTP and Syslog configuration facts.
8
+
5
9
  ## 0.2.0 (2026-10-17)
6
10
 
7
11
  ### Changed
data/README-zh.md CHANGED
@@ -73,6 +73,8 @@ puts report.to_text
73
73
 
74
74
  解析辅助方法直接描述提取意图:`matching_blocks`、`configured_values`、`last_configured_value`、`configured_state` 和 `acl_source_network`。SNMP 写权限规则统一为 `snmp.write_enabled`,禁止写入时配置 `expected: false`。自定义策略需迁移旧键 `snmp.read_only`;旧类名与规则键不保留兼容别名。
75
75
 
76
+ 上下文允许字符串或符号键,但归一化后重复的键会抛出 `ArgumentError`,避免依赖插入顺序选择设备策略。
77
+
76
78
  `complete: true` 表示调用方提供了完整快照。报告通过 `input_complete` 保留这项声明,与解析状态分别记录。不完整的有序 ACL 可能缺少更早的 deny,因此不能仅根据已见 permit 判断来源越界,此时保持 `unknown`。
77
79
 
78
80
  ## 开发与测试
data/README.md CHANGED
@@ -73,6 +73,8 @@ Vendor-specific code lives in a vendor namespace: `Baseline::Hillstone::Parser`
73
73
 
74
74
  Parser helpers describe the evidence they extract: `matching_blocks`, `configured_values`, `last_configured_value`, `configured_state`, and `acl_source_network`. SNMP write permission uses the policy key `snmp.write_enabled` (`expected: false` to prohibit writes). Update custom policies that used `snmp.read_only`; old class names and rule keys have no compatibility aliases.
75
75
 
76
+ Context accepts string or symbol keys, but rejects duplicate keys after normalization with `ArgumentError` rather than selecting a device policy by insertion order.
77
+
76
78
  `complete: true` asserts that the caller supplied the entire snapshot. Reports retain that assertion as `input_complete`, separately from parser status. A partial ordered ACL cannot prove an out-of-policy source: an unseen earlier deny may change its meaning, so the finding stays `unknown`.
77
79
 
78
80
  ## Development and tests
@@ -85,12 +85,12 @@ module Baseline
85
85
  # 采集明确启用或禁用的管理服务。
86
86
  def services
87
87
  record("ntp.servers", configured_values(/\Antp server(?: vrf \S+)?\s+(\S+)/), matching_blocks(/\Antp server/))
88
- record("ntp.authentication", configured_state(/\Antp authenticate\z/, /\Ano ntp authenticate\z/))
89
- record("ntp.source", last_configured_value(/\Antp source(?:-interface)?\s+(\S+)\z/))
88
+ record_configured_state("ntp.authentication", /\Antp authenticate\z/, /\Ano ntp authenticate\z/)
89
+ record_configured_value("ntp.source", /\Antp source(?:-interface)?\s+(\S+)\z/)
90
90
  hosts = configured_values(nxos? ? /\Alogging server\s+(\S+)/ : /\Alogging host(?: ipv6)?\s+(\S+)/)
91
91
  hosts |= configured_values(/\Alogging\s+(\d+(?:\.\d+){3})\z/) unless nxos?
92
92
  record("syslog.servers", hosts, matching_blocks(/\Alogging (?:host|server)/))
93
- record("syslog.source", last_configured_value(/\Alogging source-interface\s+(\S+)\z/))
93
+ record_configured_value("syslog.source", /\Alogging source-interface\s+(\S+)\z/)
94
94
  snmp = matching_blocks(/\Asnmp-server\b/)
95
95
  enabled = nxos? ? configured_state(/\Asnmp-server protocol enable\z/, /\Ano snmp-server protocol enable\z/) : nil
96
96
  enabled = nxos? || snmp.any? if enabled.nil? && (@facts.complete? || snmp.any?)
@@ -79,12 +79,12 @@ module Baseline
79
79
  # 采集明确启用或禁用的管理服务。
80
80
  def services
81
81
  record("ntp.servers", configured_values(/\Antp-service unicast-server\s+(\S+)/), matching_blocks(/\Antp-service unicast-server/))
82
- record("ntp.authentication", configured_state(/\Antp-service authentication enable\z/, /\Aundo ntp-service authentication enable\z/))
83
- record("ntp.source", last_configured_value(/\Antp-service source(?:-interface)?\s+(\S+)\z/))
82
+ record_configured_state("ntp.authentication", /\Antp-service authentication enable\z/, /\Aundo ntp-service authentication enable\z/)
83
+ record_configured_value("ntp.source", /\Antp-service source(?:-interface)?\s+(\S+)\z/)
84
84
  # 从日志服务器中排除 source 子命令,并保留评估所需的归纳事实。
85
85
  hosts = configured_values(/\Ainfo-center loghost\s+(\S+)/) - [ "source" ]
86
86
  record("syslog.servers", hosts, matching_blocks(/\Ainfo-center loghost/))
87
- record("syslog.source", last_configured_value(/\Ainfo-center loghost source\s+(\S+)\z/))
87
+ record_configured_value("syslog.source", /\Ainfo-center loghost source\s+(\S+)\z/)
88
88
  enabled = configured_state(/\Asnmp-agent(?:\s|\z)/, /\Aundo snmp-agent\z/)
89
89
  record("snmp.enabled", enabled.nil? && @facts.complete? ? false : enabled)
90
90
  versions = last_configured_value(/\Asnmp-agent sys-info version\s+(.+)\z/)&.split
@@ -15,6 +15,9 @@ module Baseline
15
15
  # 评估适用基线规则,分别报告合规结果与证据覆盖率。
16
16
  def call(configuration:, context: {})
17
17
  raise ArgumentError, "Invalid baseline context" unless context.is_a?(Hash)
18
+ unless context.keys.all? { |key| key.is_a?(String) || key.is_a?(Symbol) } && context.keys.map(&:to_s).uniq.size == context.size
19
+ raise ArgumentError, "Ambiguous baseline context"
20
+ end
18
21
  context = context.transform_keys(&:to_s).merge("vendor" => configuration.platform)
19
22
  unless (context.keys - Policy::SCOPES).empty? && context.values.all? { |value| value.is_a?(String) && !value.strip.empty? }
20
23
  raise ArgumentError, "Invalid baseline context"
@@ -95,8 +95,8 @@ module Baseline
95
95
  analyze(entry) { parts[2] }
96
96
  end
97
97
  record("ntp.servers", servers.uniq, entries)
98
- record("ntp.authentication", configured_state(/\Antp authentication\z/, /\Ano ntp authentication\z/))
99
- record("ntp.source", sources.first) if sources.any? && sources.first && sources.uniq.one?
98
+ record_configured_state("ntp.authentication", /\Antp authentication\z/, /\Ano ntp authentication\z/)
99
+ record("ntp.source", sources.first, entries) if sources.any? && sources.first && sources.uniq.one?
100
100
  unknown("ntp", matching_blocks(/\Ano ntp server\b/)) if matching_blocks(/\Ano ntp server\b/).any?
101
101
  end
102
102
 
@@ -124,13 +124,13 @@ module Baseline
124
124
  analyze(entry) { parts[2] }
125
125
  end
126
126
  record("syslog.servers", servers.uniq, entries)
127
- record("syslog.source", sources.first) if sources.any? && sources.first && sources.uniq.one?
127
+ record("syslog.source", sources.first, entries) if sources.any? && sources.first && sources.uniq.one?
128
128
  unknown("syslog", matching_blocks(/\Ano logging syslog\b/)) if matching_blocks(/\Ano logging syslog\b/).any?
129
129
  end
130
130
 
131
131
  # 轮询主机权限独立于出站 Trap 接收端。
132
132
  def snmp
133
- record("snmp.enabled", configured_state(/\Asnmp-server manager\z/, /\Ano snmp-server manager\z/))
133
+ record_configured_state("snmp.enabled", /\Asnmp-server manager\z/, /\Ano snmp-server manager\z/)
134
134
  entries = matching_blocks(/\Asnmp-server host\b/)
135
135
  versions, sources, communities = [], [], []
136
136
  entries.each do |entry|
@@ -144,9 +144,9 @@ module Baseline
144
144
  end
145
145
  record("ntp.servers", servers.map { |server| server[:address] }.uniq, entries.map { |row| row[:entry] })
146
146
  if servers.any? && servers.none? { |server| server[:authenticated].nil? }
147
- record("ntp.authentication", servers.all? { |server| server[:authenticated] })
147
+ record("ntp.authentication", servers.all? { |server| server[:authenticated] }, entries.map { |row| row[:entry] })
148
148
  end
149
- record("ntp.source", scalar(%w[deviceconfig system route service ntp source interface]))
149
+ record_scalar("ntp.source", %w[deviceconfig system route service ntp source interface])
150
150
  end
151
151
 
152
152
  # 仅完整的认证声明才能证明所有服务器均满足认证要求。
@@ -176,7 +176,7 @@ module Baseline
176
176
  value
177
177
  end
178
178
  record("syslog.servers", servers.uniq, entries.map { |row| row[:entry] })
179
- record("syslog.source", scalar(%w[deviceconfig system route service syslog source interface]))
179
+ record_scalar("syslog.source", %w[deviceconfig system route service syslog source interface])
180
180
  end
181
181
 
182
182
  # 轮询用户、团体字与出站 SNMP Trap 配置相互独立。
@@ -321,6 +321,12 @@ module Baseline
321
321
  nil
322
322
  end
323
323
 
324
+ # 标量语法与事实来源共享同一条最终声明。
325
+ def record_scalar(path, prefix)
326
+ row = rows(*prefix).last
327
+ record(path, scalar(prefix), row ? [ row[:entry] ] : [])
328
+ end
329
+
324
330
  # 严格校验词元数量,避免尾随内容使错误配置被误判为已知事实。
325
331
  def scalar(prefix)
326
332
  row = rows(*prefix).last
@@ -98,6 +98,20 @@ module Baseline
98
98
  analyze(entry) { entry.text[pattern, 1] }
99
99
  end
100
100
 
101
+ # 保存最后生效的标量声明及对应来源,不将被覆盖的声明作为当前证据。
102
+ def record_configured_value(path, pattern, entries = roots)
103
+ entry = entries.reverse.find { |candidate| !candidate.text[pattern, 1].nil? }
104
+ return unless entry
105
+
106
+ record(path, analyze(entry) { entry.text[pattern, 1] }, [ entry ])
107
+ end
108
+
109
+ # 启用状态可能由正向或撤销声明决定,保留所有参与归约的来源。
110
+ def record_configured_state(path, on, off)
111
+ entries = roots.select { |entry| on.match?(entry.text) || off.match?(entry.text) }
112
+ record(path, configured_state(on, off, entries), entries)
113
+ end
114
+
101
115
  # 保存归一化事实及其来源证据。
102
116
  def record(path, value, entries = [])
103
117
  return unless @facts.record(path, value, lines: entries.map(&:line))
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Baseline
4
4
  # 公开包版本,供 RubyGems 元数据和运行时调用方共用。
5
- VERSION = "0.2.0"
5
+ VERSION = "0.2.1"
6
6
  end
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.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gatework contributors