mirror of
https://github.com/safedep/pmg.git
synced 2026-08-03 07:24:09 +02:00
docs: container traffic reaches the proxy via the bridge address
Measured. Binding the proxy to the Docker bridge instead of loopback is enough to make container traffic reach it, and needs no code change in either pmg or the agent. The agent reads the address from the proxy state file, so the redirect target follows. 127.0.0.1 container connection failed, never reached the proxy 172.17.0.1 curl exit 60, reached the proxy and refused the certificate Exit 60 is a certificate error, so TCP completed and TLS began. Host traffic was unaffected in the same run. CA trust inside the container stays unsolved and is probably unsolvable from the host, since a container has its own trust store and injecting into it requires whoever starts the container to mount it. For containers the guarantee therefore degrades to fail closed. Only the default bridge is covered. Compose and custom networks use other gateways, which needs bpf_get_netns_cookie to select a target per namespace. A non loopback bind also exposes the proxy, so it wants a firewall rule outside an isolated runner. SETUP.md gains an optional Docker section with the commands and the expected exit code, and Current Limits is corrected.
This commit is contained in:
@@ -500,12 +500,42 @@ Loopback is network namespaced. The cgroup hook is not.
|
||||
So "containers included" is half true. Seeing and rewriting work. The rewritten address has to be
|
||||
valid in the caller's network namespace, which `127.0.0.1` is not.
|
||||
|
||||
Options, none implemented:
|
||||
### Reachability is solved by the bind address
|
||||
|
||||
- Bind the proxy to the bridge address as well, and redirect containers to the bridge gateway.
|
||||
- Use `bpf_get_netns_cookie()` in the hook to pick a different target per namespace.
|
||||
Measured afterwards. Binding the proxy to the Docker bridge address rather than loopback is enough,
|
||||
and needs no change to the hook or the agent:
|
||||
|
||||
Container CA trust is a separate and later problem, since traffic never reaches the proxy today.
|
||||
```
|
||||
pmg proxy start --transparent --host 172.17.0.1 ...
|
||||
```
|
||||
|
||||
The agent reads the address from the proxy state file, so the redirect target follows automatically.
|
||||
One address serves both worlds, because the bridge gateway is a host interface as well as the
|
||||
container's route to the host.
|
||||
|
||||
| Proxy bound to | Container result | Meaning |
|
||||
|---|---|---|
|
||||
| `127.0.0.1` | connection failed | never reached the proxy |
|
||||
| `172.17.0.1` | curl exit 60 | reached the proxy, refused the certificate |
|
||||
|
||||
Exit 60 is a certificate error, so the connection completed TCP and entered the TLS handshake.
|
||||
Host traffic was unaffected in the same run: a host `npm install` still redirected and succeeded.
|
||||
|
||||
Do not bind `0.0.0.0`. The state file would record `0.0.0.0`, which is not a valid rewrite target.
|
||||
|
||||
### What remains
|
||||
|
||||
- **Only the default bridge.** Compose and custom networks use other gateways. Covering them
|
||||
properly needs `bpf_get_netns_cookie()` in the hook to select a target per namespace.
|
||||
- **A non loopback bind exposes the MITM proxy.** Acceptable on an isolated runner, otherwise it
|
||||
needs a firewall rule.
|
||||
- **CA trust inside the container is unsolved and may be unsolvable from the host.** A container has
|
||||
its own trust store, and nothing on the host reaches into it. Injecting the CA requires the
|
||||
process that starts the container to mount it. So for containers the guarantee degrades to fail
|
||||
closed: traffic is mediated, the install fails, nothing unanalysed enters. That is the correct
|
||||
security outcome and a poor usability one.
|
||||
|
||||
Note `docker pull` was already covered before any of this, since the daemon runs on the host.
|
||||
|
||||
## Validate Before Building
|
||||
|
||||
|
||||
+100
-2
@@ -491,6 +491,101 @@ The `grep` command above is the reliable check.
|
||||
|
||||
---
|
||||
|
||||
## Optional: Docker Containers
|
||||
|
||||
Skip this section if you do not use Docker.
|
||||
|
||||
The hook already sees traffic from containers.
|
||||
Container processes belong to a cgroup under the same root.
|
||||
The hook applies to them.
|
||||
|
||||
The problem is the address.
|
||||
`127.0.0.1` inside a container is the container's own loopback.
|
||||
The proxy is not there.
|
||||
The connection is rewritten and then goes nowhere.
|
||||
|
||||
### The change
|
||||
|
||||
Bind the proxy to the Docker bridge address instead of loopback.
|
||||
|
||||
Find the bridge address:
|
||||
|
||||
```bash
|
||||
ip -4 addr show docker0 | grep inet
|
||||
```
|
||||
|
||||
The usual value is `172.17.0.1`.
|
||||
|
||||
Start the proxy on that address:
|
||||
|
||||
```bash
|
||||
sudo -u pmg-proxy env HOME=/var/lib/pmg-proxy \
|
||||
pmg proxy start --daemon --transparent --host 172.17.0.1 --state $STATE
|
||||
```
|
||||
|
||||
No other change is needed.
|
||||
`pmgwatch` reads the address from the proxy state file.
|
||||
|
||||
This one address serves both worlds.
|
||||
A container reaches the host at `172.17.0.1`.
|
||||
The host also owns that address, so host programs still reach the proxy.
|
||||
|
||||
Do not use `--host 0.0.0.0`.
|
||||
The state file would then hold `0.0.0.0`, which is not a valid destination.
|
||||
|
||||
### Test it
|
||||
|
||||
```bash
|
||||
sudo docker run --rm curlimages/curl:latest \
|
||||
-4 -sS -m 20 -o /dev/null https://registry.npmjs.org/is-odd
|
||||
echo "exit: $?"
|
||||
```
|
||||
|
||||
Expect exit code `60`.
|
||||
The hook log shows a `REDIRECT` line for the container.
|
||||
|
||||
### Read the result
|
||||
|
||||
| Proxy address | curl exit | Meaning |
|
||||
|---|---|---|
|
||||
| `127.0.0.1` | connection failed | The container never reached the proxy |
|
||||
| `172.17.0.1` | `60` | The container reached the proxy and refused the certificate |
|
||||
|
||||
Exit code `60` means the certificate was not trusted.
|
||||
This is the expected result.
|
||||
The container has its own certificate list inside its own file system.
|
||||
The system trust store of the host is not visible inside a container.
|
||||
|
||||
Exit code `60` is a success for the visibility layer.
|
||||
The connection is now mediated.
|
||||
It fails at trust, not at the address.
|
||||
|
||||
### To make a container succeed
|
||||
|
||||
The person who starts the container must pass the certificate in:
|
||||
|
||||
```bash
|
||||
sudo docker run --rm \
|
||||
-v /var/lib/pmg-ebpf-poc/pmg-ca-bundle.pem:/etc/ssl/certs/ca-certificates.crt:ro \
|
||||
curlimages/curl:latest -4 -sS https://registry.npmjs.org/is-odd
|
||||
```
|
||||
|
||||
There is no way to do this from the host.
|
||||
A container cannot be given a certificate it was not started to accept.
|
||||
|
||||
### Limits
|
||||
|
||||
- Only the default bridge network works.
|
||||
Docker Compose and custom networks use other addresses such as `172.18.0.1`.
|
||||
One fixed address does not cover them.
|
||||
- The proxy is no longer on loopback only.
|
||||
Anything that can reach `172.17.0.1` can use it.
|
||||
Use this on an isolated machine, or add a firewall rule.
|
||||
- `docker pull` already worked before this change.
|
||||
The Docker daemon runs on the host, so the hook always saw it.
|
||||
|
||||
---
|
||||
|
||||
## Problems and Fixes
|
||||
|
||||
| Message | Cause | Fix |
|
||||
@@ -536,8 +631,11 @@ The POC does not handle these cases:
|
||||
`curl` chose IPv6, produced no hook event, and downloaded a package that PMG
|
||||
blocks over IPv4. npm happened to choose IPv4, but that is luck, not design.
|
||||
Use `curl -4` when testing. Closing this needs a `cgroup/connect6` program.
|
||||
- **Containers.** The hook sees container traffic. The container cannot reach
|
||||
the proxy, because `127.0.0.1` in a container is not the host.
|
||||
- **Containers.** The hook sees container traffic, and the container can reach
|
||||
the proxy once it is bound to the Docker bridge address. See the Docker
|
||||
section above. The container still does not trust the certificate, so the
|
||||
request fails at TLS. That is accepted for now. Only the default bridge
|
||||
network is covered.
|
||||
- **QUIC.** The hook does not block UDP port 443.
|
||||
- **Programs with their own certificate list.** PMG adds the certificate to the
|
||||
system trust store only. It does not change the configuration of any program.
|
||||
|
||||
Reference in New Issue
Block a user