把 Samba 共享限制到一个用户和一个 VPN 地址
Samba 的 valid users 和 hosts allow 不会把某个人与某个地址配成一对:两个共享名可以表达这种配对,交叉配对测试也能证明这一点。
在这个设计里,两种不同的权限被混为一谈:谁可以使用某个目录,以及哪些地址可以连接。把账户列表写在来源列表旁边,看起来像是一次配对,但 Samba 把这两个列表当作彼此独立的谓词来求值。任何同时满足两者的人都会被放行,这比多数管理员的本意更宽。修正办法要么是另一种共享布局,要么是一个能够表达这对关系的策略层。
迷思:两个列表合成一条规则
smb.conf 参考文档把 valid users 和 hosts allow 定义为两个互不相关的参数,而把它们同时设在同一个共享上,正是混乱开始的地方。设定 valid users = mira, tomas 和 hosts allow = 10.77.0.21, 10.77.0.22 之后,会有四种组合被放行:米拉可以来自两个地址中的任意一个,托马斯也可以来自两个地址中的任意一个。配置里没有任何东西把第一个名字和第一个地址绑在一起,名字出现的顺序也没有任何含义。重新排序、再加一行 hosts allow,或者每行写一个名字,都不会改变什么。结果是两个列表的乘积,而不是两者之间的映射。
大多数人真正想要的更窄:米拉只能从一个地址来,托马斯只能从另一个地址来。单个共享定义没有表达这种绑定的语法,再聪明的顺序也造不出来。这是 Samba 无法在单个共享内表达的一种策略形态,迷思正是在这里存活下来。
Samba 评估什么,以及按什么顺序
各个阶段按固定顺序执行,顺序错了就会得出错误诊断。客户端打开连接;如果配置了全局主机检查就执行;会话建立对账户进行认证;随后客户端连接某个共享,在这里该共享自己的主机列表、valid users 和只读设置被应用;只有在打开文件时,已认证身份的文件系统权限才作决定。每个阶段都能单独拒绝请求,通过一个阶段并不能说明下一个阶段。重要的后果是:在评估该共享的主机列表时,账户已经确定,而主机列表仍然没有指名这个账户的手段。
这就是为什么一个共享无法把一个用户绑定到一个地址:hosts allow 和 valid users 是在同一树连接阶段应用的两个独立谓词,各自回答自己的问题。主机谓词从不把账户名作为输入,因此无法按用户书写。只有第二个共享,或 Samba 之外的策略层,才能表达这对关系。
全局值之所以重要,是因为继承方式。省略 hosts allow 的共享继承全局值,而设定了自己的值的共享,用该值替代全局值,而不是在其之上叠加。因此宽泛的全局列表会放宽那些从不设定该参数的共享,而对自行定义该值的共享毫无影响。运行 testparm -s 并读取它打印的按共享的值,因为那段输出才是实际生效的策略,磁盘上的文件并不是。
缩小一个共享的范围,对服务器其余部分同样毫无作用。其他共享、打印队列和 IPC 连接各自保留规则,因此依赖单个共享地址列表的设计只保护了一条路径,而不是整个主机。
一个目录上的两个共享名
当同一个目录必须让两个人从两个地址访问时,用不同的名字把它暴露两次。每个配置块带一个账户和一个来源,这就把配对变成了配置能够强制执行的东西。代价是每个人要用不同的共享名去访问同样的文件,而这必须是客户端一侧有意的改动。
[shared-mira]
path = /srv/shared
guest ok = no
read only = yes
valid users = mira
hosts allow = 10.77.0.21
hosts deny = ALL
[shared-tomas]
path = /srv/shared
guest ok = no
read only = yes
valid users = tomas
hosts allow = 10.77.0.22
hosts deny = ALL
共享层的门禁不是文件系统权限。两个身份仍然需要通过底层 ACL 才能访问该目录,只读设置是第二层而不是唯一一层。只有工作流确有必要时才授予写权限,然后检查一个身份创建的文件是否仍可被另一个身份使用。
两个配置块指向同一条路径,所以目录的改动会同时影响两个共享。这通常正是目的,但也意味着一处权限错误会同时暴露给两个用户。
来源地址能证明什么
Samba 看到的地址是到达服务器的地址,而不是客户端自以为拥有的地址。把所有客户端都转换成同一个来源地址的 VPN 网关,抹掉了这一区别:所有客户端都以同一主机身份到达,来源列表再也无法区分它们。为每个客户端提供稳定分配、并且保留来源的网关,是把地址当作策略使用的前提。缺少任何一项,这种限制衡量的是网关,而不是用户。
即便如此,地址也不是设备。同一隧道地址后面的两台笔记本、一个被重新分配的租约,以及一台共用工作站,都推翻了来源能识别某个人这一假设。把这种限制当作网络范围来看待,并保持 已认证的 SMB 账户作为身份层;如果需要设备授权,再加上证书之类的设备凭据。
这也正是该设计把门禁放在共享而不是服务器上的原因。被拒绝的配对仍能打开会话、访问另一个共享,或列出服务器,这与连接时的认证失败是不同的结果。如果要求是在认证之前就拒绝,那么 Samba 的共享参数就是错误的工具。
用交叉配对来验证
先验证配置,然后用全新的连接而不是复用的会话去测试四种组合。预期的配对应当能到达该目录,而两个交叉配对都应当失败。先前测试中缓存的会话会乐于替错误组合作答,让一条坏规则看起来是对的。
testparm -s
smbclient //fileserver/shared-mira -U mira -c 'ls'
smbclient //fileserver/shared-tomas -U tomas -c 'ls'
smbstatus --shares
语法检查只能证明文件能够被解析。凭据、ACL 和主机判定要靠真实连接来检验,所以从你要测试的地址运行客户端命令,观察是哪个阶段拒绝了它。如果某个交叉配对成功,说明实际生效的主机列表比配置块所暗示的更宽,testparm -s 就是首先要读的东西。如果某个交叉配对因为错误的原因失败,那么可疑的是账户或 ACL。
用 smbstatus 监控会话并中止它们,是在访问开始之后采取的响应动作,而不是准入控制。封锁整个被观察到的地址还可能切断同一 NAT 之后无关的用户,所以判定属于共享定义,监控属于响应流程。把这两件事分开,设计对自己强制什么才是诚实的。