SSH Tunnel within docker container - docker

I have a client running in a docker container that subscribes to a MQTT broker and then writes the data into a database.
To connect to the MQTT Broker i will have to set up port forwarding.
While developing the client on my local machine the following worked fine:
SSH -L <mqtt-port:1883>:localhost:<9000> <user>#<ip-of-server-running-broker>
The client is then configured to subscribe to the MQTT broker via localhost:9000.
This all works fine on my local machine.
Within the container it wont, unless I run the container with --net=host but I'd rather not do that due to security concerns.
I tried the following:
Create docker network "testNetwork"
Run a ssh_tunnel container within "testNetwork" and implement port forwarding inside this container.
Run the database_client container within "testNetwork" and subscribe to the mqtt broker via the bridge network like ("ssh_tunnel.testNetwork:")
(I want 2 seperate containers for this because the ip address will have to be altered quite often and I don't want to re-build the client container all the time)
But all of my attempts have failed so far. The forwarding seems to work (I can access the shell on the server in the ssh container) but I haven't found a way to actually subscribe to the mqtt broker from within the client container.
Maybe this is actually quite simple and I just don't see how it works, but I've been stuck on this problem for hours by now...
Any help or hints are appreciated!

The solution was actually quite simple and works without using -net=host.
I needed to bind to 0.0.0.0 and use the Gateway Forwarding Option to allow remote hosts (the database client) to connect to the forwarded ports.
ssh -L -g *:<hostport>:localhost:<mqtt-port/remote port> <user>#<remote-ip>
Other containers within the same Docker bridge network can then simply use the connection string <name-of-ssh-container>:<hostport>.

Related

Why can my Docker app receive UDP data without publishing the port?

I'm learning Docker networking. I'm using Docker Desktop on Windows.
I'm trying to understand the following observations:
Short version in a picture:
Longer version:
First setup (data from container to host)
I have a simple app running in a container. It sends one UDP-datagram to a specific port on the host (using "host.docker.internal")
I have a corresponding app running on the host. It listens to the port and is supposed to receive the UDP-datagram.
That works without publishing any ports in docker (expected behavior!).
Second setup (data from host to container)
I have a simple app on the host. It sends one UDP-datagram to a specific port on the loopback network (using "localhost")
I have a corresponding app running in a container. It listens to the port and is supposed to receives the UDP-datagram.
That works only if the container is run with option -p port:port/udp (expected behavior!).
Third setup (combination of the other two)
I have an app "Requestor" running in a container. It sends a UDP request-message to a specific port on the host and then wants to receive a response-message.
I have a corresponding app "Responder" running on the host. It listens to the port and is supposed to receive the request-message. Then it sends a UDP response-message to the endpoint of the request-message.
This works as well, and - that's what I don't understand - without publishing the port for the response-message!
How does this work? I'm pretty sure there's some basic networking-knowledge that I simply don't have already to explain this. I would be pleased to learn some background on this.
Sidenote:
Since I can do curl www.google.com successfully from inside a container, I realize that a container definitely must not publish ports to receive any data. But there's TCP involved here to establish a connection. UDP on the other hand is "connectionless", so that can't be the (whole) explanation.
After further investigation, NAT seems to be the answer.
According to these explanations, a NAT is involved between the loopback interface and the docker0 bridge.
This is less recognizable with Docker Desktop for Windows because of the following (source):
Because of the way networking is implemented in Docker Desktop for Windows, you cannot see a docker0 interface on the host. This interface is actually within the virtual machine.

How to make MQTT broker docker image accessible to devices in LAN?

I have a a docker container with a few images running there. I run them via docker-compose up command. On my device everything works well with localhost but I want to make so that other devices in the same network will be able to access the MQTT broker as well. How do I do that?
Currently, in my code I do this:
ws:localhost:9001
But since this localhost applies only for the device that runs docker, another laptop won't be able to use it. How do I solve that?
You use the LAN IP address of your machine (the one hosting the docker containers) in place of localhost.
We have no way of knowing what that address may be, but it could start with 192.168.x.x or may be 10.x.x.x
By default, Docker has a "bridge" network that will bridge your container to the outside world. Just use the IP address of the computer where your MQTT Broker Container is running, and port 9001, and it should work fine.
If you need to run it on an internal Docker network, you will have to use something like an ADC or TCP Proxy of some sort to allow access to it.

How to make a Docker container's service accessible via the container's IP address?

I'm a bit confused. Trying to run both a HTTP server listening on port 8080 and a SSH server listening on port 22 inside a Docker container I managed to accomplish the latter but strangely not the former.
Here is what I want to achieve and how I tried it:
I want to access services running inside a Docker container using the IP address assigned to the container:
ssh user#172.17.0.2
curl http://172.17.0.2:8080
Note: I know this is not how you would configure a real web server but I want the container to mimic an embedded device which runs both services and which I don't have available all the time. (So it's really just a local non-production thing with no security requirements).
I didn't expect integrating the SSH server to be easy, but to my surprise I just installed and started it and had to do nothing else to be able to connect to the machine via ssh (no EXPOSE 22 or --publish).
Now I wanted to access the container via HTTP on port 8080 and fiddled with --publish and EXPOSE but only managed to make the HTTP server available through localhost/127.0.0.1 on the host. So now I can access it via
curl http://127.0.0.1:8080/
but I want to access both services via the same IP address which is NOT localhost (e.g. the address the container got randomly assigned is totally OK for me).
Unfortunately
curl http://172.17.0.2:8080/
waits until it times out every time I tied it.
I tried docker run together with -p 8080, -p 127.0.0.1:8080:8080, -p 172.17.0.2:8080:8080 and much more combinations, together or without EXPOSE 8080 in the Dockerfile but without success.
Why can I access the container via port 22 without having exposed anything?
And how do I make it accessible via the container's IP address?
Update: looks like I'm experiencing exactly what's described here.

Talk to server on docker container with no exposed ports

I have some docker containers talking together through docker bridge networks. They cannot be accessed from outside (I was said) as they are launched from a script with a default command which does not include 'expose' nor '-p' option. I cannot change that script.
I would like to connect to one of this containers which runs a server and listens for requests on port 8080. I tried connecting that bridge to a newly created docker bridge network, but i did not succede.
Now I am thinking of creating a new container and letting it talk to the server one (through bridge networks). As it is a new contaienr I can use the 'expose' or '-p' options, so it would be able to talk to the host machine.
Is it a good idea? How can I forward every request made to that container to the server one and get responses back to the host machine then?
Thanks
Within the default docker network, all ports are exposed. So you only need a container that exposes a port to the host machine and is in the same network as the other containers you have already created.
This is a relatively normal pattern. You can use a reverse proxy like nginx to achieve something like this.
There are some containers that automate this process:
https://github.com/jwilder/nginx-proxy
If you have no control over the other containers though, you will need to write the proxy config by hand.
If the container to which you are trying to connect is an http server, you may be able to use a ready-made container image that can work as an http forwarder (e.g., nginx - it is relatively easy to configure it as an http forwarder).
If you need plain tcp forwarding, you could make a container running 'socat' (socat can work as a tcp forwarder).
NOTE: in either case, you will be exposing a listener that wasn't meant to be on a public address. Do take measures not to allow unauthorized connections.

Why Kafka broker connects to itself?

Few days ago I tried to configure Kafka Docker container with Docker Compose and port mapping and discover interesting behavior which I do not fully understand:
Kafka broker seems to connect to itself. Why ?
My set up is:
Ubuntu 14.04, Docker 1.13.1, Docker-Compose 1.5.2
Kafka 0.10 listens on port 9092, this port is exposed by container.
In Docker Compose I have port mapping from container port 9092 to local port 4005.
I configured host name of my Docker Host machine and local port from Compose in advertised.listeners (docker-host:4005) since broker should be visible from my company network.
With this set up when I try to send/fetch data to/from Kafka, all attempts end up with:
Topic metadata fetch included errors: {topic_name=LEADER_NOT_AVAILABLE}
After trying various combinations of ports and host names in advertised.listeners, I discovered that sole working combination is localhost:9092. Any attempt to change hostname or port led to the error mentioned above.
This made me think that Kafka tries to connect to address configured in advertised.listeners and this is somehow related to topic metadata.
So inside Docker container I did:
redirect traffic to "docker-host" to loopback
echo "127.0.0.1 $ADVERTISED_HOST" >> /etc/hosts
configure Kafka to listen on all interfaces and port (exact as advertised)
sed -r -i "s/#(listeners)=(.*)/\1=PLAINTEXT:\/\/0.0.0.0:4005/g" $KAFKA_HOME/config/server.properties
advertise "docker-host" and external port
sed -r -i "s/#(advertised.listeners)=(.*)/\1=PLAINTEXT:\/\/$ADVERTISED_HOST:4005/g" $KAFKA_HOME/config/server.properties
And now it works like a charm.
However I still do not understand:
Why Kafka broker might need to connect to itself via address configured in advertised.listeners ?
Is there a way to disable this or at least configure it to use address from 'listeners' property (with default Kafka port) ?
UPD
Worth to mention, following setup does not work: Kafka listens on 0.0.0.0:9092, advertised listener is configured to docker-host:4005.
In this case whenever consumer or producer connects to kafka it receives LEADER_NOT_AVAILABLE.
There is also connection shown by netstat (within container) to docker-host:4005 in state SYN_SENT.
UPD 2
Looks like there is similar problem with Kafka but inside AWS described here.
Difference is that in my case I want to use different Kafka port.
UPD 3
Ok, the reason why setup mentioned in the first UPD paragraph does not work is - UFW, for some reasons it blocks traffic which goes from docker container to itself via host machine.
Why Kafka broker might need to connect to itself via address
configured in advertised.listeners ?
When a Kafka broker is first connected by a client, it replies back with the address that it expects that client to use in the future to talk to the broker. This is what is set in the advertised.listeners property. If you don't set this property, the value from listeners will be used instead (which answers your second question).
So your "issue" is, that remote clients connect to yourhost:9092, reach the Kafka broker, because you forwarded the port, the broker then responds with "you can reach me at localhost:9092" and when the client sends the next packet there it just connects back to itself.
The metadata is not really related here, its just the first request that gets made.
Your solution is correct for this setup I think, have Kafka listen on local interfaces and set the advertised.listeners to the host that someone from your company network would connect to.
I don't 100% know if the broker needs to connect to itself as well, pretty sure thats not the case though. I think your setup would also work without the entry of the external hostname in your /etc/hosts file.
Is there a way to disable this or at least configure it to use address
from 'listeners' property (with default Kafka port) ?
see above

Resources