Erlang 权限分离
Erlang Privilege Separation
我正在从事一个基于 Erlang 的面向安全的项目。此应用程序需要访问仅限于 root 或其他特权用户的系统的某些部分。目前,该项目仅适用于 Unix/Linux/BSD 系统,不应更改文件(只读访问)。
我已经考虑(并测试)了其中的一些解决方案,但是,我不知道我应该用 Erlang 做什么。最糟糕的是什么?哪个最好?什么是最容易维护的?
谢谢!
1 个节点(作为根)
这个解决方案是最糟糕的,即使在测试服务器上我也想避免它。
_____________________________
| |
| (root) |
| ___________ _______ |
| | | | | |
| | Erlang VM |<---| Files | |
| |___________| |_______| |
|_____________________________|
你可以在这里看到我目前不想要的东西的大图。
#!/usr/bin/env escript
main([]) ->
ok;
main([H|T]) ->
{ok, Data} = file:read_file(H),
io:format("~p: ~p~n", [H,Data]),
main(T).
运行 它作为 root,瞧瞧。
su - root
${script_path}/readfile.escript /etc/shadow
1 个节点(作为 root)+ 1 个节点(作为受限用户)
我需要启动 2 个节点,一个 运行ning 以 root 或另一个特权用户身份启动,另一个 运行ning 节点具有受限用户,可以从外部世界轻松访问。这种方法工作得很好,但有很多问题。首先,由于连接节点之间的远程过程调用,我无法使用标准 Erlang 分布式协议连接到特权用户节点(受限节点可以在特权节点上执行任意命令)。我不知道 Erlang 是否真的可以在执行它们之前过滤 RPC。其次,我需要在一台主机上管理两个节点。
________________ ____________________________
| | | |
| (r_user) | | (root) |
| ___________ | | ___________ _______ |
| | | | | | | | | |
| | Erlang VM |<===[socket]===>| Erlang VM |<---| Files | |
| |___________| | | |___________| |_______| |
|________________| |____________________________|
在下面的例子中,我将启动两个Erlang shell。第一个 shell 将处于受限模式:
su - myuser
erl -sname restricted -cookie ${mycookie}
第二个将 运行 与特权用户:
su - root
erl -sname privileged -cookie ${mycookie}
标准 Erlang RPC(安全性不够)
最后,在受限节点上(通过本例中的 shell),我可以访问所有文件:
{ok, Data} = rpc:call(privileged, file, read_file, ["/etc/shadow"]).
用"Firewall"方法
I'm using local unix socket in this example, supported only until R19/R20.
Restricted user need to have access to this socket, stored somewhere in
/var/run
.
1 个节点(作为受限用户)+ 外部命令(使用 sudo)
我授予Erlang VM进程使用sudo执行命令的权利。我只需要执行特定程序,获取标准输出并解析它。在这种情况下,我需要使用系统中现有的可用程序或创建一个新程序。
________________ _______________________
| | | |
| (r_user) | | (root) |
| ___________ | | ______ _______ |
| | | | | | | | | |
| | Erlang VM |<===[stdin]===>| sudo |<---| Files | |
| |___________| | | |______| |_______| |
|________________| |_______________________|
1 个节点(作为受限用户)+ 端口 (setuid)
我创建了一个带有 setuid 标志的端口集。该程序现在有权读取文件,但我需要将它放在服务器上的安全位置。如果我想让它动态化,我还应该在 Erlang VM 和这个端口之间定义一个严格的协议。 IMO,setuid 很少是一个好的答案。
________________ ________________________
| | | |
| (r_user) | | (root) [setuid] |
| ___________ | | _______/ _______ |
| | | | | | | | | |
| | Erlang VM |<===[stdin]===>| ports |<---| Files | |
| |___________| | | |_______| |_______| |
|________________| |________________________|
1 个节点(作为受限用户)+ NIF
我不认为我可以在 Erlang VM 中赋予 NIF 特定的权利,也许使用辣椒或其他 non-portable/OS-specific 内核功能。
_______________
| |
| (r_user) |
| ___________ |
| | | |
| | Erlang VM | |
| |___________| |
| | | |
| | NIF | |
| |___________| | _______
| | | | | |
| | ??? |<---| Files |
| |___________| | |_______|
|_______________|
1 个节点(作为受限用户)+ 1 个守护进程(作为 root)
我可以创建一个守护进程 运行ning 作为 root,使用 Unix 套接字或其他方法连接到 Erlang VM。这个解决方案有点像带有 sudo 的端口或外部命令,除了我需要管理一个具有特权的长期守护进程。
________________ _________________________
| | | |
| (r_user) | | (root) |
| ___________ | | ________ _______ |
| | | | | | | | | |
| | Erlang VM |<===[socket]==>| daemon |<---| Files | |
| |___________| | | |________| |_______| |
|________________| |_________________________|
自定义 Erlang 虚拟机
OpenSSH and lot of other secure software 运行s 以 root 身份创建 2 个带有管道的互连进程。当以 root 身份启动 Erlang VM 时,会生成 2 个进程,一个以 root 身份,另一个以受限用户身份。当某些操作需要 root 权限时,受限进程向 root 进程发送请求并等待其答复。我想这是目前更复杂的解决方案,而且我没有掌握足够的 C 和 Erlang VM 来使这个东西运行良好。
______________ _______________
| | | |
| (root) | | (r_user) |
| __________ | | ___________ |
| | | | | | | |
| | PrivProc |<===[pipe]===>| Erlang VM | |
| |__________| | | |___________| |
|______________| |_______________|
从安全角度来看,您最好的选择是尽量减少代码的数量和复杂性 运行 root 权限。因此,当您 运行 整个 Erlang VM 作为 root 时,我会排除所有选项 - 那里的代码太多而无法安全地锁定它。
只要您只需要读取一些文件,最好的选择是使用 sudo
从 Erlang VM 运行 编写一个小的 C 程序。这个程序所要做的就是为你打开文件,并通过 Unix 套接字将文件描述符交给 Erlang 进程。我曾经在一个项目中工作,该项目依赖于这种技术来打开特权 TCP 端口,并且它非常有效。不幸的是,这不是一个开源项目,但通过一些谷歌搜索,我发现这个库做的事情完全一样:https://github.com/msantos/procket
我建议你 fork procket
并稍微精简它(你似乎不需要 icmp
支持,只需要在 read-only 模式下打开的常规文件) .
一旦您在 Erlang VM 中有了文件描述符,您就可以通过不同的方式读取它:
- 像
procket:read/2
那样使用 NIF。
- 作为 Erlang 端口访问文件描述符,请参阅
procket
文档中的 network sniffing example。
我正在从事一个基于 Erlang 的面向安全的项目。此应用程序需要访问仅限于 root 或其他特权用户的系统的某些部分。目前,该项目仅适用于 Unix/Linux/BSD 系统,不应更改文件(只读访问)。
我已经考虑(并测试)了其中的一些解决方案,但是,我不知道我应该用 Erlang 做什么。最糟糕的是什么?哪个最好?什么是最容易维护的?
谢谢!
1 个节点(作为根)
这个解决方案是最糟糕的,即使在测试服务器上我也想避免它。
_____________________________
| |
| (root) |
| ___________ _______ |
| | | | | |
| | Erlang VM |<---| Files | |
| |___________| |_______| |
|_____________________________|
你可以在这里看到我目前不想要的东西的大图。
#!/usr/bin/env escript
main([]) ->
ok;
main([H|T]) ->
{ok, Data} = file:read_file(H),
io:format("~p: ~p~n", [H,Data]),
main(T).
运行 它作为 root,瞧瞧。
su - root
${script_path}/readfile.escript /etc/shadow
1 个节点(作为 root)+ 1 个节点(作为受限用户)
我需要启动 2 个节点,一个 运行ning 以 root 或另一个特权用户身份启动,另一个 运行ning 节点具有受限用户,可以从外部世界轻松访问。这种方法工作得很好,但有很多问题。首先,由于连接节点之间的远程过程调用,我无法使用标准 Erlang 分布式协议连接到特权用户节点(受限节点可以在特权节点上执行任意命令)。我不知道 Erlang 是否真的可以在执行它们之前过滤 RPC。其次,我需要在一台主机上管理两个节点。
________________ ____________________________
| | | |
| (r_user) | | (root) |
| ___________ | | ___________ _______ |
| | | | | | | | | |
| | Erlang VM |<===[socket]===>| Erlang VM |<---| Files | |
| |___________| | | |___________| |_______| |
|________________| |____________________________|
在下面的例子中,我将启动两个Erlang shell。第一个 shell 将处于受限模式:
su - myuser
erl -sname restricted -cookie ${mycookie}
第二个将 运行 与特权用户:
su - root
erl -sname privileged -cookie ${mycookie}
标准 Erlang RPC(安全性不够)
最后,在受限节点上(通过本例中的 shell),我可以访问所有文件:
{ok, Data} = rpc:call(privileged, file, read_file, ["/etc/shadow"]).
用"Firewall"方法
I'm using local unix socket in this example, supported only until R19/R20. Restricted user need to have access to this socket, stored somewhere in
/var/run
.
1 个节点(作为受限用户)+ 外部命令(使用 sudo)
我授予Erlang VM进程使用sudo执行命令的权利。我只需要执行特定程序,获取标准输出并解析它。在这种情况下,我需要使用系统中现有的可用程序或创建一个新程序。
________________ _______________________
| | | |
| (r_user) | | (root) |
| ___________ | | ______ _______ |
| | | | | | | | | |
| | Erlang VM |<===[stdin]===>| sudo |<---| Files | |
| |___________| | | |______| |_______| |
|________________| |_______________________|
1 个节点(作为受限用户)+ 端口 (setuid)
我创建了一个带有 setuid 标志的端口集。该程序现在有权读取文件,但我需要将它放在服务器上的安全位置。如果我想让它动态化,我还应该在 Erlang VM 和这个端口之间定义一个严格的协议。 IMO,setuid 很少是一个好的答案。
________________ ________________________
| | | |
| (r_user) | | (root) [setuid] |
| ___________ | | _______/ _______ |
| | | | | | | | | |
| | Erlang VM |<===[stdin]===>| ports |<---| Files | |
| |___________| | | |_______| |_______| |
|________________| |________________________|
1 个节点(作为受限用户)+ NIF
我不认为我可以在 Erlang VM 中赋予 NIF 特定的权利,也许使用辣椒或其他 non-portable/OS-specific 内核功能。
_______________
| |
| (r_user) |
| ___________ |
| | | |
| | Erlang VM | |
| |___________| |
| | | |
| | NIF | |
| |___________| | _______
| | | | | |
| | ??? |<---| Files |
| |___________| | |_______|
|_______________|
1 个节点(作为受限用户)+ 1 个守护进程(作为 root)
我可以创建一个守护进程 运行ning 作为 root,使用 Unix 套接字或其他方法连接到 Erlang VM。这个解决方案有点像带有 sudo 的端口或外部命令,除了我需要管理一个具有特权的长期守护进程。
________________ _________________________
| | | |
| (r_user) | | (root) |
| ___________ | | ________ _______ |
| | | | | | | | | |
| | Erlang VM |<===[socket]==>| daemon |<---| Files | |
| |___________| | | |________| |_______| |
|________________| |_________________________|
自定义 Erlang 虚拟机
OpenSSH and lot of other secure software 运行s 以 root 身份创建 2 个带有管道的互连进程。当以 root 身份启动 Erlang VM 时,会生成 2 个进程,一个以 root 身份,另一个以受限用户身份。当某些操作需要 root 权限时,受限进程向 root 进程发送请求并等待其答复。我想这是目前更复杂的解决方案,而且我没有掌握足够的 C 和 Erlang VM 来使这个东西运行良好。
______________ _______________
| | | |
| (root) | | (r_user) |
| __________ | | ___________ |
| | | | | | | |
| | PrivProc |<===[pipe]===>| Erlang VM | |
| |__________| | | |___________| |
|______________| |_______________|
从安全角度来看,您最好的选择是尽量减少代码的数量和复杂性 运行 root 权限。因此,当您 运行 整个 Erlang VM 作为 root 时,我会排除所有选项 - 那里的代码太多而无法安全地锁定它。
只要您只需要读取一些文件,最好的选择是使用 sudo
从 Erlang VM 运行 编写一个小的 C 程序。这个程序所要做的就是为你打开文件,并通过 Unix 套接字将文件描述符交给 Erlang 进程。我曾经在一个项目中工作,该项目依赖于这种技术来打开特权 TCP 端口,并且它非常有效。不幸的是,这不是一个开源项目,但通过一些谷歌搜索,我发现这个库做的事情完全一样:https://github.com/msantos/procket
我建议你 fork procket
并稍微精简它(你似乎不需要 icmp
支持,只需要在 read-only 模式下打开的常规文件) .
一旦您在 Erlang VM 中有了文件描述符,您就可以通过不同的方式读取它:
- 像
procket:read/2
那样使用 NIF。 - 作为 Erlang 端口访问文件描述符,请参阅
procket
文档中的 network sniffing example。