2017-11-18 7 views
2

ICMP ECHO 요청을 작성하여 몇 가지 다른 IP 주소로 보내는 응용 프로그램을 실행 중입니다. 응용 프로그램은 Crystal로 작성됩니다. 크리스털 도커 컨테이너에서 소켓을 열려고하면 Crystal에서 예외가 발생합니다. 권한이 거부되었습니다.도커 컨테이너에서 DGRAM 소켓 열기 (사용 권한이 거부 됨)

컨테이너 내에서 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"] 

코드를 실행하면, 먼저 기본, 다음 고정 표시기를 통해 :

#!/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 

답변

1

Linux (확장명은 docker)가 macOS가 DGRAM 소켓에서 사용하는 것과 동일한 권한을 부여하지 않는다는 대답이 나온다. 소켓 선언을 socket = IPSocket.new Socket::Family::INET, Socket::Type::RAW, Socket::Protocol::ICMP으로 변경하면 소켓을 도커 아래에 연결할 수 있습니다.

비 루트 컨텍스트에서 프로그램을 실행하려면 좀 더 많은 시간이 필요합니다. 원시 소켓은 루트로 제한되므로 원시 소켓에 액세스하려면 에 대한 올바른 이진 파일을 capability으로 발행해야합니다. 그러나 도커에서는 이것이 필요하지 않습니다. sudo setcap cap_net_raw+ep bin/ping을 실행하여 수퍼 유저 컨텍스트 외부에서 실행되도록 프로그램을 가져올 수있었습니다. This is a decent primer on capabilities and the setpcap command

MacOS는 동일한 시스템을 사용하지 않으므로 setcap은 인식 할 수없는 명령입니다. 그 결과, 컴파일하고 슈퍼 사용자 컨텍스트없이 맥 OS에서 성공적으로 실행하려면 위의 코드를 얻기 위해, I가 소켓 생성 코드를 변경 :

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 기능을 적용 빌드의 다른 곳에서 발생 필요한 경우 처리.

이러한 변경 사항으로 인해 프로그램을 실행하기 위해 Docker와 함께 제공되는 기본값에서 seccomp 또는 apparmor에 대한 변경 요구 사항이 표시되지 않습니다.