【问题标题】:Connect to VPN with Podman使用 Podman 连接到 VPN
【发布时间】:2019-11-28 14:50:21
【问题描述】:

有这个Dockerfile:

FROM fedora:30

ENV LANG C.UTF-8

RUN dnf upgrade -y \
    && dnf install -y \
        openssh-clients \
        openvpn \
        slirp4netns \
    && dnf clean all

CMD ["openvpn", "--config", "/vpn/ovpn.config", "--auth-user-pass", "/vpn/ovpn.auth"]

使用以下方法构建图像:

podman build -t peque/vpn .

如果我尝试使用它运行它(注意 $(pwd),VPN 配置和凭据的存储位置):

podman run -v $(pwd):/vpn:Z --cap-add=NET_ADMIN --device=/dev/net/tun -it peque/vpn

我收到以下错误:

ERROR: Cannot open TUN/TAP dev /dev/net/tun: Permission denied (errno=13)

关于如何解决此问题的任何想法?如果有帮助,我不介意更改基本图像(即:到 Alpine 或其他任何东西,只要它允许我使用 openvpn 进行连接)。

系统信息

使用 Podman 1.4.4(无根)和带有内核 5.1.19 的 Fedora 30 发行版。

/dev/net/tun 权限

运行容器:

podman run -v $(pwd):/vpn:Z --cap-add=NET_ADMIN --device=/dev/net/tun -it peque/vpn

然后,从容器中,我可以:

# ls -l /dev/ | grep net
drwxr-xr-x. 2 root   root       60 Jul 23 07:31 net

我也可以列出/dev/net,但会得到“权限被拒绝错误”:

# ls -l /dev/net
ls: cannot access '/dev/net/tun': Permission denied
total 0
-????????? ? ? ? ?            ? tun

正在尝试--privileged

如果我尝试使用--privileged

podman run -v $(pwd):/vpn:Z --privileged --cap-add=NET_ADMIN --device=/dev/net/tun -it peque/vpn

然后我得到一个 no-such-file-or-directory 错误 (errno=2),而不是权限被拒绝错误 (errno=13):

ERROR: Cannot open TUN/TAP dev /dev/net/tun: No such file or directory (errno=2)

使用--privileged时可以有效验证不存在/dev/net/目录,即使我传递了--cap-add=NET_ADMIN --device=/dev/net/tun参数。

详细日志

这是我用verb 3配置客户端时得到的日志:

OpenVPN 2.4.7 x86_64-redhat-linux-gnu [SSL (OpenSSL)] [LZO] [LZ4] [EPOLL] [PKCS11] [MH/PKTINFO] [AEAD] built on Feb 20 2019
library versions: OpenSSL 1.1.1c FIPS  28 May 2019, LZO 2.08
Outgoing Control Channel Authentication: Using 160 bit message hash 'SHA1' for HMAC authentication
Incoming Control Channel Authentication: Using 160 bit message hash 'SHA1' for HMAC authentication
TCP/UDP: Preserving recently used remote address: [AF_INET]xx.xx.xx.xx:1194
Socket Buffers: R=[212992->212992] S=[212992->212992]
UDP link local (bound): [AF_INET][undef]:0
UDP link remote: [AF_INET]xx.xx.xx.xx:1194
TLS: Initial packet from [AF_INET]xx.xx.xx.xx:1194, sid=3ebc16fc 8cb6d6b1
WARNING: this configuration may cache passwords in memory -- use the auth-nocache option to prevent this
VERIFY OK: depth=1, C=ES, ST=XXX, L=XXX, O=XXXXX, emailAddress=email@domain.com, CN=internal-ca
VERIFY KU OK
Validating certificate extended key usage
++ Certificate has EKU (str) TLS Web Server Authentication, expects TLS Web Server Authentication
VERIFY EKU OK
VERIFY OK: depth=0, C=ES, ST=XXX, L=XXX, O=XXXXX, emailAddress=email@domain.com, CN=ovpn.server.address
Control Channel: TLSv1.2, cipher TLSv1.2 ECDHE-RSA-AES256-GCM-SHA384, 2048 bit RSA
[ovpn.server.address] Peer Connection Initiated with [AF_INET]xx.xx.xx.xx:1194
SENT CONTROL [ovpn.server.address]: 'PUSH_REQUEST' (status=1)
PUSH: Received control message: 'PUSH_REPLY,route xx.xx.xx.xx 255.255.255.0,route xx.xx.xx.0 255.255.255.0,dhcp-option DOMAIN server.net,dhcp-option DNS xx.xx.xx.254,dhcp-option DNS xx.xx.xx.1,dhcp-option DNS xx.xx.xx.1,route-gateway xx.xx.xx.1,topology subnet,ping 10,ping-restart 60,ifconfig xx.xx.xx.24 255.255.255.0,peer-id 1'
OPTIONS IMPORT: timers and/or timeouts modified
OPTIONS IMPORT: --ifconfig/up options modified
OPTIONS IMPORT: route options modified
OPTIONS IMPORT: route-related options modified
OPTIONS IMPORT: --ip-win32 and/or --dhcp-option options modified
OPTIONS IMPORT: peer-id set
OPTIONS IMPORT: adjusting link_mtu to 1624
Outgoing Data Channel: Cipher 'AES-128-CBC' initialized with 128 bit key
Outgoing Data Channel: Using 160 bit message hash 'SHA1' for HMAC authentication
Incoming Data Channel: Cipher 'AES-128-CBC' initialized with 128 bit key
Incoming Data Channel: Using 160 bit message hash 'SHA1' for HMAC authentication
ROUTE_GATEWAY xx.xx.xx.xx/255.255.255.0 IFACE=tap0 HWADDR=0a:38:ba:e6:4b:5f
ERROR: Cannot open TUN/TAP dev /dev/net/tun: No such file or directory (errno=2)
Exiting due to fatal error

错误编号可能会根据我是否使用--privileged 运行命令而改变。

【问题讨论】:

  • 无法重现 - 客户端容器在 CAP_NET_ADMIN 设置和 /dev/net/tun 允许的情况下运行良好。我使用了 podman 1.4.4(无根)、内核 5.2.1、来自官方 openvpn 示例的密钥和证书、配置 - 也来自示例,但删除了一些(可能)不需要的东西。
  • 也许向问题添加更具体的信息是有意义的——版本、服务器/客户端的最小配置等。
  • @DanilaKiver 我在我的问题中附加了更多信息。更完整的日志,关于我如何从容器中看到/dev/net/tun 的信息以及尝试--privileged 的结果(这没有帮助,或者因为/dev/net/ 不存在而变得更糟)。有什么想法吗?
  • 谢谢,这个更新现在更有意义了——Fedora 有 SELinux(在 VM 上检查——如果是非特权容器,SELinux 会阻止对/dev/net/tun 的访问)。此外,即使在 Fedora 和没有 SELinux 的情况下,似乎在特权容器的情况下缺少 /dev/net/tun 也是可以重现的——这很有趣。打算再深入一点。
  • 我认为这里的好方法是扩展现有的 SELinux 策略,以允许此特定容器与 tun_tap_device_t 一起工作 - 让我试验一下 :)

标签: dockerfile podman


【解决方案1】:

原来你被 SELinux 阻止了:在运行客户端容器并尝试访问其中的/dev/net/tun 之后,你会在审计日志中得到以下 AVC 拒绝:

type=AVC msg=audit(1563869264.270:833): avc:  denied  { getattr } for  pid=11429 comm="ls" path="/dev/net/tun" dev="devtmpfs" ino=15236 scontext=system_u:system_r:container_t:s0:c502,c803 tcontext=system_u:object_r:tun_tap_device_t:s0 tclass=chr_file permissive=0

要允许您的容器在不完全享有特权并强制执行 SELinux 的情况下配置隧道,您需要稍微自定义 SELinux 策略。但是,我没有找到一个简单的方法来正确地做到这一点。

幸运的是,有一个名为 udica 的工具,它可以从容器配置中生成 SELinux 策略。它本身不提供所需的策略,并且需要一些人工干预,因此我将逐步描述我如何让 openvpn 容器工作。

首先,安装所需工具:

$ sudo dnf install policycoreutils-python-utils policycoreutils udica

创建具有所需权限的容器,然后为该容器生成策略:

$ podman run -it --cap-add NET_ADMIN --device /dev/net/tun -v $PWD:/vpn:Z --name ovpn peque/vpn
$ podman inspect ovpn | sudo udica -j - ovpn_container

Policy ovpn_container created!

Please load these modules using: 
# semodule -i ovpn_container.cil /usr/share/udica/templates/base_container.cil

Restart the container with: "--security-opt label=type:ovpn_container.process" parameter

这是udica生成的策略:

$ cat ovpn_container.cil 
(block ovpn_container
    (blockinherit container)
    (allow process process ( capability ( chown dac_override fsetid fowner mknod net_raw setgid setuid setfcap setpcap net_bind_service sys_chroot kill audit_write net_admin ))) 

    (allow process default_t ( dir ( open read getattr lock search ioctl add_name remove_name write ))) 
    (allow process default_t ( file ( getattr read write append ioctl lock map open create  ))) 
    (allow process default_t ( sock_file ( getattr read write append open  ))) 
)

让我们试试这个策略(注意--security-opt 选项,它告诉podman 在新创建的域中运行容器):

$ sudo semodule -i ovpn_container.cil /usr/share/udica/templates/base_container.cil
$ podman run -it --cap-add NET_ADMIN --device /dev/net/tun -v $PWD:/vpn:Z --security-opt label=type:ovpn_container.process peque/vpn
<...>
ERROR: Cannot open TUN/TAP dev /dev/net/tun: Permission denied (errno=13)

呃。这就是问题所在:udica 生成的策略仍然不知道我们容器的具体要求,因为它们没有反映在它的配置中(嗯,很可能,可以推断您希望允许对 @987654334 进行操作@ 基于您请求 --device /dev/net/tun 的事实,但是...)。因此,我们需要通过添加更多语句来自定义策略。

让我们暂时禁用 SELinux 并运行容器以收集预期的拒绝:

$ sudo setenforce 0
$ podman run -it --cap-add NET_ADMIN --device /dev/net/tun -v $PWD:/vpn:Z --security-opt label=type:ovpn_container.process peque/vpn

这些是:

$ sudo grep denied /var/log/audit/audit.log
type=AVC msg=audit(1563889218.937:839): avc:  denied  { read write } for  pid=3272 comm="openvpn" name="tun" dev="devtmpfs" ino=15178 scontext=system_u:system_r:ovpn_container.process:s0:c138,c149 tcontext=system_u:object_r:tun_tap_device_t:s0 tclass=chr_file permissive=1
type=AVC msg=audit(1563889218.937:840): avc:  denied  { open } for  pid=3272 comm="openvpn" path="/dev/net/tun" dev="devtmpfs" ino=15178 scontext=system_u:system_r:ovpn_container.process:s0:c138,c149 tcontext=system_u:object_r:tun_tap_device_t:s0 tclass=chr_file permissive=1
type=AVC msg=audit(1563889218.937:841): avc:  denied  { ioctl } for  pid=3272 comm="openvpn" path="/dev/net/tun" dev="devtmpfs" ino=15178 ioctlcmd=0x54ca scontext=system_u:system_r:ovpn_container.process:s0:c138,c149 tcontext=system_u:object_r:tun_tap_device_t:s0 tclass=chr_file permissive=1
type=AVC msg=audit(1563889218.947:842): avc:  denied  { nlmsg_write } for  pid=3273 comm="ip" scontext=system_u:system_r:ovpn_container.process:s0:c138,c149 tcontext=system_u:system_r:ovpn_container.process:s0:c138,c149 tclass=netlink_route_socket permissive=1

或者更易于阅读:

$ sudo grep denied /var/log/audit/audit.log | audit2allow


#============= ovpn_container.process ==============
allow ovpn_container.process self:netlink_route_socket nlmsg_write;
allow ovpn_container.process tun_tap_device_t:chr_file { ioctl open read write };

好的,让我们修改udica-生成的策略,添加建议的allows(注意,这里我手动将语法转换为CIL):

(block ovpn_container
    (blockinherit container)
    (allow process process ( capability ( chown dac_override fsetid fowner mknod net_raw setgid setuid setfcap setpcap net_bind_service sys_chroot kill audit_write net_admin )))

    (allow process default_t ( dir ( open read getattr lock search ioctl add_name remove_name write )))
    (allow process default_t ( file ( getattr read write append ioctl lock map open create  )))
    (allow process default_t ( sock_file ( getattr read write append open  )))

    ; This is our new stuff.
    (allow process tun_tap_device_t ( chr_file ( ioctl open read write )))
    (allow process self ( netlink_route_socket ( nlmsg_write )))
)

现在我们重新启用 SELinux,重新加载模块并在我们指定自定义域时检查容器是否正常工作:

$ sudo setenforce 1
$ sudo semodule -r ovpn_container
$ sudo semodule -i ovpn_container.cil /usr/share/udica/templates/base_container.cil
$ podman run -it --cap-add NET_ADMIN --device /dev/net/tun -v $PWD:/vpn:Z --security-opt label=type:ovpn_container.process peque/vpn
<...>
Initialization Sequence Completed

最后,检查其他容器是否仍然没有这些权限:

$ podman run -it --cap-add NET_ADMIN --device /dev/net/tun -v $PWD:/vpn:Z peque/vpn
<...>
ERROR: Cannot open TUN/TAP dev /dev/net/tun: Permission denied (errno=13)

耶!我们继续使用 SELinux,只允许对我们的特定容器进行隧道配置。

【讨论】:

  • 感谢您的详细解答!我目前无法访问可以尝试此操作的机器。我明天试试,但看起来很有希望。 ^^
  • 我收到了TCP/UDP: Socket bind failed on local address [AF_INET][undef]:0: Permission denied (errno=13)。审核日志显示type=AVC msg=audit(1563968192.672:465): avc: denied { node_bind } for pid=19626 comm="openvpn" scontext=system_u:system_r:ovpn_container.process:s0:c582,c694 tcontext=system_u:object_r:node_t:s0 tclass=udp_socket permissive=0。我尝试将(allow process openvpn_port_t ( udp_socket ( name_bind )))(allow process node_t ( udp_socket ( name_bind ))) 添加到策略中,但没有成功。有任何想法吗?也许我使用了错误的语法。 :-/
  • 我认为应该是(allow process node_t ( udp_socket ( node_bind ))) - 拒绝操作是node_bind,而不是name_bind
  • 感谢您的帮助。我仍然没有设法让它工作...... :-(但你的回答肯定解决了我的第一个问题。现在我可以ssh到不需要VPN的服务器,但仍然不能ssh到那些需要VPN的服务器VPN。ssh 命令被“卡住”。没有明显的错误,也没有 SELinux 警告。ping 也无法正常工作,所以也许我仍然有一些连接问题。
  • 其实,如果我等得够久,我会得到以下错误:ssh: connect to host server.domain.com port 22: Connection refused \n kex_exchange_identification: Connection closed by remote host.
猜你喜欢
  • 2012-03-23
  • 1970-01-01
  • 2018-04-26
  • 2016-02-28
  • 2013-10-11
  • 1970-01-01
  • 1970-01-01
  • 2013-08-15
  • 1970-01-01
相关资源
最近更新 更多