Files
dlp-io/docs/DLP_IO_LIBRARY.md
T
p40000043244@byd.com 316f307034 ci: 3.8/3.9 仅提供纯 Python wheel, CI 矩阵回退 3.10-3.14
Windows runner 未安装 Python 3.8/3.9 (run 258 失败于 Python 3.8 is required)。
requires-python 维持 >=3.8: 3.8/3.9 用户安装 py3-none-any wheel, 功能一致;
pyd wheel 覆盖 runner 预装的 3.10-3.14。
2026-08-03 16:10:06 +08:00

208 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# dlp-io
`dlp-io` 为 Windows DLP 透明加密环境提供接近 `io.open` 的 Python 文件 API。非白名单进程负责业务逻辑;文件读取由已获 DLP 白名单授权的 Python helper 完成,再通过经过认证的 Windows Named Pipe 返回明文字节;文件写入通过通道探测保证落盘为未加密格式。另提供 `is_encrypted()`,在读取前判断文件是否处于 DLP 加密状态。
当前版本为 `dlp-io==0.3.0`distribution 名是 `dlp-io`import 名是 `dlp_io`
DLP 环境的实测特征(白名单矩阵、密文样本字节对比、各写入候选通道的可用性实验)与判定/写入逻辑的完整推导记录见 [DLP_ENVIRONMENT_NOTES.md](DLP_ENVIRONMENT_NOTES.md)。
## 安装
### 方式一:Gitea PyPI registry(推荐)
registry 已开放匿名下载,一条命令即可安装;pip 会自动选择最匹配的 wheelWindows 上 CPython 3.103.14 会得到对应的 pyd wheel(整个库编译为单个 `.pyd`),3.8/3.9 及其余环境得到纯 Python wheel(库要求 Python >= 3.8pyd 版仅覆盖 CI 预装的解释器版本)。
```powershell
py -m pip install --index-url https://gitea.docker.antior.cn/api/packages/antior/pypi/simple dlp-io==0.3.0
```
Package 页面:<https://gitea.docker.antior.cn/antior/-/packages/pypi/dlp-io>
### 方式二:Gitea Release 页面
发布物由 `.gitea/workflows/publish-dlp-io.yaml` 从 immutable tag `dlp-io-vX.Y.Z` 自动构建并上传,全部产物在 CI runner 上编译,不依赖本地环境。
Release 页面:<https://gitea.docker.antior.cn/antior/dlp-io/releases>
每个版本包含以下文件:
- `dlp_io-X.Y.Z-py3-none-any.whl`:纯 Python wheel,适用于任何 CPython >= 3.8 环境。
- `dlp_io-X.Y.Z-cp310-cp310-win_amd64.whl``cp314`pyd wheelWindows 专用,按解释器版本区分;整个库由 Cython 编译为单个二进制 `.pyd`,功能与纯 Python 版完全一致,可按对源码保护的要求选用。3.8/3.9 无 pyd wheel,使用纯 Python wheel。
- `dlp_io-X.Y.Z.tar.gz`sdist 源码包。
- `dlp_io.py``dlp_io.cp310-win_amd64.pyd``cp314`:免安装单文件,与 wheel 内容一致;直接放进项目目录(或加入 `PYTHONPATH`)即可 `import dlp_io`,适合不便使用 pip 的环境。
按解释器版本选择对应资产,可直接用 pip 安装 URL。Windows PowerShell
```powershell
# 纯 Python 版(任意平台)
py -m pip install https://gitea.docker.antior.cn/antior/dlp-io/releases/download/dlp-io-v0.1.1/dlp_io-0.1.1-py3-none-any.whl
# pyd 版(示例为 CPython 3.13,其它版本替换文件名中的 cp313)
py -3.13 -m pip install https://gitea.docker.antior.cn/antior/dlp-io/releases/download/dlp-io-v0.1.1/dlp_io-0.1.1-cp313-cp313-win_amd64.whl
```
也可以先从 Release 页面下载 wheel,再 `py -m pip install <文件路径>`。下载 Release 资产和 registry 包都不需要 token;发布只能在 CI 中通过注入的 `GITHUB_TOKEN` 完成,发布凭据不得写入命令行、URL 或 tracked 文件。
调用方需要安装 `dlp-io`。白名单 Python 无需安装该包:helper 源码由调用方以 UTF-8 通过 stdin 注入,且只依赖 Python 标准库,不会在磁盘创建临时 `.py` 文件。
运行环境必须是 Windows,并且机器上已有一个真正具备 DLP 明文读取权限的 Python。解释器查找顺序为:
1. `DLP_IO_PYTHON`
2. `DATAPACKER_DLP_PYTHON`(兼容 DataPacker
3. `python`
4. `py -3`
生产环境优先把 `DLP_IO_PYTHON` 设置为审批过的 Python 绝对路径。
## io 风格用法
把原来的 `io.open` 或内置 `open` 改成 `dlp_io.open` 即可。纯读取走 helper;写入、追加和独占创建仍使用调用进程的原生文件 API。
```python
from pathlib import Path
import dlp_io
source = Path(r"D:\Protected\input.txt")
output = Path(r"D:\Output\result.txt")
with dlp_io.open(source, "r", encoding="utf-8", newline="") as reader:
text = reader.read()
with dlp_io.open(output, "w", encoding="utf-8", newline="\n") as writer:
writer.write(text.upper())
dlp_io.shutdown()
```
`dlp_io.open()` 保留 `io.open()` 的参数名和位置习惯,包括 `buffering``encoding``errors``newline``closefd``opener`
模式路由规则:
- 路径型 `r``rt``rb`:通过 helper 读取,始终返回明文。
- 路径型 `w``a``x`:经过写入通道路由(见下文「未加密写入」),保证落盘为未加密文件。
- 整数文件描述符或自定义 `opener`:使用原生 `open`
- 路径型 `+` 更新模式:显式抛出 `io.UnsupportedOperation`
- 路径读取配合 `closefd=False`:与标准 API 一样抛出 `ValueError`
- `rb` 配合 `buffering=0`:返回 raw stream;默认返回 buffered reader。
- 文本模式的默认 encoding 与当前 Python `io.open` 一致;跨机器文件请显式传 `encoding="utf-8"`
## 加密状态判断
`is_encrypted(path)` 在读取前判断文件是否处于 DLP 加密状态,返回 `bool`
```python
import dlp_io
if dlp_io.is_encrypted(r"D:\Protected\input.pptx"):
# 已加密:当前进程若是打包的 EXE(非白名单),必须经 helper 中转读取
with dlp_io.open(r"D:\Protected\input.pptx", "rb") as stream:
data = stream.read()
else:
# 未加密:可用任意方式直接读取
with dlp_io.open(r"D:\Protected\input.pptx", "rb") as stream:
data = stream.read()
```
判断原理:DLP 透明加密在磁盘上存密文,白名单进程读到明文、非白名单工具(certutil)读到原始盘内字节;加密文件与未加密文件的 attrib 属性、ADS、大小完全一致,只有内容可区分。`is_encrypted()` 先比较 certutil 原始哈希与当前进程视图哈希:不一致则文件已加密且当前进程是白名单;一致时存在歧义(文件未加密,或文件已加密但当前进程是非白名单 EXE),再比较 helper 视图与当前进程视图的前 64 KiB 定案。两种运行环境(Python/pyd 与打包 EXE)结果都正确。
代价:未加密文件需要一次完整 certutil 哈希、一次本地完整读取和一次 helper 前 64 KiB 读取;大文件请缓存判断结果,不要每次读取前重复调用。helper 未启动时会复用懒加载的默认 session,结束后照常 `dlp_io.shutdown()`。非 Windows 平台直接返回 `False`
## 未加密写入
写入目标始终是「磁盘上保存未加密文件」:
- 打包为 EXE(非白名单进程):原生直写天然落盘明文,选中直写通道。
- Python 调试 / pyd(白名单进程):部分 DLP 策略会加密白名单进程的写入。首次写某个后缀时库会用临时探针文件实测直写是否落盘明文(用 certutil 原始哈希校验);若直写会被加密,自动改走 PowerShell 中转通道——字节经 stdin 管道传给非白名单的 powershell.exe 子进程写盘,不经过任何磁盘临时文件。通道按文件后缀缓存;两个通道都不可用时抛出 `DlpWriteError`,绝不静默写出密文。
可用环境变量跳过探测、强制指定通道(值为 `direct``powershell`):
```powershell
$env:DLP_IO_WRITE_CHANNEL = "powershell"
```
探测按后缀缓存,注意 DLP 策略若按目录区分,探针结果可能与目标目录不同;此时建议用环境变量显式指定通道。
## 配置和生命周期
默认 session 在第一次读取时懒启动,并被后续读取复用:
```python
import dlp_io
dlp_io.configure(
python_executable=r"C:\ApprovedPython\python.exe",
startup_timeout=30,
)
try:
with dlp_io.open(r"D:\Protected\payload.bin", "rb") as stream:
header = stream.read(64)
finally:
dlp_io.shutdown()
```
必须在默认 session 启动前调用 `configure()`。需要更换解释器时,先 `shutdown()`,再重新配置。`shutdown()` 和 session 的 `close()` 都是幂等操作。
## 显式 session
库或服务代码可以显式持有 session,避免全局状态:
```python
import dlp_io
with dlp_io.DlpSession.start(
python_cmd=[r"C:\ApprovedPython\python.exe"],
timeout=30,
) as session:
with session.open(r"D:\Protected\archive.dat", "rb") as stream:
stream.seek(-32, 2)
trailer = stream.read()
```
一个 session 同时只允许一个活动读取 stream。请关闭当前 stream 后再打开下一个;并行读取应由调用方创建彼此独立的 session。
## 兼容既有代码的全局 patch
只有当第三方代码无法逐个改为 `dlp_io.open()` 时,才临时 patch `builtins.open``io.open`
```python
import dlp_io
dlp_io.configure(python_executable=r"C:\ApprovedPython\python.exe")
dlp_io.install_open_patch()
try:
run_legacy_application()
finally:
dlp_io.uninstall_open_patch()
dlp_io.shutdown()
```
在打包入口中,应先 import 完业务模块和本地资源,再安装 patch。patch 只路由路径型纯读模式,重复安装和卸载是幂等的。它不会拦截 `os.open`,也无法拦截 C 扩展内部绕过 Python `open` 的读取。
如果应用自己管理 session,可以把 `session.open_raw` 传给 `install_reader_patch()`
## 错误和安全边界
主要异常均继承 `DlpIoError`
- `DlpConfigurationError`:平台、配置或参数不成立。
- `DlpHelperStartError`:白名单 Python 启动、源码注入或握手失败。
- `DlpProtocolError`:协议版本、控制消息或响应结构无效。
- `DlpTransportError`helper 在显式 EOF 前断开,或文件传输被截断。
- `DlpSessionBusyError`:同一个 session 已有活动 stream。
- `DlpWriteError`:没有任何写入通道能落盘未加密文件,或 PowerShell 中转写入失败。
helper 启动或读取失败时,库会显式报错并且不回退到当前 EXE 直接读取。DLP 直读可能返回合法长度的密文;静默回退会把数据损坏伪装成成功。
版本 1 会在 EOF 和 seek 重连时检测文件 size 的截断或增长,但不提供文件快照,也不能识别读取期间发生的同尺寸内容替换;调用方应避免并发修改输入文件。
每次 session 使用随机 32-byte authkeyAF_PIPE 连接执行 challenge-response 认证;协议控制消息和数据帧都有明确大小边界,零长度数据帧是唯一 EOF。
## 卸载
```powershell
py -m pip uninstall dlp-io
```