Should I include localhost when forwarding ports in Docker? - docker

Whenever I want to forward ports in a Docker container, I used a simple -p 8080:8080 command.
Now, I read in a couple of places (here and here), that this is possibly insecure, and that I should include the localhost loopback, like this: -p 127.0.0.1:8080:8080.
Could someone shed more light on this?
When should this be done and what is the actual security impact?

When you don't specify an ip address when publishing ports, the published ports are available on all interfaces. That is, if you run docker run -p 8080:8080 ..., then other systems on your network can access the service on port 8080 on your machine (and if your machine has a publicly routable address, then systems elsewhere in the world can access the service as well). (Of course, you may have host- or network- level firewall rules that prevent this access in any case.)
When you specify an ip address in the port publishing specification, like 127.0.0.1:8080:8080, then the listening ports are bound explicitly to that interface.
If your listening ports are bound only to the loopback interface, 127.0.0.1, then only clients on your local machine will be able to connect -- from the perspective of devices elsewhere on the network, those ports aren't available.
Which configuration makes sense depends (a) on what you want to do (maybe you want to expose a service that will be accessible to systems other than your local machine), (b) what your local network looks like, and (c) your level of risk aversion.

Related

container port mapping concept confusion

I know I can map host port to container port in Docker command or in Dockerfile or in docker-compose.yml. I have no problem there, I know how to do that too.
For example, I have the following container:
$ docker container ls
ID COMMAND PORTS
84.. "python app.py" 0.0.0.0:5000->5000/tcp
I know it means the host port 5000 is mapped to container port 5000.
My question is only on the 0.0.0.0 part. I have done some study, it is said that 0.0.0.0:5000 means map port 5000 of all interfaces on host.
I understand the 5000 port on host, but I don't get "all interfaces on host", what does it mean exactly? Could someone please elaborate for me? Does it mean all network interfaces on the host? What "all interface" this "0.0.0.0" refers to exactly?
Your physical hardware can have more than one network interface. In this day and age you likely have a wireless Ethernet connection, but you could also have a wired Ethernet connection, or more than one of them, or some kind of other network connection. On a Linux host if you run ifconfig you will likely have at least two interfaces, your "real" network connection and a special "loopback" connection that only reaches the host. (And this is true inside a container as well, except that the "loopback" interface only reaches the container.)
When you set up a network listener, using the low-level bind(2) call or any higher-level wrapper, you specify not just the port you're listening on but also the specific IP address. If you listen on 127.0.0.1, your process will be only reachable from the loopback interface, but not off-box. If you have, say, two network connections where one connects to an external network and one an internal one, you can specify the IP address of the internal network and have a service that's not accessible from the outside world.
This is where 0.0.0.0 comes in. It's possible to write code that scans all of the network interfaces and separately listens to all of them, but 0.0.0.0 is a shorthand that means "all interfaces".
In Docker, this comes up in three ways:
The default -p listen address is 0.0.0.0. On a typical developer system, you might want to explicitly specify -p 127.0.0.1:8080:8080 to only have your service accessible from the physical host.
If you do have a multi-homed system, you can use -p 10.20.30.40:80:8080 to publish a port on only one network interface.
Within a container, the main container process generally must listen to 0.0.0.0. Since each container has its own private localhost, listening on 127.0.0.1 (a frequent default for development servers) means the process won't be accessible from other containers or via docker run -p.

How to expose a Docker container port to one specific Docker network only, when a container is connected to multiple networks?

From the Docker documentation:
--publish or -p flag. Publish a container's port(s) to the host.
--expose. Expose a port or a range of ports.
--link. Add link to another container. Is a legacy feature of Docker. It may eventually be removed.
I am using docker-compose with several networks. I do not want to publish any ports to the host, yet when I use expose, the port is then exposed to all the networks that container is connected to. It seems that after a lot of testing and reading I cannot figure out how to limit this to a specific network.
For example in this docker-compose file with where container1 joins the following three networks: internet, email and database.
services:
container1:
networks:
- internet
- email
- database
Now what if I have one specific port that I want to expose to ONLY the database network, so NOT to the host machine and also NOT to the email and internet networks in this example? If I would use ports: on container1 it is exposed to the host or I can bind it to a specific IP address of the host. *I also tried making a custom overlay network, giving the container a static IPv4 address and trying to set the ports in that format in ports: like - '10.8.0.3:80:80', but that also did not work because I think the binding can only happen to a HOST IP address. If i use expose: on container1 the port will be exposed to all three networks: internet, email and database.
I am aware I can make custom firewall ruling but it annoys me that I cannot write such simple config in my docker-compose file. Also, maybe something like 80:10.8.0.3:80 (HOST_IP:HOST_PORT:CONTAINER_IP:CONTAINER_PORT) would make perfect sense here (did not test it).*
Am I missing something or is this really not possible in Docker and Docker-compose?
Also posted here: https://github.com/docker/compose/issues/8795
No, container to container networking in docker is one-size-fits-many. When two containers are on the same network, and ICC has not been disabled, container-to-container communication is unrestricted. Given Docker's push into the developer workflow, I don't expect much development effort to change this.
This is handled by other projects like Kubernetes by offloading the networking to a CNI where various vendors support networking policies. This may be iptables rules, eBPF code, some kind of sidecar proxy, etc to implement it. But it has to be done as the container networking is setup, and docker doesn't have the hooks for you to implement anything there.
Perhaps you could hook into docker events and run various iptables commands for containers after they've been created. The application could also be configured to listen on the specific IP address for the network it trusts, but this requires injecting the subnet you trust and then looking up your container IP in your entrypoint, non-trivial to script up, and I'm not even sure it would work. Otherwise, this is solved by either restructuring the application so components that need to be on a less secure network are minimized, by hardening the sensitive ports, or switching the runtime over to something like Kubernetes with a network policy.
Things that won't help:
Removing exposed ports: this won't help since expose is just documentation. Changing exposed ports doesn't change networking between containers, or between the container and host.
Links: links are a legacy feature that adds entries to the host file when the container is created. This was replaced by creating networks with DNS resolution of other containers.
Removing published ports on the host: This doesn't impact container to container communication. The published port with -p creates a port forward from the host to the container, which you do want to limit, but containers can still communicate over a shared network without that published port.
The answer to this for me was to remove the -p command as that binds the container to the host and makes it available outside the host.
If you don't specify -p options. The container is available on all the networks it is connected to. On whichever port or ports the application is listening on.
It seems the -P forces the container on to the host and binds it to the port specified.
In your example if you don't use -p when staring "container1". "container1" would be available to the networks: internet, email, database with all its ports but not outside the host.

docker-compose networking and publishing ports

I'm trying to better understand docker networking, but I'm confused by the following:
I spin up 2 contains via docker-compose (client, api). When I do this, a new network is created, myapp_default, and each container joins this network. The network is a bridge network, and it's at 172.18.0.1. The client is at 172.18.0.2 and the api is at 172.18.0.3.
I can now access the client at 172.18.0.2:8080 and the api at 172.18.0.3:3000 -- this makes total sense. I'm confused when I publish ports in docker-compose: 8080:8080 on the client, and 3000:3000 on the api.
Now I can access the containers from:
Client at 172.18.0.1:8080, 172.18.0.2:8080, and on the docker0 network at 172.17.0.1:8080
API at 172.18.0.1:3000, 172.18.0.3:8080, and on the docker0 network at 172.17.0.1:3000
1) Why can I access the client and api via the docker0 network when I publish ports?
2) Why can I connect to containers via 172.17.0.1 and 172.18.0.1 at all?
You can only access the container-private IP addresses because you're on the same native-Linux host as the Docker daemon. This doesn't work in any other environment (different hosts, MacOS or Windows hosts, environments like Docker Toolbox where Docker is in a VM) and even using docker inspect to find these IP addresses usually isn't a best practice.
When you publish ports they are accessible on the host at those ports. This does work in every environment (in Docker Toolbox "the host" is the VM) and is the recommended way to access your containers from outside Docker space. Unless you bind to a specific address, the containers are accessible on every host interface and every host IP address; that includes the artificial 172.17.0.1 etc. that get created with Docker bridge networks.
Publishing ports is in addition to the other networking-related setup Docker does; it doesn't prevent you from reaching the containers by other paths.
If you haven't yet, you should also read Networking in Compose in the Docker documentation. Whether you publish ports or not, you can use the names in the docker-compose.yml file like client and api as host names, connecting the the (unmapped) port the actual server processes are listening on. Between this functionality and what you get from publishing ports you don't ever actually need to directly know the container-private IP addresses.

Docker app not available with host IP when using automatic port mapping

I am deploying a eureka server on a VM(say host external IP is a.b.c.d) as a docker image. Trying this in 2 ways.
1.I am running the docker image without explicit port mapping : docker run -p 8671 test/eureka-server
Then running docker ps command shows the port mapping as : 0.0.0.0:32769->8761/tcp
Try accessing the eureka server from outside of the VM with http://a.b.c.d:32769 , its not available.
2.I am running the docker image with explicit port mapping : docker run -p 8761:8761 test/eureka-server
Then running docker ps command shows the port mapping as : 0.0.0.0:8761->8761/tcp
Try accessing the eureka server from outside of the VM with http://a.b.c.d:8761 , its available.
Why in the first case the eureka server is not available from out side the host machine even if there is a random port(32769) assigned by docker.
Is it necessary to have explicit port mapping to have docker app available from external network ?
Since you're looking for access from the outside world to the host via the mapped port you'll need to ensure that the source traffic is allowed to reach that port on the host and given protocol. I'm not a network security specialist, but I'd suggest that opening up an entire range of ports simply because you don't know which port docker will pick would be a bad idea. If you can, I'd say pick a port and explicitly map it and ensure the firewall allows access to that port from the appropriate source address(es) e.g. ALLOW TCP/8671 in from 10.0.1.2/32 as an example - obviously your specific address range will vary on your network configuration. Docker compose may help you keep this consistent (as will other orchestration technologies like Kubernetes). In addition if you use cloud hosting services like AWS you may be able to leverage VPC security groups to help you whitelist source traffic to the port without knowing all possible source IP addresses ahead of time.
You either have the firewall blocking this port, or from wherever you are making the requests, for certain ports your outgoing traffic is disabled, so your requests never leave your machine.
Some companies do this. They leave port 80, 443, and couple of more for their intranet, and disable all other destination ports.

Make docker machine available under host name in Windows

I'm trying to make a docker machine available to my Windows by a host name. After creating it like
docker-machine create -d virtualbox mymachine
and setting up a docker container that exposes the port 80, how can I give that docker machine a host name such that I can enter "http://mymachine/" into my browser to load the website? When I change "mymachine" to the actual IP address then it works.
There is an answer to this question but I would like to achieve it without an entry in the hosts file. Is that possible?
You might want to refer to docker documentaion:
https://docs.docker.com/engine/userguide/networking/#exposing-and-publishing-ports
You expose ports using the EXPOSE keyword in the Dockerfile or the
--expose flag to docker run. Exposing ports is a way of documenting which ports are used, but does not actually map or open any ports.
Exposing ports is optional.
You publish ports using the --publish or --publish-all flag to docker
run. This tells Docker which ports to open on the container’s network
interface. When a port is published, it is mapped to an available
high-order port (higher than 30000) on the host machine, unless you
specify the port to map to on the host machine at runtime. You cannot
specify the port to map to on the host machine when you build the
image (in the Dockerfile), because there is no way to guarantee that
the port will be available on the host machine where you run the
image.
I also suggest reviewing the -P flag as it differs from the -p one.
Also i suggest you try "Kitematic" for Windows or Mac, https://kitematic.com/ . It's much simpler (but dont forget to commit after any changes!)
Now concerning the network in your company, it has nothing to do with docker, as long as you're using docker locally on your computer it wont matter what configuration your company set. Even you dont have to change any VM network config in order to expose things to your local host, all comes by default if you're using Vbox ( adapter 1 ==> NAT & adapter 2 ==> host only )
hope this is what you're looking for
If the goal is to keep it as simple as possible for multiple developers, localhost will be your best bet. As long as the ports you're exposing and publishing are available on host, you can just use http://localhost in the browser. If it's a port other than 80/443, just append it like http://localhost:8080.
If you really don't want to go the /etc/hosts or localhost route, you could also purchase a domain and have it route to 127.0.0.1. This article lays out the details a little bit more.
Example:
dave-mbp:~ dave$ traceroute yoogle.com
traceroute to yoogle.com (127.0.0.1), 64 hops max, 52 byte packets
1 localhost (127.0.0.1) 0.742 ms 0.056 ms 0.046 ms
Alternatively, if you don't want to purchase your own domain and all developers are on the same network and you are able to control DHCP/DNS, you can setup your own DNS server to include a private route back to 127.0.0.1. Similar concept to the Public DNS option, but a little more brittle since you might allow your devs to work remote, outside of a controlled network.
Connecting by hostname requires that you go through hostname to IP resolution. That's handled by the hosts file and falls back to DNS. This all happens before you ever touch the docker container, and docker machine itself does not have any external hooks to go out and configure your hosts file or DNS servers.
With newer versions of Docker on windows, you run containers with HyperV and networking automatically maps ports to localhost so you can connect to http://localhost. This won't work with docker-machine since it's spinning up virtualbox VM's without the localhost mapping.
If you don't want to configure your hosts file, DNS, and can't use a newer version of docker, you're left with connecting by IP. What you can do is use a free wildcard DNS service like http://xip.io/ that maps any name you want, along with your IP address, back to that same IP address. This lets you use things like a hostname based reverse proxy to connect to multiple containers inside of docker behind the same port.
One last option is to run your docker host VM with a static IP. Docker-machine doesn't support this directly yet, so you can either rely on luck to keep the same IP from a given range, or use another tool like Vagrant to spin up the docker host VM with a static IP on the laptop. Once you have a static IP, you can modify the host file once, create a DNS entry for every dev, or use the same xip.io URL, to access the containers each time.
If you're on a machine with Multicasting DNS (that's Bonjour on a Mac), then the approach that's worked for me is to fire up an Avahi container in the Docker Machine vbox. This lets me refer to VM services at <docker-machine-vm-name>.local. No editing /etc/hosts, no crazy networking settings.
I use different Virtualbox VMs for different projects for my work, which keeps a nice separation of concerns (prevents port collisions, lets me blow away all the containers and images without affecting my other projects, etc.)
Using docker-compose, I just put an Avahi instance at the top of each project:
version: '2'
services:
avahi:
image: 'enernoclabs/avahi:latest'
network_mode: 'host'
Then if I run a webserver in the VM with a docker container forwarding to port 80, it's just http://machine-name.local in the browser.
You can add a domain name entry in your hosts file :
X.X.X.X mymachine # Replace X.X.X.X by the IP of your docker machine
You could also set up a DNS server on your local network if your app is meant to be reachable from your coworkers at your workplace and if your windows machine is meant to remain up as a server.
that would require to make your VM accessible from local network though, but port forwarding could then be a simple solution if your app is the only webservice running on your windows host. (Note that you could as well set up a linux server to avoid using docker-machine on windows, but you would still have to set up a static IP for this server to ensure that your domain name resolution works).
You could also buy your own domain name (or get a free one) and assign it your docker-machine's IP if you don't have rights to write in your hosts file.
But these solution may not work anymore after some time if app host doesn't have a static IP and if your docker-machine IP changes). Not setting up a static IP doesn't imply it will automatically change though, there should be some persistence if you don't erase the machine to create a new one, but that wouldn't be guaranteed either.
Also note that if you set up a DNS server, you'd have to host it on a device with a static IP as well. Your coworkers would then have to configure their machine to use this one.
I suggest nginx-proxy. This is what I use all the time. It comes in especially handy when you are running different containers that are all supposed to answer to the same port (e.g. multiple web-services).
nginx-proxy runs seperately from your service and listens to docker-events to update it's own configuration. After you spun up your service and query the port nginx-proxy is listening to, you will be redirected to your service. Therefore you either need to start nginx-proxy with the DEFAULT_HOST flag or send the desired host as header param with the request.
As I am running this only with plain docker, I don't know if it works with docker-machine, though.
If you go for this option, you can decide for a certain domain (e.g. .docker) to be completely resolved to localhost. This can be either done company-wide by DNS, locally with hosts file or an intermediate resolver (the specific solution depends on your OS, of course). If you then try to reach http://service1.docker nginx-proxy will route to the container that has then ENV VIRTUAL_HOST=service1.docker. This is really convenient, because it only needs one-time setup and is from then on dynamic.

Resources