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

我正在运行一个应用程序,它build立并发送ICMP ECHO请求到几个不同的IP地址。 该应用程序是用Crystal编写的。 当试图从晶体泊坞窗容器内打开一个套接字时,Crystal引发一个exception:权限被拒绝。

从容器内,我没有问题运行ping 8.8.8.8

在macos上运行应用程序,我没有问题。

阅读apparmor和seccomp上的https://docs.docker.com/engine/security/apparmor/和https://docs.docker.com/engine/security/seccomp/页面我确定find了解决scheme,但问题仍然没有解决,即使作为docker run --rm --security-opt seccomp=unconfined --security-opt apparmor=unconfined socket_permission

更新/编辑 :挖掘到capabilities(7) ,我添加了以下行到我的dockerfile: RUN setcap cap_net_raw+ep bin/ping试图让套接字被打开,但没有改变。

谢谢!

相关的水晶sockets代码,完整的工作代码示例如下:

  # 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 

最后,在docker中无法打开套接字的50行水晶脚本:

 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 

事实certificate,答案是Linux(和通过扩展docker)不给macOS为DGRAM套接字提供相同的权限。 将套接字声明更改为socket = IPSocket.new Socket::Family::INET, Socket::Type::RAW, Socket::Protocol::ICMP允许套接字在docker下连接。

还有一点需要在非根上下文中运行程序。 由于原始套接字仅限于root用户,因此二进制文件也必须具有访问原始套接字CAP_NET_RAW的正确能力 。 但是,在docker工人,这是没有必要的。 通过运行sudo setcap cap_net_raw+ep bin/ping我能够让程序在超级用户上下文之外运行。 这是一个体面的入门能力和setpcap命令

MacOS不使用相同的权限系统,所以setcap只是一个无法识别的命令。 因此,为了让上面的代码在没有超级用户上下文的情况下在macOS上成功编译和运行,我将套接字创build代码更改为:

 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 

如果需要,在构build过程的其他地方应用CAP_NET_RAWfunction以在Linux中使用。

通过这些更改,我没有看到为了运行程序而对Docker默认的seccomp或apparmor进行更改的任何要求。

Interesting Posts