identd-ng/pam_authnft

GitHub: identd-ng/pam_authnft

为 Linux 提供基于 PAM 与 nftables cgroup 绑定的按会话防火墙,解决认证会话与网络策略原子化联动问题。

Stars: 2 | Forks: 0

# pam_authnft [![build](https://static.pigsec.cn/wp-content/uploads/repos/cas/05/052bc9eaea1744e860f382f618f1146be38e27dfd499bb29c253bcd1827fad60.svg)](https://github.com/identd-ng/pam_authnft/actions/workflows/build.yml) [![CIFuzz](https://static.pigsec.cn/wp-content/uploads/repos/cas/1d/1d6c57db702530875a76eeae391c681405e59a8289717845a64819a5e9691ed1.svg)](https://github.com/identd-ng/pam_authnft/actions/workflows/cifuzz.yml) [![cppcheck](https://static.pigsec.cn/wp-content/uploads/repos/cas/e3/e39cc26d467a5097e7c0e4f59a9e38476694cb371ee0f5aa2e7ab8a28646c35b.svg)](https://github.com/identd-ng/pam_authnft/actions/workflows/cppcheck.yml) [![CodeQL](https://static.pigsec.cn/wp-content/uploads/repos/cas/53/539e9a6bf48ad24469a4363bff3aa68124154549e26592783d3d8577f2acbbfc.svg)](https://github.com/identd-ng/pam_authnft/actions/workflows/codeql.yml) [![sanitizers](https://static.pigsec.cn/wp-content/uploads/repos/cas/73/73f26308a39a63e9f646cb051c000ce1f400885ef813623d35efff8d548345c7.svg)](https://github.com/identd-ng/pam_authnft/actions/workflows/sanitizers.yml) [![OpenSSF Best Practices](https://www.bestpractices.dev/projects/12496/badge?v=passing)](https://www.bestpractices.dev/projects/12496) [![Coverity Scan](https://scan.coverity.com/projects/pam_authnft/badge.svg)](https://scan.coverity.com/projects/pam_authnft) [![Language: C](https://img.shields.io/badge/language-C-blue.svg)](https://en.wikipedia.org/wiki/C_(programming_language)) [![License: GPL v2+](https://img.shields.io/badge/license-GPL--2.0--or--later-blue.svg)](LICENSE) Linux 没有内置的方法将包过滤规则绑定到已认证的用户会话,并在注销时撤销它们。 pam_authnft 填补了这一空白。规则在注销时通过 `close_session` 移除, 依赖于通过 `pam_set_data` 存储的每个会话的状态。24 小时的元素超时是 一项安全兜底机制,用于应对 `close_session` 完全未执行的情况——例如 daemon 崩溃、OOM kill、kernel panic 或硬重置。 OpenBSD 的 pf 功能已有多年历史——它通过 pfctl 加载按会话划分的 命名 anchor,并在会话结束时将其销毁。pam_authnft 将相同的模型 引入 Linux:nftables 的命名 set 充当 anchor 的等价物,而 systemd transient scope 的 cgroupv2 inode 取代已认证的 shell 作为会话标识。 无需专用 shell,无需 setuid 二进制文件,也无需打 kernel 补丁。

pam_authnft mascot

## 用例 pam_authnft 适用于任何启用 PAM 的服务。以下是最具应用价值的场景: - **SSH 服务器** — 无需包装脚本或 ForceCommand 技巧即可实现 按会话的防火墙策略。用户的 fragment 可以限制出站端口、 固定允许的目标地址,或仅针对该会话启用 masquerade。 过滤适用于用户在其会话内打开的 socket(监听器、出站连接); SSH 控制连接本身则由模块在共享 filter 链中于会话跳转之前添加的 `ct state established,related accept` 规则处理。请参阅 [docs/ARCHITECTURE.txt](docs/ARCHITECTURE.txt) 了解完整的信任模型。 - **VPN 集中器** (WireGuard, OpenConnect, strongSwan) — 绑定到 VPN 的 PAM 认证而不是应用于所有隧道的静态规则集的按隧道包过滤。 - **堡垒机 / 跳板机** — 可审计的按会话网络访问。每个会话元素在 `nft list table inet authnft` 中可见,包含用户名、PID 和可选的 claims 标签,并在 systemd journal 中提供共享的相关性 token 以便 SIEM 关联。 - **RADIUS / TACACS+ 和 OIDC 部署** — `claims_env` 机制可以承载 来自上游 PAM 模块的 AAA 属性或 token 派生的 claims,并将其放入 nftables 元素的 comment 中,从而在认证决策与防火墙规则之间 建立可追溯的关联。 - **位于容器前端的堡垒机** — 用户通过 PAM(SSH、 OIDC)进行认证,pam_authnft 限制该会话可以访问哪些容器 IP 和端口。 这种匹配机制适用于带有支持 namespace 的 cgroupv2 偏移量的内核: 主线版本 >= 6.11,以及 LTS 向后移植版本 6.6 >= 6.6.53 和 6.1 >= 6.1.112 (commit 7f3287db6543,以及随后的 null-deref 修复 7052622fccb1; 均发布于 2024 年 9 月)。RHEL 9 / Rocky 9 / Alma 9 内核(源自 5.14) 不支持此功能。对于 *在* Kubernetes pod 内部(无 PAM 会话)执行策略, BPF cgroup 程序是自然而然的途径——请参阅 [docs/TODO.txt](docs/TODO.txt)。 ## 工作原理 cgroupv2 文件系统为每个 cgroup 目录分配一个唯一的 inode,在 cgroup 的生命周期内保持稳定。当 systemd 通过 D-Bus 为会话创建 transient `.scope` 时,所有会话进程都会落入该 cgroup 下。 该模块将 `{ cgroup_path . src_ip }` 插入到命名的 nftables set 中; kernel 在插入时通过 `socket cgroupv2 level 2` 将路径解析为 inode。 在包分类时刻,nftables 读取 socket 起源的 cgroup——即 socket *被创建* 时所在的 cgroup,而不是其当前拥有的任务所处的 cgroup——并将其与 set 进行匹配,从而在不引用 PID、UID 或 用户名的情况下将防火墙规则绑定到该会话。 会话策略可以使用标准的 `nft` 工具(`nft list table inet authnft`) 进行检查;无需使用 `bpftool` 或进行 BPF 程序检查。 ### 生命周期一览 ``` sequenceDiagram participant U as User (sshd) participant P as PAM stack participant A as pam_authnft participant D as systemd (D-Bus) participant N as nftables (kernel) participant F as filesystem participant J as journald U->>P: open_session P->>A: pam_sm_open_session A->>A: validate PAM_USER, normalize PAM_RHOST A->>A: apply seccomp-BPF allowlist A->>D: StartTransientUnit (authnft--.scope) D-->>A: scope created under authnft.slice A->>A: construct cg_path, per-session chain/set names Note over A,N: nft transaction 1 A->>N: add table + shared filter chain A->>N: add ct state established,related accept (pre-scope sockets) A->>N: add per-session chain A->>N: add 3 per-session sets (_v4, _v6, _cg) A->>N: add element { cg_path . src_ip } in selected set Note over A,N: nft transaction 2 A->>N: add jump rule from filter to per-session chain N-->>A: jump rule handle (stored for cleanup) Note over A,N: nft transaction 3 A->>A: read /etc/authnft/users/, validate ownership A->>A: substitute @session_v4/v6/cg/chain placeholders A->>N: execute substituted fragment A->>F: write /run/authnft/sessions/.json A->>J: AUTHNFT_EVENT=open (correlation token) A-->>P: PAM_SUCCESS Note over U,N: --- session active ---
packets matched by socket cgroupv2 level 2 U->>P: close_session P->>A: pam_sm_close_session A->>N: delete jump rule (by handle) A->>N: flush + delete per-session chain A->>N: delete 3 per-session sets A->>F: unlink session JSON A->>J: AUTHNFT_EVENT=close (same correlation token) A-->>P: PAM_SUCCESS ``` ### 包分类 当一个包进入 kernel 时,nftables 会遍历 `filter` 链。第一条 规则接受已建立/相关的流量(涵盖诸如 SSH 控制连接本身的 scope 前期 socket)。新连接跳转到按会话的链中;该链的规则 会检查该会话自己的按会话 set。每个会话都有其独立的链和 set—— alice 和 bob 由完全不同的规则匹配。 ``` incoming packet │ ▼ ┌──────────────────────────────────┐ │ chain filter │ │ hook input, priority filter - 1 │ ├──────────────────────────────────┤ │ ct state established,related │ ──▶ pre-scope sockets, accept │ jump session_alice_1127936 │ ──▶ alice's per-session chain │ jump session_bob_4321 │ ──▶ bob's per-session chain └─────────────────┬────────────────┘ │ ▼ (alice's session chain) ┌──────────────────────────────────┐ ┌──────────────────────────────────┐ │ chain session_alice_1127936 │ │ set session_alice_1127936_v4 │ ├──────────────────────────────────┤ ├──────────────────────────────────┤ │ socket cgroupv2 level 2 │ lookup │ { "authnft.slice/ │ │ . ip saddr │ ──────▶│ authnft-alice-1127936 │ │ @session_alice_1127936_v4 │ │ .scope" . 192.0.2.1 } │ │ accept │ └──────────────────────────────────┘ │ (loaded from alice's fragment │ │ with @session_v4 placeholder │ │ substituted at open_session) │ └──────────────────────────────────┘ key the set is matched on: socket's originating cgroup (set at socket creation) . packet source IP ``` 会话之间是相互隔离的:alice 的按会话链只引用 alice 的按会话 set,而该 set 仅包含一个元素(她的 cg_path . src_ip)。 在 close_session 时移除该元素,或者完全删除按会话链,都会 立即停止其规则的触发——bob 的链和 set 完全不受影响。 在打开会话时: 1. 规范化 `PAM_RHOST`:IPv4/IPv6 字面量直接通过,去除 zone 后缀, 主机名根据 `rhost_policy` 处理(参见[模块参数](#module-arguments))。 2. 使用 seccomp-BPF 白名单(默认 `SCMP_ACT_KILL`)锁定 PAM 进程。 3. 通过 D-Bus 在 `authnft.slice` 下创建一个命名的 transient `.scope`。 4. 构建 scope 的 cgroupv2 路径(`authnft.slice/.scope`),并将其 连同按会话的链/set 名称、规范化的源 IP 和一个相关性 token 一起 存储在 PAM data 中。 5. 创建一个按会话的链(`session__`)和三个按会话的 set(`_v4`, `_v6`, `_cg`)。根据解析出的 IP 族将一个会话元素 插入到其中一个 set 中。在共享的 `filter` 链中添加一条跳转规则。 6. 验证并加载位于 `/etc/authnft/users/` 且属主为 root 的 用户 fragment,将四个占位符 (`@session_v4`, `@session_v6`, `@session_cg`, `@session_chain`) 替换为当前的按会话名称。 7. 写入 `/run/authnft/sessions/.json`(0644 root:root), 以便非特权观察者能够将 cgroup 关联回所属会话——参见 [docs/INTEGRATIONS.txt](docs/INTEGRATIONS.txt) §5.6。 8. 发出带有相关性 token 的结构化 `AUTHNFT_EVENT=open` journal 条目—— 参见 [docs/INTEGRATIONS.txt](docs/INTEGRATIONS.txt) §6.2。 在注销时,存储的会话状态将从 PAM data 中检索:跳转规则按句柄删除, 按会话链被刷新并删除,三个按会话 set 在单个事务中被删除。会话标识 JSON 被取消链接,并发出带有相同相关性 token 的匹配 `AUTHNFT_EVENT=close` journal 条目。共享的 `filter` 链和 `authnft` 表在会话之间持久存在。 有关完整的生命周期、信任模型和 seccomp 详细信息,请参见 [docs/ARCHITECTURE.txt](docs/ARCHITECTURE.txt)。 ## 集成接口 pam_authnft 的设计刻意保持轻量和可组合。它公开了六个稳定的 接口;[集成契约](docs/INTEGRATIONS.txt) 记录了每一个接口的 MUST/SHOULD 要求和版本控制保证。 | Interface | 它是什么 | 适用对象 | |---|---|---| | **PAM** | 恰好有两个导出符号:`pam_sm_open_session`, `pam_sm_close_session`。读取 `PAM_USER`, `PAM_RHOST`,以及可选的两个环境变量(`claims_env=NAME`, `AUTHNFT_CORRELATION`)。 | PAM 模块作者,发行版打包者 | | **nftables sets** | `table inet authnft` 下每个活跃会话有三个按会话的 set(`session___{v4,v6,cg}`),可通过 `nft list` 查看。 | 防火墙工具、策略引擎 | | **按用户的 fragment** | 位于 `/etc/authnft/users/` 的纯 nftables 语法。可以使用 `include` 来组合共享的组级规则(§4.6)。 | 配置管理(Ansible/Salt/Puppet)、身份代理 | | **systemd** | `authnft.slice` 下的 transient `.scope` 单元。可通过 `systemctl list-units 'authnft-*.scope'` 发现。支持所有 `systemd.resource-control(5)` 指令。 | 编排器、资源核算工具 | | **claims_env** | 可选的 keyring-payload 通道:上游 PAM 模块通过 `add_key(2)` + `pam_putenv(3)` 写入一个标签;pam_authnft 读取、清理并将其嵌入到 nftables 元素的 comment 中。 | AAA/审计集成、身份代理 | | **会话 JSON + journal 事件** | `/run/authnft/sessions/.json` 用于可观测性(§5.6);带有共享 `AUTHNFT_CORRELATION` token 的 `AUTHNFT_EVENT=open/close` journald 记录(§6.2)。 | SIEM 收集器、工作负载调度器、操作面板 | 该模块不是一个插件宿主。它没有共享库 ABI,也没有 回调注册表。每个契约都使用现有的 kernel 或用户空间原语 (PAM env、kernel keyring、文件系统、D-Bus、netlink、journald), 并带有严格的 schema。 ``` flowchart LR subgraph producers["Producers (write)"] IB[Identity broker
OIDC PAM module] CM[Config management
Ansible/Salt/Puppet] OPS[Operator] end subgraph kernel["Kernel + userspace primitives"] KR[Kernel keyring
add_key/keyctl] FS["/etc/authnft/users/
fragments"] ENV[PAM env
claims_env, AUTHNFT_CORRELATION] end subgraph authnft["pam_authnft"] OPEN[pam_sm_open_session] CLOSE[pam_sm_close_session] end subgraph sinks["Consumers (read)"] NFT[nftables sets
nft list] JSON["/run/authnft/sessions/
JSON files"] JRNL[journald
AUTHNFT_EVENT] SCOPE[systemd scopes
systemctl] end subgraph audience["Who reads what"] FW[Firewall tooling] SIEM[SIEM / SOC] ORCH[Orchestrators] end IB -->|claims tag| KR IB -->|correlation token| ENV CM -->|writes| FS OPS -->|writes| FS KR --> OPEN FS --> OPEN ENV --> OPEN OPEN --> NFT OPEN --> JSON OPEN --> JRNL OPEN --> SCOPE CLOSE --> NFT CLOSE --> JRNL NFT --> FW JSON --> SIEM JRNL --> SIEM SCOPE --> ORCH ``` 生产者(左侧)独立于消费者(右侧)。pam_authnft 位于中间, 没有共享库或回调注册表——每个箭头都是记录在案的 kernel 或用户空间原语。 ## 快速开始 ### 前提条件 - Linux kernel >= 5.10,cgroupv2 统一层级 - 带有 D-Bus 的 systemd - nftables >= 1.0.6(`socket cgroupv2` 表达式需要 kernel >= 4.10) - 构建:`gcc`, `make`, `pkg-config` - 库:`libnftables`, `libseccomp`, `libsystemd`, `libcap`, `libaudit`, `pam` ### 构建并安装 ``` make # release build make debug # rebuild with -DDEBUG -g for stderr tracing make man # build pam_authnft(8) manpage (requires pandoc) sudo make install # installs pam_authnft.so, authnft.slice, tmpfiles.d sudo make install-man ``` 将模块安装到 `/usr/lib/security/pam_authnft.so`,将 `authnft.slice` 安装到 `/etc/systemd/system/`,并将用于在引导时 创建 `/run/authnft/sessions/` 的 tmpfiles.d 片段安装到 `/usr/lib/tmpfiles.d/authnft.conf`。 ### 最简工作配置 ``` # 创建 authnft 组(成员受 session firewall 规则约束) sudo groupadd authnft # 向组中添加用户 sudo usermod -aG authnft alice # 为该用户创建 root 拥有的 fragment sudo tee /etc/authnft/users/alice > /dev/null <<'EOF' add rule inet authnft @session_chain socket cgroupv2 level 2 . ip saddr @session_v4 accept EOF sudo chmod 644 /etc/authnft/users/alice ``` 添加到 `/etc/pam.d/sshd`(在 `pam_systemd.so` 之后): ``` session optional pam_authnft.so ``` 在打开会话时,没有有效 fragment 的组成员将被拒绝(并记录到 syslog)。非组成员将直接通过而不受影响。 请查看 `examples/examples_generator.sh -f` 以获取受限端口、masquerade 和限时 fragment 的变体。 ## 配置参考 ### 模块参数 | 参数 | 默认值 | 效果 | |---|---|---| | `host_policy=lax` | ✓ | 如果 PAM_RHOST 解析为 IP 则使用它,否则回退到仅 cgroup 的 set | | `rhost_policy=strict` | | 当 PAM_RHOST 不是可解析的 IP 字面量时拒绝会话(0.2 之前的行为) | | `rhost_policy=kernel` | | 通过 `NETLINK_SOCK_DIAG` 从会话进程自身的 ESTABLISHED TCP socket 推导出对端 IP(参见 `ss(8)`)。如果与 PAM_RHOST 存在差异,则记录警告。查找失败时回退到 `lax` | | `claims_env=NAME` | | 读取 PAM 环境变量 `NAME` 以获取 kernel-keyring 序列号;将经过清理的 keyed payload 嵌入到 nftables 元素的 comment 中。参见 [docs/INTEGRATIONS.txt](docs/INTEGRATIONS.txt) §2 | | `AUTHNFT_NO_SANDBOX=1` | | 禁用 seccomp sandbox。仅用于调试 | ### Kernel keyring 交接 (claims_env) 当设置了 `claims_env=NAME` 时,在同一会话中较早运行的上游 PAM 模块 可以通过 Linux kernel keyring 将会话元数据传递给 pam_authnft。 keyring 是一个由 kernel 管理的键/值存储,其作用域限定于 登录会话——参见 `keyrings(7)`。它不是文件、socket 或共享内存: kernel 分配 key,强制执行权限,并在会话结束时自动将其销毁。 ``` sequenceDiagram participant U as Upstream PAM module
(producer) participant K as Kernel keyring participant E as PAM env participant A as pam_authnft
(consumer) participant N as nftables Note over U,A: Both modules run in the same PAM session,
producer earlier in the stack than consumer U->>K: add_key("user", "", payload, SESSION_KEYRING) K-->>U: serial number U->>K: keyctl(SET_TIMEOUT, serial, ttl) U->>K: keyctl(SETPERM, POSSESSOR view/read/search) U->>E: pam_putenv("NAME=") Note over K: claims live in kernel,
UID-locked, TTL-bounded A->>E: pam_getenv("NAME") E-->>A: "" A->>K: keyctl(READ, serial) K-->>A: payload bytes A->>A: sanitize payload to safe charset A->>N: insert element with claims as comment ``` 生产者要求(key 类型、权限、payload 格式、 在 `SETPERM` 之前执行 `SET_TIMEOUT` 的顺序)记录在 [docs/INTEGRATIONS.txt §2](docs/INTEGRATIONS.txt) 中。pam_authnft 将 payload 视为不透明的可打印 ASCII——它不解析结构,只进行 清理和嵌入。这使得契约保持严谨,并允许任何生产者 (身份代理、AAA 栈、自定义模块)无需共享代码即可参与。 为什么选择 keyring 而不是仅仅使用文件或环境变量: - **生命周期由 kernel 管理。** 当会话结束时,keyring 被销毁,claims 随之消失。无需清理代码。 - **在 kernel 级别进行 UID 锁定。** 主机上的其他进程无法 读取其他会话的 claims,即使是 root 用户,如果不首先获取 该 keyring(POSSESSOR 检查)也无法读取。 - **没有文件系统足迹。** 无需写入、同步或取消链接。不会 产生竞态条件,崩溃时也不会留下残留状态。 - **在 sshd privsep fork 中依然存活。** 与 shell 环境变量不同,kernel keys 在 sshd 内部发生的 auth-worker → session-worker 过渡中 依然保持可读。 ### PAM 栈选项 选项 A — 模块内部检查组成员身份;非组成员直接通过: ``` session optional pam_authnft.so ``` 选项 B — PAM 根据组成员身份进行控制。没有有效 fragment 的成员将被 拒绝;非组成员将完全跳过该模块: ``` session [success=1 default=ignore] pam_succeed_if.so user notingroup authnft quiet session required pam_authnft.so ``` ### 按用户的 fragment 每个组成员都需要 `/etc/authnft/users/`,其属主为 root 且对 全局不可写。在加载之前,模块会对 fragment 路径调用 `stat(2)` 并 拒绝它,除非 `st_uid == 0` 并且全局可写位被清除——这与 `/etc/nftables.conf` 和 sudoers include 所使用的信任模型相同。 该 fragment 在顶层被包含,并作为 nftables 命令运行。 一个 fragment 可以使用 nftables 的 `include` 指令从其他文件中 提取共享规则——例如,位于 `/etc/authnft/groups/` 下、被属于 该组的每个用户引用的组级 fragment。 libnftables 会以传递方式解析 include。pam_authnft 仅对顶层的按用户 fragment 强制执行属主和模式;管理员需对每个传递包含的 文件的权限负责。请参阅 [docs/INTEGRATIONS.txt](docs/INTEGRATIONS.txt) §4.6 以了解组合 模式、安全说明和循环检测指南。 ### 会话打开后的 nftables 状态 ``` # nft list table inet authnft table inet authnft { set session_alice_1127936_v4 { typeof socket cgroupv2 level 0 . ip saddr flags timeout elements = { "authnft.slice/authnft-alice-1127936.scope" . 192.0.2.1 timeout 1d expires 23h55m56s comment "alice (PID:1127936)" } } set session_alice_1127936_v6 { typeof socket cgroupv2 level 0 . ip6 saddr flags timeout } set session_alice_1127936_cg { typeof socket cgroupv2 level 0 flags timeout } chain filter { type filter hook input priority filter - 1; policy accept; ct state established,related accept jump session_alice_1127936 } chain session_alice_1127936 { socket cgroupv2 level 2 . ip saddr @session_alice_1127936_v4 accept } } ``` 注意:`nft list` 将 set 类型规范化为 `level 0`;而规则 保留了配置的 `level 2`。这是预期的 nftables 行为—— level 是规则表达式的属性,而不是 set 类型的属性。 设置了 `claims_env=NAME` 并且栈中较早的模块生成了有效的 keyring 条目时, 元素的 comment 会扩展经过清理的 payload: ``` elements = { "authnft.slice/authnft-alice-1127936.scope" . 192.0.2.1 timeout 1d comment "alice (PID:1127936) [audit-session:7f3e9a]" } ``` 引号中的路径是 `authnft.slice` 下会话的 cgroupv2 scope。 kernel 在插入时会将其解析为 cgroupv2 inode。在包分类 时刻,`socket cgroupv2 level 2` 会读取 socket 的起源 cgroup 并将其与 set 匹配——从而在不引用 PID、UID 或用户名的 情况下将防火墙规则绑定到会话。24 小时的超时是一项安全兜底机制; 在注销时显式删除才是主要的清理手段。 ### 运行时可观测性(会话 JSON + 审计事件) 除了上述 nftables 状态之外,pam_authnft 还通过两个互补的 带外通道发布会话状态: - **`/run/authnft/sessions/.json`** — 一个按会话的 JSON 文件 (0644 root:root),在打开时写入,在关闭时移除,包含带有版本号的 schema(`v=2`),其中含有 user、cgroup path、remote IP、fragment path、 claims tag、scope unit、correlation token 以及 RFC 3339 打开时间戳。 该目录由 `/usr/lib/tmpfiles.d/authnft.conf` 在引导时创建; 来自失败关闭路径的孤立文件将在 7 天后被清理。完整 schema 见于 [docs/INTEGRATIONS.txt](docs/INTEGRATIONS.txt) §5.6。 - **结构化 journald 审计事件** — 会话打开时的 `AUTHNFT_EVENT=open` 和关闭时的 `AUTHNFT_EVENT=close`,两者均处于 `SYSLOG_IDENTIFIER=pam_authnft` 之下,携带一个共享的 `AUTHNFT_CORRELATION` token,允许 SIEM 将这两个事件关联起来(并且, 按照惯例,也可以关联产生相同 token 的上游认证事件)。 上游 PAM 模块通过 `pam_putenv(pamh, "AUTHNFT_CORRELATION=")` 播种相关性。 完整字段 schema 见于 [docs/INTEGRATIONS.txt](docs/INTEGRATIONS.txt) §6.2。 这两个接收器都采用 fail-open 策略:写入失败会记录为 LOG_WARNING,但不会 拒绝会话。 ### systemd 控制 由于每个会话都存在于一个命名的 `.scope` 单元中,因此可以使用完整的 systemd 资源控制和沙箱机制——`man systemd.resource-control(5)`。`data/authnft.slice` 中的所有设置 均已被注释掉;请根据需要取消注释。 **出站网络策略** — 通过 systemd 的 cgroup-BPF 集成执行, 独立于 nftables: ``` IPAddressDeny=any IPAddressAllow=10.0.0.0/8 SocketBindDeny=ipv4:tcp:1-1023 SocketBindDeny=ipv6:tcp:1-1023 ``` **Syscall 和 capability 限制** — 在创建时应用于 scope 中的所有 进程: ``` SystemCallFilter=@system-service SystemCallErrorNumber=EPERM NoNewPrivileges=yes CapabilityBoundingSet= RestrictNamespaces=yes ``` ## 测试 ``` make test # unit tests, no root needed make test-integration # pamtester + valgrind, requires root ``` 容器工作流(推荐——无主机变更,仅需 `podman`): ``` make test-container # unit suite, 13 stages, CAP_NET_ADMIN make test-integration-container # pamtester end-to-end + valgrind make trace-container # seccomp allowlist trace make test-musl # unit suite built against musl (Alpine) ``` 有关单元 + 集成阶段的矩阵(阶段 0–13 和 10.1–10.24) 以及 CI 门禁清单,请参阅 [docs/CONTRIBUTING.txt](docs/CONTRIBUTING.txt) § Tests。 ## 局限性 - 仅支持 cgroupv2 统一层级;未经测试的混合设置。 - 强依赖 systemd;不支持非 systemd init。 - Fragment 的语法错误会在加载时被捕获并记录;语义错误 由管理员负责。 - 如果在注销时清理失败(例如 nftables 不可用),set 元素将通过 插入时的安全兜底超时在 24 小时后过期。会话 JSON 孤立文件将由 systemd-tmpfiles 在 7 天后清理。 - cgroup 路径是在 `open_session` 时根据 scope 单元名称确定性地 构建的。`socket cgroupv2` 匹配适用于在会话 scope 内创建的 socket;在创建 scope 之前存在的 socket(例如 SSH 控制连接)携带其原始 cgroup,因此不参与匹配。该模块将 `ct state established,related accept` 添加到共享的 `filter` 链 以处理 scope 之前的流量。 - pam_authnft 不会对传递包含的 fragment 验证 属主或模式。管理员必须确保每个包含的文件均属主为 root 且 不对全局可写。 ## 稳定性与路线图 **当前稳定** — PAM 接口(恰好两个导出符号),三个 nftables set 类型及其 schema,fragment 属主模型 (`st_uid == 0`,不可全局写入),记录在 [INTEGRATIONS.txt §6.1](docs/INTEGRATIONS.txt) 中的元素 comment 语法, 会话标识 JSON schema(`v=2`, §5.6),结构化 journald 审计字段(§6.2), 以及 Linux audit-syscall 通道 (带有原因标签 `missing | perms | content | nft-syntax` 的 `AUDIT_USER_ERR`,§6.2.7)。 **1.0 版本之前可能更改** — `claims_env` 线路格式细节、 `rhost_policy=kernel` NETLINK 内部机制、`authnft.slice` 附带的默认值。 **已规划** — OSS-Fuzz 注册(项目文件暂存于 [infra/oss-fuzz/](infra/oss-fuzz/),提交受限于项目年龄), fragment linter(包装 libnftables 试运行),可插拔 fragment 来源,为 Arch (AUR) 和 Debian 提供打包。完整列表请参见 [docs/TODO.txt](docs/TODO.txt)。 ## 项目文档 | 文档 | 内容 | |---|---| | [docs/ARCHITECTURE.txt](docs/ARCHITECTURE.txt) | 生命周期、信任模型、会话标识、seccomp 设计、会话标识文件、审计事件 | | [docs/INTEGRATIONS.txt](docs/INTEGRATIONS.txt) | 针对生产者和消费者的稳定契约:PAM 栈(§1)、claims_env keyring(§2)、nft fragment 组合(§4.6)、systemd scope(§5)、会话 JSON(§5.6)、结构化 journal 事件(§6.2)、Linux audit-syscall 事件(§6.2.7) | | [docs/CONTRIBUTING.txt](docs/CONTRIBUTING.txt) | 构建、布局、不变式、风格、测试过程、seccomp 白名单推导 | | [docs/TODO.txt](docs/TODO.txt) | 近期、中期和推迟的工作项 | | [docs/DOC_CHECKLIST.txt](docs/DOC_CHECKLIST.txt) | 按变更类型的文档更新矩阵 | | [docs/THIRD_PARTY.md](docs/THIRD_PARTY.md) | 权威的依赖清单:许可证、版本下限、安全提要 | | [docs/INCIDENT_RESPONSE.md](docs/INCIDENT_RESPONSE.md) | 处理安全报告的内部运行手册(公开政策见 [SECURITY.md](SECURITY.md)) | | [docs/SECURITY_PRACTICES.md](docs/SECURITY_PRACTICES.md) | 项目中每个安全工具、文档、目标和里程碑的单页概览 | | [docs/REPRODUCIBLE_BUILDS.md](docs/REPRODUCIBLE_BUILDS.md) | 我们提供的可重现性是什么,我们不提供什么,以及打包者如何验证发布产物 | | [SECURITY.md](SECURITY.md) | 漏洞范围和报告程序 | ## 许可证 GPL-2.0-or-later。详情请参阅 [LICENSE](LICENSE)。 每个源文件都带有 `SPDX-License-Identifier: GPL-2.0-or-later` 标签。接收者可以在 GPL-2.0 的条款下重新分发和/或修改本软件,或者, 由其自行选择,Free Software Foundation 发布的任何更高版本。 Copyright (C) 2025-2026 Avinash H. Duduskar.
标签:Cgroupv2, Linux, nftables, PAM, 客户端加密, 网络防火墙, 身份认证