从 docker 容器中打开 DGRAM 套接字失败(权限被拒绝)
Opening a DGRAM socket from within a docker container fails (permission denied)
我是 运行 一个应用程序,它构建 ICMP ECHO 请求并将其发送到几个不同的 IP 地址。该应用程序是在 Crystal 中编写的。尝试从 crystal docker 容器中打开套接字时,Crystal 引发异常:权限被拒绝。
在容器中,我没有问题运行 ping 8.8.8.8
。
运行 macos上的应用,我没问题。
阅读有关 apparmor 和 seccomp 的 https://docs.docker.com/engine/security/apparmor/ and https://docs.docker.com/engine/security/seccomp/ 页面,我确定我找到了解决方案,但问题仍未解决,即使 运行 为 docker run --rm --security-opt seccomp=unconfined --security-opt apparmor=unconfined socket_permission
update/edit:深入了解 capabilities(7)
后,我将以下行添加到我的 docker 文件中:RUN setcap cap_net_raw+ep bin/ping
trying让套接字打开但不改变。
谢谢!
相关 crystal 套接字代码,完整的工作代码示例如下:
# send request
address = Socket::IPAddress.new host, 0
socket = IPSocket.new Socket::Family::INET, Socket::Type::DGRAM, Socket::Protocol::ICMP
socket.send slice, to: address
Docker 文件:
FROM crystallang/crystal:0.23.1
WORKDIR /opt
COPY src/ping.cr src/
RUN mkdir bin
RUN crystal -v
RUN crystal build -o bin/ping src/ping.cr
ENTRYPOINT ["/bin/sh","-c"]
CMD ["/opt/bin/ping"]
运行 代码,首先是本地代码,然后是 docker:
#!/bin/bash
crystal run src/ping.cr
docker build -t socket_permission .
docker run --rm --security-opt seccomp=unconfined --security-opt apparmor=unconfined socket_permission
最后,一个 50 行的 crystal 脚本无法打开 docker 中的套接字:
require "socket"
TYPE = 8_u16
IP_HEADER_SIZE_8 = 20
PACKET_LENGTH_8 = 16
PACKET_LENGTH_16 = 8
MESSAGE = " ICMP"
def ping
sequence = 0_u16
sender_id = 0_u16
host = "8.8.8.8"
# initialize packet with MESSAGE
packet = Array(UInt16).new PACKET_LENGTH_16 do |i|
MESSAGE[ i % MESSAGE.size ].ord.to_u16
end
# build out ICMP header
packet[0] = (TYPE.to_u16 << 8)
packet[1] = 0_u16
packet[2] = sender_id
packet[3] = sequence
# calculate checksum
checksum = 0_u32
packet.each do |byte|
checksum += byte
end
checksum += checksum >> 16
checksum = checksum ^ 0xffff_ffff_u32
packet[1] = checksum.to_u16
# convert packet to 8 bit words
slice = Bytes.new(PACKET_LENGTH_8)
eight_bit_packet = packet.map do |word|
[(word >> 8), (word & 0xff)]
end.flatten.map(&.to_u8)
eight_bit_packet.each_with_index do |chr, i|
slice[i] = chr
end
# send request
address = Socket::IPAddress.new host, 0
socket = IPSocket.new Socket::Family::INET, Socket::Type::DGRAM, Socket::Protocol::ICMP
socket.send slice, to: address
# receive response
buffer = Bytes.new(PACKET_LENGTH_8 + IP_HEADER_SIZE_8)
count, address = socket.receive buffer
length = buffer.size
icmp_data = buffer[IP_HEADER_SIZE_8, length-IP_HEADER_SIZE_8]
end
ping
事实证明,答案是 Linux(并扩展为 docker)没有提供与 macOS 对 DGRAM 套接字相同的权限。将套接字声明更改为 socket = IPSocket.new Socket::Family::INET, Socket::Type::RAW, Socket::Protocol::ICMP
允许套接字在 docker.
下连接
运行 非 root 上下文中的程序还需要一点。因为原始套接字仅限于 root,二进制文件也必须发布正确的 capability for access to a raw socket, CAP_NET_RAW
. However, in docker, this isn't necessary. I was able to get the program to run outside of super-user context by running sudo setcap cap_net_raw+ep bin/ping
. This is a decent primer on capabilities and the setpcap command
MacOS 不使用相同的权限系统,因此 setcap
只是一个无法识别的命令。因此,为了在没有超级用户上下文的情况下使上述代码在 macOS 上成功编译和 运行,我将套接字创建代码更改为:
socket_type = Socket::Type::RAW
{% if flag?(:darwin) %}
socket_type = Socket::Type::DGRAM
{% end %}
socket = IPSocket.new Socket::Family::INET, socket_type, Socket::Protocol::ICMP
如果需要,在构建过程的其他地方应用 CAP_NET_RAW
功能以在 linux 中使用。
通过这些更改,我没有看到任何需要更改 Docker 随附的默认设置的 seccomp 或 apparmor 才能 运行 程序。
我是 运行 一个应用程序,它构建 ICMP ECHO 请求并将其发送到几个不同的 IP 地址。该应用程序是在 Crystal 中编写的。尝试从 crystal docker 容器中打开套接字时,Crystal 引发异常:权限被拒绝。
在容器中,我没有问题运行 ping 8.8.8.8
。
运行 macos上的应用,我没问题。
阅读有关 apparmor 和 seccomp 的 https://docs.docker.com/engine/security/apparmor/ and https://docs.docker.com/engine/security/seccomp/ 页面,我确定我找到了解决方案,但问题仍未解决,即使 运行 为 docker run --rm --security-opt seccomp=unconfined --security-opt apparmor=unconfined socket_permission
update/edit:深入了解 capabilities(7)
后,我将以下行添加到我的 docker 文件中:RUN setcap cap_net_raw+ep bin/ping
trying让套接字打开但不改变。
谢谢!
相关 crystal 套接字代码,完整的工作代码示例如下:
# send request
address = Socket::IPAddress.new host, 0
socket = IPSocket.new Socket::Family::INET, Socket::Type::DGRAM, Socket::Protocol::ICMP
socket.send slice, to: address
Docker 文件:
FROM crystallang/crystal:0.23.1
WORKDIR /opt
COPY src/ping.cr src/
RUN mkdir bin
RUN crystal -v
RUN crystal build -o bin/ping src/ping.cr
ENTRYPOINT ["/bin/sh","-c"]
CMD ["/opt/bin/ping"]
运行 代码,首先是本地代码,然后是 docker:
#!/bin/bash
crystal run src/ping.cr
docker build -t socket_permission .
docker run --rm --security-opt seccomp=unconfined --security-opt apparmor=unconfined socket_permission
最后,一个 50 行的 crystal 脚本无法打开 docker 中的套接字:
require "socket"
TYPE = 8_u16
IP_HEADER_SIZE_8 = 20
PACKET_LENGTH_8 = 16
PACKET_LENGTH_16 = 8
MESSAGE = " ICMP"
def ping
sequence = 0_u16
sender_id = 0_u16
host = "8.8.8.8"
# initialize packet with MESSAGE
packet = Array(UInt16).new PACKET_LENGTH_16 do |i|
MESSAGE[ i % MESSAGE.size ].ord.to_u16
end
# build out ICMP header
packet[0] = (TYPE.to_u16 << 8)
packet[1] = 0_u16
packet[2] = sender_id
packet[3] = sequence
# calculate checksum
checksum = 0_u32
packet.each do |byte|
checksum += byte
end
checksum += checksum >> 16
checksum = checksum ^ 0xffff_ffff_u32
packet[1] = checksum.to_u16
# convert packet to 8 bit words
slice = Bytes.new(PACKET_LENGTH_8)
eight_bit_packet = packet.map do |word|
[(word >> 8), (word & 0xff)]
end.flatten.map(&.to_u8)
eight_bit_packet.each_with_index do |chr, i|
slice[i] = chr
end
# send request
address = Socket::IPAddress.new host, 0
socket = IPSocket.new Socket::Family::INET, Socket::Type::DGRAM, Socket::Protocol::ICMP
socket.send slice, to: address
# receive response
buffer = Bytes.new(PACKET_LENGTH_8 + IP_HEADER_SIZE_8)
count, address = socket.receive buffer
length = buffer.size
icmp_data = buffer[IP_HEADER_SIZE_8, length-IP_HEADER_SIZE_8]
end
ping
事实证明,答案是 Linux(并扩展为 docker)没有提供与 macOS 对 DGRAM 套接字相同的权限。将套接字声明更改为 socket = IPSocket.new Socket::Family::INET, Socket::Type::RAW, Socket::Protocol::ICMP
允许套接字在 docker.
运行 非 root 上下文中的程序还需要一点。因为原始套接字仅限于 root,二进制文件也必须发布正确的 capability for access to a raw socket, CAP_NET_RAW
. However, in docker, this isn't necessary. I was able to get the program to run outside of super-user context by running sudo setcap cap_net_raw+ep bin/ping
. This is a decent primer on capabilities and the setpcap command
MacOS 不使用相同的权限系统,因此 setcap
只是一个无法识别的命令。因此,为了在没有超级用户上下文的情况下使上述代码在 macOS 上成功编译和 运行,我将套接字创建代码更改为:
socket_type = Socket::Type::RAW
{% if flag?(:darwin) %}
socket_type = Socket::Type::DGRAM
{% end %}
socket = IPSocket.new Socket::Family::INET, socket_type, Socket::Protocol::ICMP
如果需要,在构建过程的其他地方应用 CAP_NET_RAW
功能以在 linux 中使用。
通过这些更改,我没有看到任何需要更改 Docker 随附的默认设置的 seccomp 或 apparmor 才能 运行 程序。