在Docker容器内打开DGRAM套接字失败(权限被拒绝)

3
我正在运行一个应用程序,该程序构建并向几个不同的IP地址发送ICMP ECHO请求。该应用程序是使用Crystal编写的。当尝试从Crystal Docker容器内部打开套接字时,Crystal会引发异常:Permission Denied。
在容器内部,我没有问题运行ping 8.8.8.8
在macOS上运行应用程序,我也没有问题。
阅读有关apparmor和seccomp的https://docs.docker.com/engine/security/apparmor/https://docs.docker.com/engine/security/seccomp/页面后,我确信已经找到了解决方案,但问题仍未解决,即使作为docker run --rm --security-opt seccomp=unconfined --security-opt apparmor=unconfined socket_permission运行也是如此。
更新/编辑:在深入研究capabilities(7)后,我将以下行添加到我的Dockerfile:RUN setcap cap_net_raw+ep bin/ping,试图让套接字被打开,但没有改变。
谢谢!

相关的晶体插座代码,完整的可运行代码示例如下:

  # 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

Dockerfile:

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行的水晶脚本无法在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
1个回答

2
原来的问题在于Linux(以及扩展到docker)并没有给予DGRAM套接字相同的权限,因此需要将套接字声明更改为socket = IPSocket.new Socket::Family::INET, Socket::Type::RAW, Socket::Protocol::ICMP以便连接docker。此外,为了在非root环境下运行该程序,还需要使用正确的capability访问原始套接字(CAP_NET_RAW)。但是在docker中,这不是必要的。通过运行sudo setcap cap_net_raw+ep bin/ping,我能够在超级用户环境之外运行该程序。 这是关于capabilities和setpcap命令的一个很好的入门指南
由于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

如果需要,在Linux中应用CAP_NET_RAW功能的过程发生在构建过程的其他地方。

通过这些更改,我没有看到运行该程序需要更改seccomp或apparmor的要求,因为Docker默认已经包含了它们。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接