baseline-rb 0.8.0 → 0.9.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 +8 -0
- data/README-zh.md +2 -0
- data/README.md +2 -0
- data/docs/baseline-metrics.md +24 -0
- data/examples/remediation_nxos_aaa.rb +24 -0
- data/lib/baseline/cisco/parser.rb +3 -3
- data/lib/baseline/cisco_nxos/authentication.rb +155 -0
- data/lib/baseline/cisco_nxos/command_renderer.rb +25 -0
- data/lib/baseline/cisco_nxos/parser.rb +2 -0
- data/lib/baseline/configuration_operation.rb +2 -0
- data/lib/baseline/nxos_aaa_methods.rb +22 -0
- data/lib/baseline/nxos_aaa_remediation.rb +104 -0
- data/lib/baseline/nxos_aaa_schema.rb +52 -0
- data/lib/baseline/remediation.rb +6 -1
- data/lib/baseline/remediation_planner.rb +9 -4
- data/lib/baseline/rule.rb +11 -4
- data/lib/baseline/version.rb +1 -1
- metadata +6 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b5143fc59a995670ab9a7f35a7fb22353de3a471c1646c0790bfcdabe2405246
|
|
4
|
+
data.tar.gz: 1ce0e2e703d80a3cba8fff9cb84c441cfa41ef7dad5e47c87303dc4c7eba6849
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 56fc448769e9cdd00bd6ad3acfed63e5e00406165706a38c4c33049e041de68020b28994aa6f93afe872dae164d947d3999e5b9e62fce550f07b327719faae94
|
|
7
|
+
data.tar.gz: b7ba5f71cb034fec86a76d8081591ee753e503e6c992c5afb3fb818880cb98ed63918fabef5ca744ff8b52734510d0fda4cdf8238a21c802994a5b804decc14e
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
Changes to the public contract are recorded here. Versions follow Semantic Versioning.
|
|
4
4
|
|
|
5
|
+
## 0.9.0 (2026-10-20)
|
|
6
|
+
|
|
7
|
+
- Generate explicitly approved NX-OS default-login, command/config-command authorization and accounting corrections with native ordered groups and separate local error fallback; preserve console, shared groups and existing materials.
|
|
8
|
+
- Require resolved native role evidence, peer/consumer/recovery approval and existing endpoint keys before activating remote methods. Bind rendering to complete operation fingerprints and withhold coupled authorization changes if rendering fails.
|
|
9
|
+
|
|
10
|
+
- Audit NX-OS management authentication with native global method lists, independent local error fallback and separate command/config-command authorization; retain sanitized typed evidence instead of applying IOS line-login syntax.
|
|
11
|
+
- Respect native SSH defaults and explicit feature state, reject malformed replacements, and prevent disabled TACACS from certifying remote AAA usage. Telnet defaults remain unknown without product-specific or explicit evidence.
|
|
12
|
+
|
|
5
13
|
## 0.8.0 (2026-10-20)
|
|
6
14
|
|
|
7
15
|
- Generate explicitly approved Huawei S-series VTY AAA-mode corrections with native UI/profile proofs while preserving shared schemes, templates, methods and ACLs.
|
data/README-zh.md
CHANGED
|
@@ -209,6 +209,8 @@ MIT,见 [LICENSE](LICENSE)。
|
|
|
209
209
|
|
|
210
210
|
Comware/VRP TACACS 密钥按替换、撤销及方案重新进入后的最终状态归纳。`check.details[:key_slots]` 分别列出 H3C 认证/授权/计费及 Huawei 共享材料,关联已知端点并提示对端、AAA 引用和恢复前提。损坏状态需补证,检查仍受阻,不生成秘密命令。当前模型描述方案密钥,不代表已支持端点私有密钥继承。参见[TACACS 材料示例](examples/remediation_tacacs.rb)。
|
|
211
211
|
|
|
212
|
+
NX-OS 管理 AAA 按原生全局登录方法、独立本地错误后备及命令/配置命令授权审计,非秘密 `aaa.nxos` 证据保留这些区别。禁用 TACACS 或损坏的最后声明不能证明远程使用;完整输入采用已确认的 SSH 默认,未明确的产品 Telnet 默认继续未知。明确的 `nxos` 批准可引用现有已核验组与密钥修正默认登录、两类授权及计费;登录本地错误后备需单独批准,控制台及共享对象保持原样。参见[离线示例](examples/remediation_nxos_aaa.rb)。参见[指标契约](docs/baseline-metrics.md)。
|
|
213
|
+
|
|
212
214
|
Comware/VRP `aaa.bindings` 描述选中管理域的有序方法、TACACS 职责/组/端点引用、来源行及登录 VTY;本地后备与 `none` 分开记录,均不代表恢复访问已经可用。TACACS 使用检查复用这些证据,拒绝旧值和损坏引用。人工方案列出具体链路证据与批准前提,不自动挑选候选组;明确批准的 Comware 7 管理域 login 方法链可在完整核验后生成修正命令。`aaa.bindings` 保留选中域视角,`aaa.domain_bindings` 扩展覆盖已建模显式域的共享 TACACS 引用;实际用户路径、其他服务提供者及其他厂商绑定仍待分别补齐。参见[AAA 审核示例](examples/remediation_authentication.rb)。
|
|
213
215
|
|
|
214
216
|
`tacacs.endpoints` 可声明 `bindings: [{kind: "aaa.domain", domain: "ADMIN", role: "authentication", position: "primary", selected: true}]`,在支持的 Comware/VRP 范围内核验完整域引用集合,包含未选中域的共享使用。position 是方法链优先级,与服务器主备不同。省略 bindings 只查定义;`[]` 需已知未绑定证据。局部/未建模引用保持未知,能力项区分对象与绑定支持。当前方案展示共享引用要求,不生成绑定切换命令。参见[绑定策略示例](examples/resource_bindings.rb)。
|
data/README.md
CHANGED
|
@@ -199,6 +199,8 @@ Credential review plans identify unsafe-storage and observable weak-password obj
|
|
|
199
199
|
|
|
200
200
|
Comware/VRP TACACS key requirements retain the final key state across replacement, withdrawal, and reopened schemes. `check.details[:key_slots]` distinguishes missing H3C authentication/authorization/accounting material from Huawei shared material, associates known endpoints, and lists peer, AAA-reference, and recovery prerequisites. Malformed key state requires evidence; plans stay blocked and contain no secret commands. This models scheme-level keys, not endpoint-private-key inheritance. See the [TACACS material example](examples/remediation_tacacs.rb).
|
|
201
201
|
|
|
202
|
+
NX-OS management AAA auditing uses native global login methods, independent local error fallback and both command/config-command authorization. Sanitized `aaa.nxos` evidence preserves these distinctions; disabled TACACS and malformed final declarations cannot certify remote use. SSH defaults are modelled for complete input, while unknown product-specific Telnet defaults remain unknown. Explicit `nxos` approvals can correct default login, both authorization actions and accounting using existing verified groups/keys; native login fallback is approved separately. Console and shared objects stay unchanged. See the [offline example](examples/remediation_nxos_aaa.rb). See the [metric contract](docs/baseline-metrics.md).
|
|
203
|
+
|
|
202
204
|
Comware/VRP `aaa.bindings` describes the selected management domain's ordered methods, TACACS role/group/endpoint references, source lines, and login VTY evidence. Local fallback and `none` are separate declarations; neither proves working recovery access. TACACS usage checks share this evidence and reject stale/malformed references. Plans list concrete chain evidence and approval requirements without selecting a candidate group. Explicitly approved Comware 7 login-chain corrections can bind an existing selected domain to a verified role group with local fallback, or to an approved local alternative; endpoint changes remain separate prerequisites. Comware 7 VTY account-mode corrections also accept explicit complete range approval, preserve the existing approved method chain and effective protocols, and project requested SSH-only and TACACS-login constraints together; inherited protocol rebinding needs separate temporary-transition approval. Huawei S-series VTY corrections use `authentication-mode aaa` with an explicit `vrp_s_series` profile and complete `user-interface vty` scope, preserving the selected domain, shared schemes/templates and exact current methods, including `local-case` username matching. Both mode planners require resolved authentication, authorization and accounting roles plus external recovery verification. `aaa.bindings` retains the selected-domain view; `aaa.domain_bindings` inventories explicitly modelled domains and their shared TACACS references. Actual per-user paths, other providers and vendor bindings remain separate evidence gaps. See the [AAA review example](examples/remediation_authentication.rb).
|
|
203
205
|
|
|
204
206
|
`tacacs.endpoints` objects may declare typed `bindings: [{kind: "aaa.domain", domain: "ADMIN", role: "authentication", position: "primary", selected: true}]`. On supported Comware/VRP profiles this compares the exact domain-reference set, including unselected shared uses; `position` is method-chain preference, separate from server priority. Omitted bindings check definitions only; `[]` needs known unbound evidence. Partial/unmodelled references stay unknown, and capability entries separate binding support from object support. Current plans expose shared-reference requirements without binding-change commands. See the [binding-policy example](examples/resource_bindings.rb).
|
data/docs/baseline-metrics.md
CHANGED
|
@@ -330,3 +330,27 @@ Huawei 的 local 与 local-case 用户名匹配规则不同。`aaa.bindings` 和
|
|
|
330
330
|
域、共享认证/授权/计费方案、HWTACACS 模板、密钥、用户、其他范围及 ACL 均保留;恢复仍需独立认证规划,Huawei 的保存/commit 流程仍须按目标版本确认。独立状态模型验证原生 UI 命令、最终指标、共享配置保留及二次零变更。现有真实备份不含 Huawei 样本,当前 Huawei 证据来自原厂语法、合成配置及安装包验证,不能称为真机或该备份集上的 Huawei 验收。
|
|
331
331
|
|
|
332
332
|
依据:[Huawei VTY 认证模式命令](https://support.huawei.com/enterprise/en/doc/EDOC1100214493/677f04d0)、[Huawei local/local-case 语义](https://info.support.huawei.com/hedex/api/pages/EDOC1100210438/AEK0901G/09/resources/dc/local-user_ap-group_view.html)、[Huawei HWTACACS、本地认证与计费故障说明](https://support.huawei.com/enterprise/en/doc/EDOC1100411502/5561445f/example-for-configuring-hwtacacs-and-local-authentication-command-authorization-and-command-auditing-for-administrators)。
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
## NX-OS 原生管理认证审计
|
|
336
|
+
|
|
337
|
+
NX-OS 使用全局 default/console 登录方法,不套用 IOS 的逐线命名登录列表。`aaa.nxos` 保留非秘密方法顺序、来源行、独立的 `fallback_on_error` 状态、commands/config-commands 两类授权、默认计费和 TACACS 功能状态。关闭本地错误后备不会清空服务器组;撤销方法列表回到本地默认,损坏的最后声明不能复用先前证据。组无法解析、未知方法或未建模 AAA 控制会保留不完整状态。该图是配置证据,不证明认证服务器可达或本地恢复可用。
|
|
338
|
+
|
|
339
|
+
`ssh.vty_aaa` 依据原生默认登录链判断账户认证,`none` 不会通过;无需虚构数字 VTY 范围。`tacacs.authorization` 同时检查默认命令与配置命令授权,缺失一类不会被另一类已配置的 TACACS 掩盖。禁用 TACACS 功能时,三项 TACACS 使用检查不能通过。授权链中的 `none` 保留为无数据库后备证据,不表示本地账户恢复。
|
|
340
|
+
|
|
341
|
+
完整输入可采用已确认的 SSH 默认启用状态;Telnet 默认随 Nexus 产品不同,缺少明确声明时不推导 SSH-only。显式 feature/no feature 按最终声明归约,损坏声明保留未知。IOS 的 login/transport input 不能作为 NX-OS 原生登录证明。原生 AAA 修正使用下节独立批准,不复用 IOS 命名列表;NX-OS VTY 协议修正命令仍未实现。
|
|
342
|
+
|
|
343
|
+
依据:[NX-OS 原生 AAA 与本地后备](https://www.cisco.com/c/en/us/td/docs/dcn/nx-os/nexus9000/105x/configuration/security/cisco-nexus-9000-series-nx-os-security-configuration-guide-release-105x/m-configuring-aaa.html)、[原生授权方法与 none](https://www.cisco.com/c/en/us/td/docs/switches/datacenter/nexus5500/sw/command/reference/security/n5500-sec-cr/n5500-sec_cmds_a.html)、[SSH 默认状态](https://www.cisco.com/c/en/us/td/docs/dcn/nx-os/nexus9000/104x/configuration/security/cisco-nexus-9000-series-nx-os-security-configuration-guide-release-104x/m-configuring-ssh-and-telnet.html)、[Nexus 3600 的 Telnet 默认差异](https://www.cisco.com/c/en/us/td/docs/dcn/nx-os/nexus3600/102x/configuration/security/cisco-nexus-3600-nx-os-security-configuration-guide-102x/m-configuring-ssh-and-telnet.pdf)。官方语法、合成测试与备份形态验证不等同于实机登录验收。
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
## NX-OS 原生管理 AAA 修正
|
|
347
|
+
|
|
348
|
+
`tacacs.login`、`tacacs.authorization`、`tacacs.accounting` 及 `ssh.vty_aaa` 可使用 `remediation.syntax_profile: nxos`。作用域固定为当前配置上下文的全局 default,不能混用 management_domain 或数字 vty_ranges;console 登录与其后备控制保持原样。所有操作都需 `management_consumers_verified: true` 和 `local_fallback_access_verified: true`;这些是外部批准,不由备份或可用组自动填充。标量已满足时不强制迁移到另一组或修改后备。
|
|
349
|
+
|
|
350
|
+
`methods` 必须明确指定。默认登录接受 `[{kind: local}]` 或一个及以上有序 `[{kind: tacacs, group: OPS}]`。远程登录还需 `local_fallback_on_error: true`;原生 fallback error local 在激活新登录组之前设置,不能误拼为 group OPS local。SSH 账户认证为 true 时可将 none 改为批准的本地或远程方法;其他已启用 TACACS/SSH 规则同时约束最终方法,不能为同一登录链批准不同目标。
|
|
351
|
+
|
|
352
|
+
授权的远程 methods 为有序 TACACS 组后接 `{kind: local}`,分别规划 commands 与 config-commands;两者只修改不匹配项,整项校验和渲染完成后才公开操作。计费接受有序 TACACS 组,不拼接未核验的 local 尾部。三项 TACACS 规则为 false 时只支持明确批准的单个 local 替代,不从 false 推导 none、删除服务器或开放无认证访问。本地方法切换保留独立 fallback 声明。
|
|
353
|
+
|
|
354
|
+
远程目标必须已有启用的 TACACS 功能、可解析的组地址成员和同身份端点密钥存在性,并具备 `tacacs_peer_compatibility_verified: true`。特殊 group tacacs+ 验证当前全体服务器;命名组逐成员验证。共享组的 VRF/source、其他使用者和材料保持原样,外部对端核验须涵盖既有路径。端点新增、删除、材料配置及未解析的原生引用不能由此偷偷完成;同一策略要求的端点变更须先处理。操作携带完整指纹,其他厂商呈现器或不同操作不能借用批准。
|
|
355
|
+
|
|
356
|
+
参见[可直接运行的合成示例](../examples/remediation_nxos_aaa.rb)及上节原厂语法依据。备份形态故障注入与独立命令状态模型验证的是离线配置闭环,不能替代目标固件接受、远程职责、本地恢复和共享消费者的人工实测;保存仍是独立人工提示。
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "baseline"
|
|
4
|
+
|
|
5
|
+
# 全部身份、材料及批准均为合成示例;只生成待人工审核命令,不访问设备。
|
|
6
|
+
configuration = "hostname LAB\nfeature tacacs+\nno feature telnet\n" \
|
|
7
|
+
"tacacs-server host 192.0.2.30 key 7 SYNTHETIC-ONLY\n" \
|
|
8
|
+
"aaa group server tacacs+ OPS\n server 192.0.2.30\n use-vrf management\n!\n" \
|
|
9
|
+
"aaa authentication login default none\nno aaa authentication login default fallback error local\n" \
|
|
10
|
+
"aaa authentication login console local\naaa authorization commands default local\n" \
|
|
11
|
+
"aaa authorization config-commands default local\naaa accounting default local\nline vty\n!\n"
|
|
12
|
+
checks = %w[tacacs.login tacacs.authorization tacacs.accounting ssh.vty_aaa].to_h do |id|
|
|
13
|
+
methods = [ { "kind" => "tacacs", "group" => "OPS" } ]
|
|
14
|
+
methods << { "kind" => "local" } if id == "tacacs.authorization"
|
|
15
|
+
options = { "syntax_profile" => "nxos", "methods" => methods,
|
|
16
|
+
"local_fallback_access_verified" => true, "management_consumers_verified" => true, "tacacs_peer_compatibility_verified" => true }
|
|
17
|
+
options["local_fallback_on_error"] = true if %w[tacacs.login ssh.vty_aaa].include?(id)
|
|
18
|
+
[ id, { "expected" => true, "remediation" => options } ]
|
|
19
|
+
end
|
|
20
|
+
policy = Baseline::Policy.new("version" => 1, "checks" => checks)
|
|
21
|
+
plan = Baseline::Remediation.new(policy: policy).call(configuration: configuration, platform: :cisco_nxos, complete: true)
|
|
22
|
+
raise "Expected native management-role corrections" unless plan.status == :ready && plan.operations.size == 4
|
|
23
|
+
raise "Unexpected secret output" if plan.to_h.to_s.include?("SYNTHETIC-ONLY")
|
|
24
|
+
puts plan.to_text
|
|
@@ -54,13 +54,13 @@ module Baseline
|
|
|
54
54
|
version = effective_configured_value(/\Aip ssh version\s+([12])\z/,
|
|
55
55
|
family: /\A(?:no )?ip ssh version\b/, fact: "ssh.versions", reset: /\Ano ip ssh version(?: [12])?\z/)
|
|
56
56
|
record_fact("ssh.versions", [ version ]) if version
|
|
57
|
-
ssh = nxos? ?
|
|
57
|
+
ssh = nxos? ? nxos_feature_state("ssh", default: true) : version ? true : nil
|
|
58
58
|
record_fact("ssh.enabled", ssh, matching_blocks(/\A(?:ip ssh|(?:no )?feature ssh)/))
|
|
59
59
|
default_aaa = nxos? || configured_state(/\Aaaa new-model\z/, /\Ano aaa new-model\z/) == true
|
|
60
60
|
vtys = matching_blocks(/\Aline vty(?:\s|\z)/).map do |section|
|
|
61
61
|
login = configured_vty_login(section, default_aaa: default_aaa)
|
|
62
62
|
acl_entries = section.children.reject { |entry| /\A(?:no )?access-class\s+\S+\s+out(?:\s|\z)/.match?(entry.text) }
|
|
63
|
-
{ name: section.text, protocols: effective_configured_value(/\Atransport input\s+((?:all|none|ssh|telnet)(?:\s+(?:ssh|telnet))*)\z/, entries: section.children,
|
|
63
|
+
{ name: section.text, protocols: nxos? ? nxos_remote_protocols : effective_configured_value(/\Atransport input\s+((?:all|none|ssh|telnet)(?:\s+(?:ssh|telnet))*)\z/, entries: section.children,
|
|
64
64
|
family: /\A(?:no )?transport input\b/, fact: "ssh.vty_ssh_only", reset: /\Ano transport input(?:\s+\S+)*\z/)&.split,
|
|
65
65
|
authentication: login[:authentication], authentication_list: login[:authentication_list],
|
|
66
66
|
acl_vrf_also: acl_entries.reverse.find { |entry| /\A(?:no )?access-class\b/.match?(entry.text) }&.text&.end_with?(" vrf-also") == true,
|
|
@@ -77,7 +77,7 @@ module Baseline
|
|
|
77
77
|
ssh_port: rotary.empty? ? listener_port("ssh port", default: 22, scope: "management.acl", negate: "no") : nil,
|
|
78
78
|
telnet_port: telnet_ports.empty? ? 23 : nil)
|
|
79
79
|
telnet = if nxos?
|
|
80
|
-
|
|
80
|
+
nxos_feature_state("telnet")
|
|
81
81
|
elsif vtys.any? && vtys.all? { |entry| entry[:protocols] }
|
|
82
82
|
vtys.any? { |entry| (entry[:protocols] & %w[all telnet]).any? }
|
|
83
83
|
end
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../nxos_aaa_methods"
|
|
4
|
+
|
|
5
|
+
module Baseline
|
|
6
|
+
module CiscoNxos
|
|
7
|
+
# 原生默认方法、独立后备与命令/配置命令授权分别归约,不套用 IOS 的逐线登录列表。
|
|
8
|
+
module Authentication
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
def prepare_configuration
|
|
12
|
+
super
|
|
13
|
+
@nxos_aaa_graph = nil
|
|
14
|
+
@nxos_features = {}
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def nxos_feature_state(name, default: nil)
|
|
18
|
+
key = [ name, default ]
|
|
19
|
+
return @nxos_features[key] if @nxos_features.key?(key)
|
|
20
|
+
entry = matching_blocks(/\A(?:no )?feature #{Regexp.escape(name)}(?:\s|\z)/).last
|
|
21
|
+
value = if entry
|
|
22
|
+
case entry.text
|
|
23
|
+
when "feature #{name}" then analyze(entry) { true }
|
|
24
|
+
when "no feature #{name}" then analyze(entry) { false }
|
|
25
|
+
else
|
|
26
|
+
fact = { "ssh" => "ssh.enabled", "telnet" => "services.telnet", "tacacs+" => "tacacs" }.fetch(name)
|
|
27
|
+
record_unknown_fact(fact, [ entry ], code: :unsupported_authentication_method)
|
|
28
|
+
nil
|
|
29
|
+
end
|
|
30
|
+
elsif @fact_builder.complete?
|
|
31
|
+
default
|
|
32
|
+
end
|
|
33
|
+
@nxos_features[key] = value
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# SSH 缺省稳定;Telnet 缺省跨 Nexus 产品不同,缺显式声明时继续未知。
|
|
37
|
+
def nxos_remote_protocols
|
|
38
|
+
ssh, telnet = nxos_feature_state("ssh", default: true), nxos_feature_state("telnet")
|
|
39
|
+
return if ssh.nil? || telnet.nil?
|
|
40
|
+
values = []
|
|
41
|
+
values << "ssh" if ssh
|
|
42
|
+
values << "telnet" if telnet
|
|
43
|
+
values.empty? ? [ "none" ] : values
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def configured_vty_login(section, default_aaa:)
|
|
47
|
+
foreign = matching_blocks(/\A(?:no )?(?:login|transport input)(?:\s|\z)/, section.children)
|
|
48
|
+
if foreign.any?
|
|
49
|
+
record_unknown_fact("ssh.vty_aaa", foreign, code: :unsupported_authentication_method)
|
|
50
|
+
record_unknown_fact("tacacs.login", foreign, code: :unsupported_authentication_method)
|
|
51
|
+
end
|
|
52
|
+
state = nxos_method_state("aaa authentication login default", "authentication")
|
|
53
|
+
account = state["complete"] && state["methods"].none? { |method| method["kind"] == "none" }
|
|
54
|
+
{ authentication: foreign.any? || !state["complete"] ? nil : account ? "aaa" : "none", authentication_list: "default" }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# 后备行不参与主列表覆盖;撤销独立控制也不能清除现有服务器组。
|
|
58
|
+
def nxos_method_state(prefix, role)
|
|
59
|
+
state = { "methods" => [ { "kind" => "local" } ], "declared" => false, "complete" => @fact_builder.complete?, "lines" => [] }
|
|
60
|
+
matching_blocks(/\A(?:no )?#{Regexp.escape(prefix)}(?:\s|\z)/).each do |entry|
|
|
61
|
+
text = entry.text.delete_prefix("no ")
|
|
62
|
+
tail = text.delete_prefix(prefix).strip
|
|
63
|
+
next if role == "authentication" && (tail == "fallback" || tail.start_with?("fallback "))
|
|
64
|
+
methods = NxosAaaMethods.parse(tail, role: role)
|
|
65
|
+
negative = entry.text.start_with?("no ")
|
|
66
|
+
if methods || negative && tail.empty?
|
|
67
|
+
analyze(entry) { true }
|
|
68
|
+
state = { "methods" => negative ? [ { "kind" => "local" } ] : methods, "declared" => !negative, "complete" => true, "lines" => [ entry.line ] }
|
|
69
|
+
else
|
|
70
|
+
state = { "methods" => [], "declared" => true, "complete" => false, "lines" => [ entry.line ] }
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
state
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def nxos_login_state(selector)
|
|
77
|
+
node = nxos_method_state("aaa authentication login #{selector}", "authentication")
|
|
78
|
+
fallback, lines = @fact_builder.complete? ? true : nil, []
|
|
79
|
+
matching_blocks(/\A(?:no )?aaa authentication login #{selector} fallback(?:\s|\z)/).each do |entry|
|
|
80
|
+
fallback = case entry.text
|
|
81
|
+
when "aaa authentication login #{selector} fallback error local" then analyze(entry) { true }
|
|
82
|
+
when "no aaa authentication login #{selector} fallback error local" then analyze(entry) { false }
|
|
83
|
+
end
|
|
84
|
+
lines = [ entry.line ]
|
|
85
|
+
end
|
|
86
|
+
node.merge("fallback_on_error" => fallback, "fallback_lines" => lines, "complete" => node["complete"] && !fallback.nil?)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def nxos_resolve_methods(node, servers, inventory)
|
|
90
|
+
complete = node["complete"]
|
|
91
|
+
methods = node["methods"].map do |method|
|
|
92
|
+
next method unless method["kind"] == "server_group"
|
|
93
|
+
name = method["group"]
|
|
94
|
+
known = name == "tacacs+" ? servers.any? && !@fact_builder.unresolved?("tacacs.servers") :
|
|
95
|
+
inventory.closed && inventory.groups.fetch(name, []).any? && inventory.references.fetch(name, []).all? { |reference| reference[:complete] }
|
|
96
|
+
if known
|
|
97
|
+
{ "kind" => "tacacs", "group" => name }
|
|
98
|
+
elsif name == "radius"
|
|
99
|
+
{ "kind" => "radius" }
|
|
100
|
+
else
|
|
101
|
+
complete = false
|
|
102
|
+
{ "kind" => "unresolved" }
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
node.merge("methods" => methods, "complete" => complete)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def record_tacacs_login_usage(servers, inventory)
|
|
109
|
+
authentication = %w[default console].to_h { |selector| [ selector, nxos_resolve_methods(nxos_login_state(selector), servers, inventory) ] }
|
|
110
|
+
authorization = %w[commands config-commands].to_h do |action|
|
|
111
|
+
[ action, nxos_resolve_methods(nxos_method_state("aaa authorization #{action} default", "authorization"), servers, inventory) ]
|
|
112
|
+
end
|
|
113
|
+
accounting = nxos_resolve_methods(nxos_method_state("aaa accounting default", "accounting"), servers, inventory)
|
|
114
|
+
unsupported = matching_blocks(/\A(?:no )?aaa (?:authentication login|authorization|accounting)(?:\s|\z)/).reject do |entry|
|
|
115
|
+
/\A(?:no )?aaa (?:authentication login (?:default|console|ascii-authentication|error-enable|invalid-username-log|(?:mschap|mschapv2|chap) enable)(?:\s|\z)|authorization (?:commands|config-commands) default(?:\s|\z)|accounting (?:default|dot1x)(?:\s|\z))/.match?(entry.text)
|
|
116
|
+
end
|
|
117
|
+
foreign = matching_blocks(/\Aline vty(?:\s|\z)/).flat_map do |section|
|
|
118
|
+
matching_blocks(/\A(?:no )?(?:login|transport input)(?:\s|\z)/, section.children)
|
|
119
|
+
end
|
|
120
|
+
scope_complete = unsupported.empty? && foreign.empty?
|
|
121
|
+
@nxos_aaa_graph = CtxParser::Immutable.copy({ "authentication" => authentication, "authorization" => authorization,
|
|
122
|
+
"accounting" => { "default" => accounting }, "scope_complete" => scope_complete, "tacacs_enabled" => nxos_feature_state("tacacs+", default: false) })
|
|
123
|
+
record_fact("aaa.nxos", @nxos_aaa_graph)
|
|
124
|
+
record_unknown_fact("aaa.nxos", unsupported + foreign, code: :unsupported_authentication_method) unless scope_complete
|
|
125
|
+
node = authentication.fetch("default")
|
|
126
|
+
nxos_record_usage("tacacs.login", [ node ], scope_complete: scope_complete)
|
|
127
|
+
if node["complete"]
|
|
128
|
+
record_fact("ssh.vty_aaa", node["methods"].none? { |method| method["kind"] == "none" })
|
|
129
|
+
else
|
|
130
|
+
record_unknown_fact("ssh.vty_aaa", code: :authentication_list_unresolved)
|
|
131
|
+
end
|
|
132
|
+
if (protocols = nxos_remote_protocols)
|
|
133
|
+
record_fact("ssh.vty_ssh_only", protocols == [ "ssh" ])
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def record_tacacs_action_usage(kind, _servers, _inventory)
|
|
138
|
+
nodes = @nxos_aaa_graph.fetch(kind).values
|
|
139
|
+
nxos_record_usage("tacacs.#{kind}", nodes, scope_complete: @nxos_aaa_graph["scope_complete"])
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def nxos_record_usage(fact, nodes, scope_complete:)
|
|
143
|
+
feature = @nxos_aaa_graph["tacacs_enabled"]
|
|
144
|
+
if !scope_complete || nodes.empty? || nodes.any? { |node| !node["complete"] } || feature.nil?
|
|
145
|
+
record_unknown_fact(fact, code: :tacacs_group_unresolved)
|
|
146
|
+
else
|
|
147
|
+
used = feature && nodes.all? { |node| node["methods"].first&.fetch("kind") == "tacacs" }
|
|
148
|
+
lines = nodes.flat_map { |node| node["lines"] }
|
|
149
|
+
entries = roots.select { |entry| lines.include?(entry.line) }
|
|
150
|
+
record_fact(fact, used, entries)
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../cisco/command_renderer"
|
|
4
|
+
require_relative "../nxos_aaa_schema"
|
|
4
5
|
|
|
5
6
|
module Baseline
|
|
6
7
|
module CiscoNxos
|
|
@@ -14,6 +15,30 @@ module Baseline
|
|
|
14
15
|
NTP_STYLE = :nxos
|
|
15
16
|
SSH_DISABLE_COMMAND = "no feature ssh"
|
|
16
17
|
|
|
18
|
+
def supports?(id) = NxosAaaSchema::IDS.key?(id) || super
|
|
19
|
+
|
|
20
|
+
def render(operation, verified_aaa_operation_ids: [], **options)
|
|
21
|
+
return super unless operation.kind == NxosAaaSchema::KIND
|
|
22
|
+
unless verified_aaa_operation_ids.is_a?(Array) && verified_aaa_operation_ids.all? { |id| id.is_a?(String) && /\A[0-9a-f]{64}\z/.match?(id) } && verified_aaa_operation_ids.include?(operation.id)
|
|
23
|
+
raise UnsupportedOperation, "management_method_preconditions_unverified"
|
|
24
|
+
end
|
|
25
|
+
NxosAaaSchema.validate!(identity: operation.identity, scope: operation.scope, before: operation.before, after: operation.after, action: operation.action, phase: operation.phase)
|
|
26
|
+
role = operation.identity.fetch("role")
|
|
27
|
+
methods = operation.after.fetch("methods")
|
|
28
|
+
native = methods == [ { "kind" => "local" } ] ? "local" : "group #{methods.select { |method| method['kind'] == 'tacacs' }.map { |method| method.fetch('group') }.join(' ')}#{role == 'authorization' ? ' local' : ''}"
|
|
29
|
+
prefix = case role
|
|
30
|
+
when "authentication" then "aaa authentication login default"
|
|
31
|
+
when "authorization" then "aaa authorization #{operation.identity.fetch('function')} default"
|
|
32
|
+
when "accounting" then "aaa accounting default"
|
|
33
|
+
end
|
|
34
|
+
commands = []
|
|
35
|
+
if role == "authentication" && operation.before["fallback_on_error"] != operation.after["fallback_on_error"]
|
|
36
|
+
commands << "aaa authentication login default fallback error local"
|
|
37
|
+
end
|
|
38
|
+
commands << "#{prefix} #{native}" unless operation.before["methods"] == methods
|
|
39
|
+
commands.map { |command| CommandStep.new(view: [], command: command, phase: operation.phase, operation_id: operation.id) }
|
|
40
|
+
end
|
|
41
|
+
|
|
17
42
|
private
|
|
18
43
|
|
|
19
44
|
# NX-OS 地址族由地址本身决定,路由域使用 use-vrf。
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../cisco/parser"
|
|
4
|
+
require_relative "authentication"
|
|
4
5
|
|
|
5
6
|
module Baseline
|
|
6
7
|
module CiscoNxos
|
|
7
8
|
# 解析 NX-OS 功能开关、角色权限及命名扩展 ACL。
|
|
8
9
|
class Parser < Cisco::Parser
|
|
10
|
+
include Authentication
|
|
9
11
|
private
|
|
10
12
|
|
|
11
13
|
# 选择共享 Cisco 解析逻辑中的 NX-OS 分支。
|
|
@@ -32,6 +32,8 @@ module Baseline
|
|
|
32
32
|
TacacsOperationSchema.validate!(kind: kind, identity: identity, scope: scope, before: before, after: after, action: action, phase: phase)
|
|
33
33
|
elsif kind == VtyAuthenticationSchema::KIND
|
|
34
34
|
VtyAuthenticationSchema.validate!(identity: identity, scope: scope, before: before, after: after, action: action, phase: phase)
|
|
35
|
+
elsif kind == NxosAaaSchema::KIND
|
|
36
|
+
NxosAaaSchema.validate!(identity: identity, scope: scope, before: before, after: after, action: action, phase: phase)
|
|
35
37
|
elsif kind == AaaChainSchema::KIND
|
|
36
38
|
AaaChainSchema.validate!(identity: identity, scope: scope, before: before, after: after, action: action, phase: phase)
|
|
37
39
|
elsif kind == "syslog.reference"
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "resource_schema"
|
|
4
|
+
|
|
5
|
+
module Baseline
|
|
6
|
+
# NX-OS 的 group-list 是单个有序组列表,本地错误后备不是认证列表尾部方法。
|
|
7
|
+
module NxosAaaMethods
|
|
8
|
+
def self.group?(name) = name == "tacacs+" || ResourceSchema.token?(name) && !%w[group local none fallback error default console].include?(name)
|
|
9
|
+
|
|
10
|
+
def self.parse(text, role:)
|
|
11
|
+
return [ { "kind" => "local" } ] if text == "local"
|
|
12
|
+
return [ { "kind" => "none" } ] if text == "none" && %w[authentication authorization].include?(role)
|
|
13
|
+
tokens = text.split
|
|
14
|
+
return unless tokens.shift == "group"
|
|
15
|
+
terminal = tokens.pop if role == "authorization" && %w[local none].include?(tokens.last) || role == "authentication" && tokens.last == "none"
|
|
16
|
+
return unless !tokens.empty? && tokens.join(" ").bytesize <= 127 && tokens.uniq == tokens && tokens.all? { |name| group?(name) }
|
|
17
|
+
methods = tokens.map { |name| { "kind" => "server_group", "group" => name } }
|
|
18
|
+
methods << { "kind" => terminal } if terminal
|
|
19
|
+
methods
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "configuration_operation"
|
|
4
|
+
require_relative "nxos_aaa_schema"
|
|
5
|
+
require_relative "tacacs_remediation"
|
|
6
|
+
|
|
7
|
+
module Baseline
|
|
8
|
+
# 只激活现有已核验的原生方法引用,不改变共享服务器对象、控制台或既有材料。
|
|
9
|
+
class NxosAaaRemediation
|
|
10
|
+
def self.applicable?(finding, evidence)
|
|
11
|
+
evidence.platform == "cisco_nxos" && evidence.complete? && NxosAaaSchema::IDS.key?(finding.id) &&
|
|
12
|
+
!finding.expected.nil? && %i[fail unknown].include?(finding.status)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def initialize(policy:) = @policy = policy
|
|
16
|
+
|
|
17
|
+
def call(finding, audit)
|
|
18
|
+
options = @policy.rules.find { |rule| rule.id == finding.id }.remediation
|
|
19
|
+
require!(options["syntax_profile"] == "nxos", "native_management_method_syntax_profile")
|
|
20
|
+
%w[methods local_fallback_access_verified management_consumers_verified].each do |key|
|
|
21
|
+
require!(options[key] && (!key.end_with?("verified") || options[key] == true), key)
|
|
22
|
+
end
|
|
23
|
+
evidence = audit.configuration
|
|
24
|
+
require!(evidence.complete? && evidence.platform == "cisco_nxos", "complete_native_configuration")
|
|
25
|
+
graph = evidence.fact("aaa.nxos")
|
|
26
|
+
require!(evidence.fact_known?("aaa.nxos") && graph["scope_complete"], "resolved_native_management_method_scope")
|
|
27
|
+
nodes = [ graph["authentication"]["default"], *graph["authorization"].values, graph["accounting"]["default"] ]
|
|
28
|
+
require!(nodes.all? { |node| node["complete"] }, "resolved_management_login_roles")
|
|
29
|
+
role = NxosAaaSchema::IDS.fetch(finding.id)
|
|
30
|
+
methods = options.fetch("methods")
|
|
31
|
+
remote = methods.first["kind"] == "tacacs"
|
|
32
|
+
conflict! if finding.id == "ssh.vty_aaa" ? finding.expected != true : remote != finding.expected
|
|
33
|
+
verify_authentication_policy!(audit, methods) if role == "authentication"
|
|
34
|
+
stable = audit.findings.select { |item| TacacsRemediation::IDS.include?(item.id) && item.status != :not_applicable && !item.expected.nil? }
|
|
35
|
+
require!(stable.all? { |item| item.status == :pass }, "stable_approved_tacacs_endpoints")
|
|
36
|
+
if remote
|
|
37
|
+
require!(graph["tacacs_enabled"] == true, "existing_enabled_tacacs_feature")
|
|
38
|
+
require!(options["tacacs_peer_compatibility_verified"] == true, "tacacs_peer_compatibility_verified")
|
|
39
|
+
require!(options["local_fallback_on_error"] == true, "local_fallback_on_error") if role == "authentication"
|
|
40
|
+
verify_targets!(evidence, methods)
|
|
41
|
+
end
|
|
42
|
+
functions = role == "authorization" ? %w[commands config-commands] : [ "default" ]
|
|
43
|
+
functions.filter_map do |function|
|
|
44
|
+
node = graph.fetch(role).fetch(function)
|
|
45
|
+
before = { "methods" => node.fetch("methods") }
|
|
46
|
+
after = { "methods" => methods }
|
|
47
|
+
if role == "authentication"
|
|
48
|
+
before["fallback_on_error"] = node.fetch("fallback_on_error")
|
|
49
|
+
after["fallback_on_error"] = remote ? true : before["fallback_on_error"]
|
|
50
|
+
end
|
|
51
|
+
next if before == after
|
|
52
|
+
identity = { "role" => role }
|
|
53
|
+
identity["function"] = function if role == "authorization"
|
|
54
|
+
ConfigurationOperation.new(kind: NxosAaaSchema::KIND, identity: identity, scope: { "selector" => "default", "syntax_profile" => "nxos" },
|
|
55
|
+
before: before, after: after, action: :replace, phase: :activate, risk: finding.severity == "critical" ? "critical" : "high")
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def require!(condition, requirement)
|
|
62
|
+
raise TargetRemediation::Blocked.new(:authentication_chain_evidence_required, requirements: [ requirement ]) unless condition
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def conflict! = raise(TargetRemediation::Blocked, :policy_conflict)
|
|
66
|
+
|
|
67
|
+
# 同一 default 登录链不得接受两个不同目标,已有通过的标量规则仍约束投影结果。
|
|
68
|
+
def verify_authentication_policy!(audit, methods)
|
|
69
|
+
related = audit.findings.select { |item| %w[tacacs.login ssh.vty_aaa].include?(item.id) && item.status != :not_applicable && !item.expected.nil? }
|
|
70
|
+
related.each do |item|
|
|
71
|
+
conflict! if item.id == "ssh.vty_aaa" ? item.expected != true : item.expected != (methods.first["kind"] == "tacacs")
|
|
72
|
+
rule = @policy.rules.find { |candidate| candidate.id == item.id }
|
|
73
|
+
if rule.remediation["syntax_profile"] == "nxos"
|
|
74
|
+
flags = %w[management_consumers_verified local_fallback_access_verified]
|
|
75
|
+
flags += %w[tacacs_peer_compatibility_verified local_fallback_on_error] if methods.first["kind"] == "tacacs"
|
|
76
|
+
conflict! if flags.any? { |key| rule.remediation[key] == false }
|
|
77
|
+
end
|
|
78
|
+
if item.status != :pass && rule.remediation["syntax_profile"] == "nxos" && rule.remediation.key?("methods")
|
|
79
|
+
conflict! unless rule.remediation["methods"] == methods
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# 现有地址成员逐个关联相同身份的材料;原生组的 VRF/source 等保持原样,由外部对端核验覆盖运行路径。
|
|
85
|
+
def verify_targets!(evidence, methods)
|
|
86
|
+
require!(%w[tacacs.servers tacacs.groups tacacs.group_references tacacs.endpoints].all? { |fact| evidence.fact_known?(fact) }, "resolved_tacacs_reference_inventory")
|
|
87
|
+
methods.select { |method| method["kind"] == "tacacs" }.each do |method|
|
|
88
|
+
group = method.fetch("group")
|
|
89
|
+
refs = evidence.fact("tacacs.group_references")[group]
|
|
90
|
+
addresses = if group == "tacacs+"
|
|
91
|
+
evidence.fact("tacacs.servers")
|
|
92
|
+
else
|
|
93
|
+
require!(refs && refs.any? && refs.all? { |ref| ref[:kind] == "address" && ref[:complete] }, "resolved_native_tacacs_group_members")
|
|
94
|
+
refs.flat_map { |ref| ref[:servers] }.uniq
|
|
95
|
+
end
|
|
96
|
+
require!(addresses.any?, "resolved_tacacs_role_endpoints")
|
|
97
|
+
addresses.each do |address|
|
|
98
|
+
targets = evidence.fact("tacacs.endpoints").select { |target| target["identity"] == { "address" => address } }
|
|
99
|
+
require!(targets.any? && targets.all? { |target| target["attributes"]["key_present"] == true }, "existing_tacacs_endpoint_key")
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "aaa_chain_schema"
|
|
4
|
+
require_relative "nxos_aaa_methods"
|
|
5
|
+
|
|
6
|
+
module Baseline
|
|
7
|
+
# 原生全局 default 范围与职责语法独立于 Comware 域和 IOS 命名登录列表。
|
|
8
|
+
module NxosAaaSchema
|
|
9
|
+
KIND = "aaa.nxos_chain"
|
|
10
|
+
IDS = AaaChainSchema::IDS.merge("ssh.vty_aaa" => "authentication").freeze
|
|
11
|
+
APPROVALS = %w[syntax_profile methods local_fallback_on_error local_fallback_access_verified management_consumers_verified tacacs_peer_compatibility_verified].freeze
|
|
12
|
+
|
|
13
|
+
def self.methods?(methods, role:, approved:)
|
|
14
|
+
return false unless methods.is_a?(Array) && methods.any? && methods.all? { |method| method.is_a?(Hash) }
|
|
15
|
+
return true if methods == [ { "kind" => "local" } ]
|
|
16
|
+
return !approved && role != "accounting" if methods == [ { "kind" => "none" } ]
|
|
17
|
+
terminal = methods.last if %w[local none].include?(methods.last&.fetch("kind", nil))
|
|
18
|
+
groups = terminal ? methods[0...-1] : methods
|
|
19
|
+
valid = groups.any? && groups.all? do |method|
|
|
20
|
+
method.is_a?(Hash) && (method.keys.sort == %w[group kind] && method["kind"] == "tacacs" && NxosAaaMethods.group?(method["group"]) ||
|
|
21
|
+
!approved && method == { "kind" => "radius" })
|
|
22
|
+
end
|
|
23
|
+
return false unless valid && groups.uniq == groups && groups.map { |method| method["group"] || "radius" }.join(" ").bytesize <= 127
|
|
24
|
+
return terminal.nil? if role == "accounting"
|
|
25
|
+
return !terminal || !approved && terminal == { "kind" => "none" } if role == "authentication"
|
|
26
|
+
|
|
27
|
+
approved ? terminal == { "kind" => "local" } : !terminal || [ { "kind" => "local" }, { "kind" => "none" } ].include?(terminal)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.validate_approval!(value, id:)
|
|
31
|
+
role = IDS.fetch(id)
|
|
32
|
+
valid = (value.keys - APPROVALS).empty? && (!value.key?("methods") || methods?(value["methods"], role: role, approved: true)) &&
|
|
33
|
+
(!value.key?("local_fallback_on_error") || role == "authentication" && value["methods"] != [ { "kind" => "local" } ] && [ true, false ].include?(value["local_fallback_on_error"]))
|
|
34
|
+
raise ArgumentError, "Invalid native NX-OS method approval" unless valid
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.validate!(identity:, scope:, before:, after:, action:, phase:)
|
|
38
|
+
role = identity.is_a?(Hash) && identity["role"]
|
|
39
|
+
fields = role == "authentication" ? %w[fallback_on_error methods] : [ "methods" ]
|
|
40
|
+
identity_valid = role == "authorization" ? identity.keys.sort == %w[function role] && %w[commands config-commands].include?(identity["function"]) :
|
|
41
|
+
%w[authentication accounting].include?(role) && identity.keys == [ "role" ]
|
|
42
|
+
valid = identity_valid && scope == { "selector" => "default", "syntax_profile" => "nxos" } && action == :replace && phase == :activate &&
|
|
43
|
+
before.is_a?(Hash) && before.keys.sort == fields && methods?(before["methods"], role: role, approved: false) &&
|
|
44
|
+
after.is_a?(Hash) && after.keys.sort == fields && methods?(after["methods"], role: role, approved: true)
|
|
45
|
+
if valid && role == "authentication"
|
|
46
|
+
valid = [ true, false ].include?(before["fallback_on_error"]) &&
|
|
47
|
+
(after["methods"] == [ { "kind" => "local" } ] ? after["fallback_on_error"] == before["fallback_on_error"] : after["fallback_on_error"] == true)
|
|
48
|
+
end
|
|
49
|
+
raise ArgumentError, "Invalid native NX-OS method operation" unless valid
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
data/lib/baseline/remediation.rb
CHANGED
|
@@ -24,7 +24,12 @@ module Baseline
|
|
|
24
24
|
CtxParser::Immutable.copy(Audit.capabilities(platform: name).to_h do |id, capability|
|
|
25
25
|
supported = capability[:status] != :unsupported && renderer.supports?(id)
|
|
26
26
|
requirements = RemediationPlanner::REQUIREMENTS.fetch(id, [ "supported_rule_handler" ])
|
|
27
|
-
if supported &&
|
|
27
|
+
if supported && name == "cisco_nxos" && NxosAaaSchema::IDS.key?(id)
|
|
28
|
+
requirements = %w[native_management_method_syntax_profile approved_ordered_native_methods resolved_native_management_method_scope
|
|
29
|
+
resolved_management_login_roles local_fallback_access_verified management_consumers_verified stable_approved_tacacs_endpoints
|
|
30
|
+
existing_enabled_tacacs_feature_for_remote_methods existing_tacacs_endpoint_keys_for_remote_methods tacacs_peer_compatibility_verified_for_remote_methods]
|
|
31
|
+
requirements << "local_fallback_on_error_for_remote_authentication" if NxosAaaSchema::IDS[id] == "authentication"
|
|
32
|
+
elsif supported && id == "ssh.vty_aaa"
|
|
28
33
|
requirements = AaaChainSchema::APPROVALS + %w[vty_ranges complete_matching_vty_scope existing_approved_management_method_chain
|
|
29
34
|
exact_existing_methods_including_local_case resolved_management_login_roles known_vty_authentication_and_protocols]
|
|
30
35
|
requirements += name == "h3c" ? [ "temporary_vty_protocol_rebind_approved_for_inherited_protocol_changes" ] : [ "existing_shared_tacacs_key_for_remote_roles" ]
|
|
@@ -14,6 +14,7 @@ require_relative "credential_remediation"
|
|
|
14
14
|
require_relative "tacacs_key_remediation"
|
|
15
15
|
require_relative "authentication_remediation"
|
|
16
16
|
require_relative "aaa_chain_remediation"
|
|
17
|
+
require_relative "nxos_aaa_remediation"
|
|
17
18
|
require_relative "vty_authentication_remediation"
|
|
18
19
|
require_relative "resource_binding_remediation"
|
|
19
20
|
require_relative "tacacs_remediation"
|
|
@@ -90,6 +91,8 @@ module Baseline
|
|
|
90
91
|
elsif TacacsKeyRemediation.applicable?(finding, audit.configuration)
|
|
91
92
|
result = TacacsKeyRemediation.new.call(finding, audit.configuration)
|
|
92
93
|
check(finding, :blocked, result.fetch(:reason), requirements: result.fetch(:requirements), details: result.fetch(:details))
|
|
94
|
+
elsif NxosAaaRemediation.applicable?(finding, audit.configuration)
|
|
95
|
+
compile_aaa_chain(finding, audit, operations, steps)
|
|
93
96
|
elsif AuthenticationRemediation.applicable?(finding, audit.configuration)
|
|
94
97
|
if AaaChainSchema::IDS.key?(finding.id) && @policy.rules.find { |rule| rule.id == finding.id }.remediation.any?
|
|
95
98
|
next compile_aaa_chain(finding, audit, operations, steps)
|
|
@@ -148,11 +151,13 @@ module Baseline
|
|
|
148
151
|
|
|
149
152
|
# 认证链在完整核验后才公开单个原生修改,原操作指纹不能为不同目标背书。
|
|
150
153
|
def compile_aaa_chain(finding, audit, operations, steps)
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
+
handler = audit.configuration.platform == "cisco_nxos" ? NxosAaaRemediation : AaaChainRemediation
|
|
155
|
+
proposed = Array(handler.new(policy: @policy).call(finding, audit))
|
|
156
|
+
ids = proposed.map(&:id)
|
|
157
|
+
rendered = proposed.flat_map { |operation| @renderer.render(operation, verified_aaa_operation_ids: ids) }
|
|
158
|
+
operations.concat(proposed)
|
|
154
159
|
steps.concat(rendered)
|
|
155
|
-
check(finding, :planned, nil, operation_ids:
|
|
160
|
+
check(finding, :planned, nil, operation_ids: ids, details: { verification_scope: :configured_inventory_and_external_management_approval })
|
|
156
161
|
rescue TargetRemediation::Blocked => error
|
|
157
162
|
check(finding, :blocked, error.reason, requirements: error.requirements)
|
|
158
163
|
rescue CommandRenderer::UnsupportedOperation
|
data/lib/baseline/rule.rb
CHANGED
|
@@ -6,6 +6,7 @@ require_relative "resource_schema"
|
|
|
6
6
|
require_relative "syslog_reference_schema"
|
|
7
7
|
require_relative "snmp_group_migration_schema"
|
|
8
8
|
require_relative "aaa_chain_schema"
|
|
9
|
+
require_relative "nxos_aaa_schema"
|
|
9
10
|
require_relative "vty_authentication_schema"
|
|
10
11
|
|
|
11
12
|
require "ctx_parser/immutable"
|
|
@@ -85,6 +86,7 @@ module Baseline
|
|
|
85
86
|
end
|
|
86
87
|
@managed_scopes = CtxParser::Immutable.copy(options["managed_scopes"])
|
|
87
88
|
@remediation = CtxParser::Immutable.copy(options.fetch("remediation", {}))
|
|
89
|
+
native_aaa_keys = remediation.is_a?(Hash) && [ nil, "nxos" ].include?(remediation["syntax_profile"]) ? NxosAaaSchema::APPROVALS : []
|
|
88
90
|
remediation_keys = if id == "snmp.versions"
|
|
89
91
|
%w[existing_v3_access_verified]
|
|
90
92
|
elsif id == "snmp.write_enabled"
|
|
@@ -102,9 +104,9 @@ module Baseline
|
|
|
102
104
|
elsif %w[syslog.minimum_servers syslog.servers syslog.destinations ntp.minimum_servers ntp.servers].include?(id)
|
|
103
105
|
%w[maximum_targets order]
|
|
104
106
|
elsif id == "ssh.vty_aaa"
|
|
105
|
-
AaaChainSchema::APPROVALS + %w[vty_ranges temporary_vty_protocol_rebind_approved]
|
|
107
|
+
(AaaChainSchema::APPROVALS + native_aaa_keys + %w[vty_ranges temporary_vty_protocol_rebind_approved]).uniq
|
|
106
108
|
elsif AaaChainSchema::IDS.key?(id)
|
|
107
|
-
AaaChainSchema::APPROVALS
|
|
109
|
+
(AaaChainSchema::APPROVALS + native_aaa_keys).uniq
|
|
108
110
|
elsif id == "tacacs.endpoints"
|
|
109
111
|
%w[maximum_targets order syntax_profile local_fallback_access_verified tacacs_key_compatibility_verified]
|
|
110
112
|
else []
|
|
@@ -120,7 +122,8 @@ module Baseline
|
|
|
120
122
|
syntax_profiles = case id
|
|
121
123
|
when "ntp.authentication" then %w[comware_legacy comware7 vrp_s_series]
|
|
122
124
|
when "snmp.write_enabled" then SnmpGroupMigrationSchema::PROFILES
|
|
123
|
-
when "ssh.vty_aaa" then VtyAuthenticationSchema::PROFILES.keys
|
|
125
|
+
when "ssh.vty_aaa" then VtyAuthenticationSchema::PROFILES.keys + [ "nxos" ]
|
|
126
|
+
when *AaaChainSchema::IDS.keys then %w[comware7 nxos]
|
|
124
127
|
else [ "comware7" ]
|
|
125
128
|
end
|
|
126
129
|
unless (!remediation.key?("ntp_peer_key_compatibility_verified") || [ true, false ].include?(remediation["ntp_peer_key_compatibility_verified"])) &&
|
|
@@ -131,7 +134,11 @@ module Baseline
|
|
|
131
134
|
AccessApproval.validate!(remediation["access_targets"], domain: id.start_with?("snmp.") ? :snmp : :management) if remediation.key?("access_targets")
|
|
132
135
|
SyslogReferenceSchema.validate_migrations!(remediation["binding_migrations"]) if remediation.key?("binding_migrations")
|
|
133
136
|
SnmpGroupMigrationSchema.validate_approvals!(remediation["group_migrations"]) if remediation.key?("group_migrations")
|
|
134
|
-
|
|
137
|
+
if remediation["syntax_profile"] == "nxos" && NxosAaaSchema::IDS.key?(id)
|
|
138
|
+
NxosAaaSchema.validate_approval!(remediation, id: id)
|
|
139
|
+
elsif AaaChainSchema::IDS.key?(id) || id == "ssh.vty_aaa"
|
|
140
|
+
AaaChainSchema.validate_approval!(remediation)
|
|
141
|
+
end
|
|
135
142
|
if remediation.key?("vty_ranges") && !VtyAuthenticationSchema.ranges?(remediation["vty_ranges"], syntax_profile: remediation["syntax_profile"])
|
|
136
143
|
raise ArgumentError, "Invalid approved VTY scope"
|
|
137
144
|
end
|
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.9.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Gatework contributors
|
|
@@ -120,6 +120,7 @@ files:
|
|
|
120
120
|
- examples/remediation_huawei_vty_authentication.rb
|
|
121
121
|
- examples/remediation_ntp_authentication.rb
|
|
122
122
|
- examples/remediation_ntp_authentication_switch.rb
|
|
123
|
+
- examples/remediation_nxos_aaa.rb
|
|
123
124
|
- examples/remediation_palo_alto_ntp.rb
|
|
124
125
|
- examples/remediation_palo_alto_syslog.rb
|
|
125
126
|
- examples/remediation_palo_alto_syslog_migration.rb
|
|
@@ -147,6 +148,7 @@ files:
|
|
|
147
148
|
- lib/baseline/cisco/tacacs_groups.rb
|
|
148
149
|
- lib/baseline/cisco_ios/command_renderer.rb
|
|
149
150
|
- lib/baseline/cisco_ios/parser.rb
|
|
151
|
+
- lib/baseline/cisco_nxos/authentication.rb
|
|
150
152
|
- lib/baseline/cisco_nxos/command_renderer.rb
|
|
151
153
|
- lib/baseline/cisco_nxos/parser.rb
|
|
152
154
|
- lib/baseline/command_renderer.rb
|
|
@@ -181,6 +183,9 @@ files:
|
|
|
181
183
|
- lib/baseline/ntp_commands.rb
|
|
182
184
|
- lib/baseline/ntp_remediation.rb
|
|
183
185
|
- lib/baseline/ntp_server_operation_schema.rb
|
|
186
|
+
- lib/baseline/nxos_aaa_methods.rb
|
|
187
|
+
- lib/baseline/nxos_aaa_remediation.rb
|
|
188
|
+
- lib/baseline/nxos_aaa_schema.rb
|
|
184
189
|
- lib/baseline/palo_alto/command_index.rb
|
|
185
190
|
- lib/baseline/palo_alto/command_renderer.rb
|
|
186
191
|
- lib/baseline/palo_alto/document.rb
|