️This article has been over 2 years since the last update.
很多版本管理工具都基于ACL Path实现了管理权限,本文加以综述介绍。
分析方法论
在权限设计讨论中,难度最高的就是关系和颗粒度
- 继承与优先级关系:即 inheritance 和 priority 的问题 ,比如精确匹配优先 most specific path always matches first,还是最短优先(比如正则表达式)
- 颗粒度:基于路径的读写即可。
本文对主流的SVN/Artifactory/NAS三种ACL模型进行分析。
背景技术介绍
什么是WebDAV
WebDAV可以被简单地被理解为基于HTTP的网盘,可以将一个HTTP Endpoint挂载到本地机器上读写。在日常生活中,常见的比如坚果云、日历等同步服务,在IT领域,有Artifactory/Subversion/S3等兼容服务,常见的Nginx、Apache、Caddy、Traefik、SVNKit服务均支持WebDAV插件。
同时,它与传统的NFS也有差距,本质是对象存储,比如不支持chmod,性能肯定不如Block储存。
WebDAV的权限模型
在RFC规范中,它是一种基于HTTP XML的定义。是基于目录-用户-RW操作
的ACL实现,没有RBAC(角色)的概念。
- 在最主流实现的Apache(mod_dav)中,采用了XML来定义URL目录资源和用户。
- 在Artifactory中,采用了基于JSON的PermissionTarget纯Java实现。
- 在SVN中,采用了authz的文件实现,这个文件需要同时被Apache的httpd.conf读取到;SVN仓库本身的authz是可以非必要不操作的。
SVN的鉴权方式
虽然SVN是一种很老的工具,但是传统领域中仍然有庞大的存量用户。本文对SVN的权限模型进行介绍。SVN支持Socket协议和HTTP(S)协议(基于WebDAV),但是由于安全与兼容性,管理员更加偏好使用HTTP实现。本文只介绍HTTP的实现,即svn不开启socket,而通过apache代理静态源码文件的方案(即SVN侧没有任何守护进程)。
SVN权限权限拦截器的实现思路
- 基于Apache mod_dav_svn,写一堆XML,但是需要同步authz文件,和文件系统绑死了,同时它是C语言项目,天天爆出漏洞,可想而知源码中有大量的祖传加固代码。
- 定制开发,可以参考SVNKit/scm-manager的DAVServlet,自己开发filter,测试的难度高,但是高度定制,可以纯Java实现。
- 定制开发,也可以参考其它云原生的middleware,或者策略引擎(比如OPA),进行网络层的ACL
SVN的Authn
这里即证明你是你,一般在Apache侧采用集成LDAP实现,直接Basic auth即可。
SVN的Authz文件格式介绍
这个文件本质上和AWS的JSON权限配置是一样的,唯一不同是历史非常悠久。它的语法类似toml
1 | [groups] |
这个配置文件设计的非常糟糕,体现在
- 群组groups与路径path共用一个语义
- 蕴含的exclude语义没有体现,即
* =
表示禁止任何人访问;优先级也没有体现 - 非常难测试,搭建依赖非常繁琐
它的源码位置位于 libsvn_repos/authz.c 的 svn_repos_authz_check_access,但是足足有1800的C代码,测试用例4500行,基本上是一个迷你的规则引擎了,所以分析源码不是上策。
Artifactory的权限模型
Artifactory是DevOp业界知名二进制仓库托管平台,它内部有一套简洁的ACL模型(可以扩展为RBAC),以下为OSS版的研究。个人认为这个项目的Authn和Authz非常完善,比SpringSecurity等框架有更好的实践。
Artifactory的Authn
AuthN即证明你是你。通过如下方法,采用编码方案(而非AOP/注解)在filter或者controller中实现
1 | // 来自Spring security框架 |
关键类:
- Authentication: 证明你是你,并录入缓存中。
- SecurityContext:缓存,可以是ThreadLocal或者redis等,本场景中是ThreadLocal。
这里处理了LDAP/BasicAuth/Token等各种接入方案,但是它没有接入集中式的Session方案,相反从当前开源代码中猜测,收费版可能是转发Header/Token/License的方案实现的。
Artifactory的Authz
全局与仓库:基于SpringSecurity的
@RolesAllowed
注解的模型,判断当前用户是管理员还是普通用户,一个简单的boolean存储,这个is_admin
是直接扔到用户表中的,因此它不是重点。仓库内:基于Ant-style pattern实现的权限规则引擎,一般落地时以仓库key+include为主键。
同样pathkey下,exclude的优先级更高
假如有如下仓库文件
1 | /aaa |
我希望让某个用户只能读写某个路径,那么可以配置如下
1 | # includes |
这样用户就只能请求到符合要求的url
当需要判断权限时,比如当用户下载某个目录下的文件时,将调用如下服务
1 | org.artifactory.api.security.AuthorizationService#canRead(org.artifactory.repo.RepoPath) |
最核心的代码在
1 | org.artifactory.security.SecurityServiceImpl#permissionCheckOnAcl |
它是一个主要为两个for循环的方法,伪代码如下
1 | boolean check(Repo repo, String dir){ |
它通过牺牲空间避免了树的深度遍历,缺点就是target干涉太强了,导致excludes基本上就是废材。假如一个仓库有2个PermissionTarget,一个同意通过,一个被否决,最终eval的结果仍然是通过,导致exclude不生效。
三者测试对比
我们拿它与常见的工具进行对比
Artifactory场景(全局默认为禁止访问)
child dir | parent dir | 实现方案 | 子目录最终支持效果 |
---|---|---|---|
ro | rw | 需要子节点配置exclude | ro(只能做到叶子目录级别,否则需要递归) |
rw | ro | 不涉及覆盖 | rw |
ro | default | 不涉及覆盖 | ro |
rw | default | 不涉及覆盖 | rw |
SVN场景(全局默认为禁止访问)
child dir | parent dir | 实现方案 | 子目录最终支持效果 |
---|---|---|---|
ro | rw | 子节点覆盖父配置 | ro |
rw | ro | 子节点覆盖父配置 | rw |
ro | default | 不涉及覆盖 | ro |
rw | default | 不涉及覆盖 | rw |
NAS场景(全局默认为禁止访问 )
child dir | parent dir | 实现方案 | 子目录最终支持效果 |
---|---|---|---|
ro | rw | 子目录chmod 500 | ro |
rw | ro | 子目录chmod 700 | rw |
ro | default | 不涉及覆盖 | ro |
rw | default | 不涉及覆盖 | rw |
综上,策略排序算法如下
child dir | parent dir | SVN | Artifactory | NFS |
---|---|---|---|---|
ro | rw | ro | ro(limited) | ro(500) |
rw | ro | rw | rw | rw(700) |
ro | default | ro | ro | deny |
rw | default | rw | rw | deny |
结论如下
- SVN:路径先从长到短排序,找到第一个通过为止,类似回溯法的后续遍历。
- Artifactory:两层for循环,第一层AnyMatch;第二层excludes一票否决,再任意匹配includes,是贪心方案。
- NFS:访问子目录必须支持父目录,也是贪心方案
Node的元数据管理的系统设计
在最终落地时,需要用Postgres等数据去存储path的key-value数据(比如tag标记),主要有两个方案
- 通过hstore/json存储数据,但是产生了供应商锁定
- 通过node_prop的外键存储方案,更新与索引比较麻烦,比如Artifactory就设计了4个索引
经过对比,大容量(200M+)项目比如sonarqube/Artifactory的表结构均采用了传统外键的方案,而且这类项目均是高读写比的类型,建议采用传统方案。
附录
Artifactory的替代方案
假如你的场景仅仅是当作存储盘,而不会高度定制元数据,那么分布式存储是更好的方案。比如Lustre或者MinIO等,它们本身也有基础的ACL、挂载和replicate能力。但是NFS协议的Authn是基于客户端的UID实现,比较弱
Artifactory的挂载能力
Artifactory提供基础的WebDAV挂载能力,但是它需要单一凭证,无法实现类似NFS的按用户的LDAP AutoFS挂载能力。协议本身不支持文件属性,无法进行chmod。
Artifactory的Masterkey方案
它采用AES对称加密,初始安装时,masterKey与数据库口令等均通过配置文件明文保存,启动后此文件会被自动加密。如果需要更安全,可以移除masterKey,但是重启时需要修改配置文件并重新录入masterKey。目前看起来不支持定期CredentialRotation的功能,因此需要reset后重新加密,将涉及到停机。
关于SpringSecurity
研究了多款知名框架后,真正按照它默认注解模型的项目几乎没有,而全部是高度自定义+手写的方案,可以使用其中的SecureContext等模型,但是它的注解的确与configure的确反人类。