【问题标题】:Opening a DGRAM socket from within a docker container fails (permission denied)从 docker 容器中打开 DGRAM 套接字失败(权限被拒绝)
【发布时间】:2017-11-18 19:52:32
【问题描述】:

我正在运行一个应用程序,它构建 ICMP ECHO 请求并将其发送到几个不同的 IP 地址。该应用程序是用 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

【问题讨论】:

    标签: sockets docker tcp crystal-lang seccomp


    【解决方案1】:

    事实证明,Linux(以及扩展名为 docker)没有提供与 macOS 对 DGRAM 套接字相同的权限。将套接字声明更改为socket = IPSocket.new Socket::Family::INET, Socket::Type::RAW, Socket::Protocol::ICMP 允许套接字在 docker 下连接。

    在非 root 上下文中运行程序还需要更多操作。因为原始套接字仅限于 root,所以二进制文件还必须发出正确的capability 才能访问原始套接字CAP_NET_RAW。但是,在 docker 中,这不是必需的。通过运行sudo setcap cap_net_raw+ep bin/ping,我能够让程序在超级用户上下文之外运行。 This is a decent primer on capabilities and the setpcap command

    MacOS 不使用相同的权限系统,因此setcap 只是一个无法识别的命令。结果,为了让上面的代码在没有超级用户上下文的macOS上编译运行成功,我把创建socket的代码改成了:

    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 以运行程序。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-07-10
      • 2017-09-17
      • 2012-09-07
      • 2020-11-29
      • 1970-01-01
      • 2019-03-17
      • 1970-01-01
      相关资源
      最近更新 更多