I'm running an application which builds and sends ICMP ECHO requests to a few different ip addresses. The application is written in Crystal. When attempting to open a socket from within the crystal docker container, Crystal raises an exception: Permission Denied.
From within the container, I have no problem running ping 8.8.8.8.
Running the application on macos, I have no problem.
Reading the https://docs.docker.com/engine/security/apparmor/ and https://docs.docker.com/engine/security/seccomp/ pages on apparmor and seccomp I was sure I'd found the solution, but the problem remains unresolved, even when running as docker run --rm --security-opt seccomp=unconfined --security-opt apparmor=unconfined socket_permission
update/edit: After digging into capabilities(7), I added the following line to my dockerfile: RUN setcap cap_net_raw+ep bin/ping trying to let the socket get opened but without change.
Thanks!
Relevant crystal socket code, full working code sample below:
# 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"]
Running the code, first native, then via 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
And finally, a 50 line crystal script which fails to open a socket in 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
It turns out the answer is that Linux (and by extension docker) does not give the same permissions that macOS does for DGRAM sockets. Changing the socket declaration to socket = IPSocket.new Socket::Family::INET, Socket::Type::RAW, Socket::Protocol::ICMP allows the socket to connect under docker.
A little more still is required to run the program in a non-root context. Because raw sockets are restricted to root, the binary must also be issued the correct 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 doesn't use the same system of permissions, so setcap is just an unrecognized command. As a result, to get the above code to compile and run successfully on macOS without super-user context, I changed the socket creation code to:
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
Applying the CAP_NET_RAW capability for use in linux happens elsewhere in the build process if needed.
With those changes, I'm not seeing any requirement for changes to seccomp or apparmor from the default shipped with Docker in order to run the program.
Related
I am trying to connect my Wii-mote's to my Raspberry Pi, so I can use them in pygame. I know to use the Wii controller, I need to make some configurations, and I have made them. However, when I run my .sh script to connect the remotes, I am thrown some errors. Here are the scripts:
/mywinput
#WiiMote
Wiimote.A = BTN_A
Wiimote.B = BTN_B
Wiimote.Dpad.X = ABS_Y
Wiimote.Dpad.Y = -ABS_X
Wiimote.Minus = BTN_SELECT
Wiimote.Plus = BTN_START
Wiimote.Home = BTN_MODE
Wiimote.1 = BTN_X
Wiimote.2 = BTN_Y
# Nunchuk
Nunchuk.C = BTN_C
Nunchuk.Z = BTN_Z
Plugin.led.Led1 = 1
#Plugin.led.Led2 = 1
Plugin.led.Led3 = 1
#Plugin.led.Led4 = 1
/wiicontroller.sh
#!/bin/bash
sleep 1 # Wait until Bluetooth services are fully initialized
hcitool dev | grep hci >/dev/null
if test $? -eq 0 ; then
wminput -d -c /home/pi/mywinput CC:FB:65:2C:52:8F &
else
echo "Blue-tooth adapter not present!"
exit 1
fi
I have made wiicontoller.sh executable:
sudo chmod 775 /home/pi/bin/connectwii.sh
Supposedly, I should press one and two one the controller and it should connect. However, the script returns immediately. And even then, when I press one and two, I get thrown errors. I don't think that should be possible as the script has returned, but here is the output:
Socket connect error (control channel)
I am also aware that using cwiid in python is another option. But that doesn't work either, as it does not detect my remote. wmgui also cannot pickup my remote. The only way I am able to detect my remote is through the hcitool scan command. Any help would be appreciated!
I am using pySerial and I am running this command using CMD to list available COM ports and displays a COM port number when found:
python -m serial.tools.list_ports
I know that the command line will import the serial module when I use the python -m flag and I can access the objects inside it so it should show the output. However, the same command however does not work when run using the IDLE shell:
import serial
print(serial.tools.list_ports_common)
This returns an error AttributeError: module 'serial' has no attribute 'tools'
Why is it not working at IDLE?
You need to import it first:
from serial.tools import list_ports
list_ports.main() # Same result as python -m serial.tools.list_ports
You can check out the source here
You can simply try connecting to each possible port (COM0...COM255). Then add the ports with successful connections to a list. Here is my example:
import serial
def connectedCOMports ():
allPorts = [] #list of all possible COM ports
for i in range(256):
allPorts.append("COM" + str(i))
ports = [] #a list of COM ports with devices connected
for port in allPorts:
try:
s = serial.Serial(port) #attempt to connect to the device
s.close()
ports.append(port) #if it can connect, add it the the list
except:
pass #if it can't connect, don't add it to the list
return(ports)
print(connectedCOMports())
When I ran this program, it printed ['COM7'] to the console. This represents the ESP32 microcontroller that I connected to my USB port.
On running the sample code given in the dispy documentation
def compute(n):
import time, socket
time.sleep(n)
host = socket.gethostname()
return (host, n)
if name == 'main':
import dispy, random
cluster = dispy.JobCluster(compute)
jobs = []
for i in range(10):
# schedule execution of 'compute' on a node (running 'dispynode')
# with a parameter (random number in this case)
job = cluster.submit(random.randint(5,20))
job.id = i # optionally associate an ID to job (if needed later)
jobs.append(job)
# cluster.wait() # wait for all scheduled jobs to finish
for job in jobs:
host, n = job() # waits for job to finish and returns results
print('%s executed job %s at %s with %s' % (host, job.id, job.start_time, n))
# other fields of 'job' that may be useful:
# print(job.stdout, job.stderr, job.exception, job.ip_addr, job.start_time, job.end_time)
cluster.print_status()
I get the following output
2017-03-29 22:39:52 asyncoro - version 4.5.2 with epoll I/O notifier
2017-03-29 22:39:52 dispy - dispy client version: 4.7.3
2017-03-29 22:39:52 dispy - Port 51347 seems to be used by another program
And then nothing happens.
How to free the 51347 port?
If you are under Linux, run sudo netstat -tuanp | grep 51347 and take note of the pid using that port.
Then execute ps ax | grep <pid> to check which service/program is running with that pid.
Then execute kill <pid> to terminate the process using that port.
Please check which process is using the port before killing it just in case it is something that you should not kill.
I just can't figure out what's going on with my RSync. I'm running RSync on RHEL5, ip = xx.xx.xx.97. It's getting files from RHEL5, ip = xx.xx.xx.96.
Here's what the log (which I specified on the RSync command line) shows on xx.97 (the one requesting the files):
(local time)
2015/08/30 13:40:01 [17353] #ERROR: auth failed on module tomcat_backup
2015/08/30 13:40:01 [17353] rsync error: error starting client-server protocol (code 5) at main.c(1530) [receiver=3.0.6]
Here's what the log(which is specified in the rsyncd.conf file) shows on xx.96 (the one supplying the files):
(UTC time)
2015/08/30 07:40:01 [8836] name lookup failed for xx.xx.xx.97: Name or service not known
2015/08/30 07:40:01 [8836] connect from UNKNOWN (xx.xx.xx.97)
2015/08/30 07:40:01 [8836] auth failed on module tomcat_backup from unknown (xx.xx.xx.97): password mismatch
Here's the actual rsync.sh command called from xx.xx.xx.97 (the requester):
export RSYNC_PASSWORD=rsyncclient
rsync -havz --log-file=/usr/local/bin/RSync/test.log rsync://rsyncclient#xx.xx.xx.96/tomcat_backup/ProcessSniffer/ /usr/local/bin/ProcessSniffer
Here's the rsyncd.conf on xx.xx.xx.97:
lock file = /var/run/rsync.lock
log file = /var/log/rsyncd.log
pid file = /var/run/rsyncd.pid
[files]
name = tomcat_backup
path = /usr/local/bin/
comment = The copy/backup of tomcat from .96
uid = tomcat
gid = tomcat
read only = no
list = yes
auth users = rsyncclient
secrets file = /etc/rsyncd.secrets
hosts allow = xx.xx.xx.96/255.255.255.0
Here's the rsyncd.secrets on xx.xx.xx.97:
files:files
Here's the rsyncd.conf on xx.xx.xx.96 (the supplier of files):
Note: there is a 'cwrsync' (Windows version of rsync) successfully calling for files also (xx.xx.xx.100)
Note: yes, there is the possibility of xx.96 requesting files from xx.97. However, this is NOT actually happening.
It's commented out of the init.d mechanism.
lock file = /var/run/rsync.lock
log file = /var/log/rsync.log
pid file = /var/run/rsync.pid
strict modes = false
[files]
name = tomcat_backup
path = /usr/local/bin
comment = The copy/backup of tomcat from xx.97
uid = tomcat
gid = tomcat
read only = no
list = yes
auth users = rsyncclient
secrets file = /etc/rsyncd.secrets
hosts allow = xx.xx.xx.97/255.255.255.0, xx.xx.xx.100/255.255.255.0
Here's the rsyncd.secrets on xx.xx.xx.97:
files:files
It was something else. I had a script calling the rsync command, and that was causing the problem. The actual rsync command line was ok.
Apologies.
This is what I have been through when I got this error. My first thinking was to check rsync server log. and it is not in the place configured in rsync.conf. Then I checked the log printed in systemctl status rsyncd
rsyncd[23391]: auth failed on module signaling from unknown (172.28.15.10): missing secret for user "rsync_backup"
rsyncd[23394]: Badly formed boolean in configuration file: "no # rsync daemon before transmission, change to the root directory and limited within.".
rsyncd[23394]: params.c:Parameter() - Ignoring badly formed line in configuration file: ignore errors # ignore some io error informations.
rsyncd[23394]: Badly formed boolean in configuration file: "false # if true, cannot upload file to this server.".
rsyncd[23394]: Badly formed boolean in configuration file: "false # if true, cannot download file from this server.".
rsyncd[23394]: Badly formed boolean in configuration file: "false # if true, can only list files here.".
Combining the fact that log configuration does not come into play. It seems that the comment after each line of configuration in rsync.conf makes configurations invalid. So I deleted those # ... and restart rsyncd.
Running CentOS. xinetd.d/clhtest entry is as follows:
service clhtest
{
disable = no
port = 8020
socket_type = stream
protocol = tcp
wait = no
user = charrison
passenv = PATH
server = /home/charrison/bin/clhtest
}
In debugging this I need to write to a file. I set the server process up to open /home/charrison/log/foo.txt as one of the first steps (note user=charrison), but it doesn't - and I assume it tries to. When I launch the server program from command line it opens the file successfully.
I suspect the umask parameter may be needed, but I don't know what it defaults to.
Any hints?