feat: Agent 支持 etcdctl/etcdutl 诊断命令
etcd 是 K8S 控制平面的关键组件,缺少 etcd 诊断会导致很多故障无法 继续排查。本次将 etcdctl 和 etcdutl 纳入命令白名单: 只读 (自动执行): - etcdctl endpoint status/health/hashkv - 端点健康 - etcdctl member list - 成员列表 - etcdctl alarm list - 告警 - etcdctl auth/user/role status|list - 认证与权限查看 - etcdctl check perf/datascale - 性能检查 - etcdctl get/watch - 读取 key - etcdctl version - etcdutl snapshot status/hash - 快照检查 - etcdutl version 写操作 (人工审批): - etcdctl put/del/compact/txn - 数据修改 - etcdctl snapshot save - 保存快照 - etcdctl member add/remove/update - 成员管理 - etcdctl alarm disarm - 解除告警 - etcdctl auth enable/disable - 认证开关 - etcdctl user/role add/delete/grant/revoke - 权限管理 - etcdutl snapshot restore - 恢复快照 实现细节: - etcdctl/etcdutl 命令支持全局 flag (--endpoints/--cacert 等在子命令前) - 两段式命令按 (verb, subverb) 组合精确分类,避免 member list (只读) 和 member remove (写) 混淆 - 新增 12 个 etcd 命令分类测试,全部 56 个测试通过
This commit is contained in:
@@ -102,6 +102,71 @@ class ShellCommandPolicyTest(unittest.TestCase):
|
||||
classify_shell_command("cat /etc/shadow")
|
||||
|
||||
|
||||
class EtcdCommandPolicyTest(unittest.TestCase):
|
||||
def test_etcdctl_endpoint_status_is_read_only(self):
|
||||
decision = classify_shell_command("etcdctl endpoint status")
|
||||
self.assertEqual(decision.mode, "read")
|
||||
self.assertFalse(decision.requires_approval)
|
||||
|
||||
def test_etcdctl_with_global_flags_still_classifies(self):
|
||||
decision = classify_shell_command(
|
||||
"etcdctl --endpoints=https://127.0.0.1:2379 --cacert=/etc/ssl/ca.pem endpoint health"
|
||||
)
|
||||
self.assertEqual(decision.mode, "read")
|
||||
self.assertFalse(decision.requires_approval)
|
||||
|
||||
def test_etcdctl_member_list_is_read_only(self):
|
||||
decision = classify_shell_command("etcdctl member list")
|
||||
self.assertEqual(decision.mode, "read")
|
||||
self.assertFalse(decision.requires_approval)
|
||||
|
||||
def test_etcdctl_get_and_version_are_read_only(self):
|
||||
for command in ("etcdctl get /kubernetes", "etcdctl version", "etcdctl alarm list"):
|
||||
with self.subTest(command=command):
|
||||
decision = classify_shell_command(command)
|
||||
self.assertEqual(decision.mode, "read")
|
||||
self.assertFalse(decision.requires_approval)
|
||||
|
||||
def test_etcdutl_snapshot_status_is_read_only(self):
|
||||
decision = classify_shell_command("etcdutl snapshot status /tmp/etcd.db")
|
||||
self.assertEqual(decision.mode, "read")
|
||||
self.assertFalse(decision.requires_approval)
|
||||
|
||||
def test_etcdctl_put_requires_approval(self):
|
||||
decision = classify_shell_command("etcdctl put key value")
|
||||
self.assertEqual(decision.mode, "write")
|
||||
self.assertTrue(decision.requires_approval)
|
||||
|
||||
def test_etcdctl_member_remove_requires_approval(self):
|
||||
decision = classify_shell_command("etcdctl member remove abc123")
|
||||
self.assertEqual(decision.mode, "write")
|
||||
self.assertTrue(decision.requires_approval)
|
||||
|
||||
def test_etcdctl_snapshot_save_requires_approval(self):
|
||||
decision = classify_shell_command("etcdctl snapshot save /tmp/etcd.db")
|
||||
self.assertEqual(decision.mode, "write")
|
||||
self.assertTrue(decision.requires_approval)
|
||||
|
||||
def test_etcdctl_compact_requires_approval(self):
|
||||
decision = classify_shell_command("etcdctl compact 12345")
|
||||
self.assertEqual(decision.mode, "write")
|
||||
self.assertTrue(decision.requires_approval)
|
||||
|
||||
def test_etcdctl_auth_enable_requires_approval(self):
|
||||
decision = classify_shell_command("etcdctl auth enable")
|
||||
self.assertEqual(decision.mode, "write")
|
||||
self.assertTrue(decision.requires_approval)
|
||||
|
||||
def test_etcdutl_snapshot_restore_requires_approval(self):
|
||||
decision = classify_shell_command("etcdutl snapshot restore /tmp/etcd.db")
|
||||
self.assertEqual(decision.mode, "write")
|
||||
self.assertTrue(decision.requires_approval)
|
||||
|
||||
def test_etcdctl_rejects_unknown_subcommand(self):
|
||||
with self.assertRaises(ValueError):
|
||||
classify_shell_command("etcdctl bogus command")
|
||||
|
||||
|
||||
class AgentCommandDispatchTest(unittest.TestCase):
|
||||
def test_dispatches_kubectl_command_to_kubectl_classifier(self):
|
||||
decision = classify_agent_command("kubectl get pods -A")
|
||||
|
||||
Reference in New Issue
Block a user