拆出软件名录(67 行)、清单语法(114 行)、归档布局与迁移(47 行)、安全描述符(69 行),共 297 行;README 从 653 降到 360 行,每节原位留一句摘要加链接。 为什么拆:这四块是「要查的资料」,README 剩下的是「要读的流程」。混在一起时,想查清单语法的人得先滚过一百多行总览;拆开后每篇也能被单独引用与单独评审。 搬运按标题抽取原文、不重打,并在写之前断言正文长度、写之后再读回断言一次(第一次尝试就是栽在没有断言上:@(a, b, $arr) 不会展开数组,而是把 $arr 拼成一行,结果四篇各只有 5 行、正文却已从 README 删除。那次已回滚)。
4.7 KiB
4.7 KiB
安全描述符(属主 / ACL)
本文从 README.md 拆出,单独成篇是为了让它能被单独引用与单独评审。
问题:归档格式装不下 NTFS 安全描述符 —— 7-Zip 的 -sni(Store NT security information)
官方文档写明「当前版本只能写进 WIM 归档」,.7z 里一个字节的 ACL 都没有。
于是"备份 → 恢复"之后,每个对象的安全描述符都是新建对象的默认值:属主是跑恢复脚本的
那个进程,DACL 是从目标父目录继承来的那一套。
为什么这对 C:\ProgramData 是致命的:那里的目录 ACL 里有
(A;OICIIO;GA;;;CO) CREATOR OWNER + inherit-only + GENERIC_ALL
CREATOR OWNER(S-1-3-0)不是账户,是访问检查时才替换的占位符 —— 替换成
"被检查对象的属主"。所以这句话的真实含义是「谁创建的东西谁有全权」。只回放 ACE 文本、
不恢复属主,等于把里面的"谁"换成了跑恢复脚本的账户,原程序(服务账户 / 专用用户)
反而没了读写权限。真机实测(tools\lab\Lab.ps1 acl-test):
原属主 = S-1-5-18 (NT AUTHORITY\SYSTEM)
恢复后属主 = S-1-5-18 ← 正确恢复(要靠显式启用的 SeRestorePrivilege)
负对照属主 = S-1-5-32-544 ← 只搬文件、不回放安全描述符时,属主落到"跑脚本的账户"
怎么做:
- 备份时把每个对象的 SDDL(
Get-Acl的原文,含O:/G:/D:)写进旁挂文件Backups/<归档名>.acl.json,键是归档内相对路径(目标机器上%UserProfile%和 名录的前缀补全都会变,只有归档内路径两端同源)。 - SDDL 里的 SID 是数值形式,
CO/OW这类占位符原样保留。全程不做账户名解析 —— 名字解析会把占位符映射成当前用户,或者直接抛IdentityNotMappedException, 那正是"权限落到脚本头上"的另一种成因。 - 恢复时在解压之后、对真实目标路径自顶向下回放:父目录先写,子对象的继承才收敛。
原本不
protected的 DACL 只写显式 ACE,其余交给父目录重新继承(保住活继承语义);protected的原样写。 - 写属主要
SeRestorePrivilege,而且必须显式启用:管理员的过滤令牌里它默认是 disabled,Set-Acl/SetAccessControl都不会替你打开。所以恢复要在管理员(或 SYSTEM)下跑, 脚本启动时会明确告警"属主将无法恢复,只能恢复 DACL"。 - 写失败有三级回退:
属主+属组+DACL→属主+DACL→仅 DACL(属组常常是最先失败的那个, 而它对访问判定几乎没影响,不能因为它把属主一起丢掉)。 - 对象的安全描述符读不到(系统目录里很常见)时带错误记账、写进 sidecar 并计入 manifest
的
security.errors,恢复时跳过它并告警 —— 而不是当成"这个对象没有特殊权限"。
配置在 BackupConfig.psd1:
Security = @{
Mode = 'Full' # Off | Full | Smart | Roots
IncludeSacl = $false # 连审计规则(SACL)一起存取,需要 SeSecurityPrivilege
SidMap = @{} # 跨机恢复的 SID 映射:@{ 'S-1-5-21-旧' = 'S-1-5-21-新' }
FailOnError = $false # sidecar 写不出来时,是否把该条目算作失败
}
Full(默认):每个对象都存。正确性优先,几万文件的树 sidecar 几 MB。Smart:只存"继承复现不出来"的对象(protected / 有显式 ACE / 属主属组与父目录不同 / 继承链已脱节)。判据偏保守,但终究是启发式,所以不是默认。Roots:只存每个归档项的根,最省。Off:完全不采集,恢复出来的就是新建对象的默认值。
Restore.ps1 另有 -SkipSecurity 可以只恢复文件内容。
已知取舍(有意为之):
- 归档旁边没有
acl.json的旧归档照常恢复,只是打一行告警说明"属主/ACL 是默认值"。 - 陈旧继承 ACE 会被"冻结":如果某个对象的 DACL 里留着已经没有任何出处的继承 ACE (父目录改过权限、Windows 自己也不会再传播它),那它靠继承复现不出来,只能整套冻结成 显式 ACE 并置 protected —— 这是唯一"既不丢 ACE、也不产生重复 ACE"的做法(实测: 目标上原本就留着那条陈旧 ACE,再补一条显式 ACE 会让同一条 ACE 出现两次)。 代价是这个对象从此不跟随父目录,而它本来就已经跟父目录脱节了。
- ACL 只跟着归档旁边的
acl.json走:搬归档时要把同名的.acl.json一起搬。