Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
FROM haproxy:3.2.4-alpine

EXPOSE 2375
ENV ALLOW_RESTARTS=0 \
ENV ALLOW_DELETE=0 \
ALLOW_EXEC=0 \
ALLOW_KILL=0 \
ALLOW_RESTARTS=0 \
ALLOW_STOP=0 \
ALLOW_START=0 \
AUTH=0 \
Expand Down
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,16 @@ extremely critical but can expose some information that your service does not ne
- `ALLOW_RESTARTS` (containers/`id`/`stop`|`restart`|`kill`)
- `ALLOW_PAUSE` (containers/`id`/`pause`)
- `ALLOW_UNPAUSE` (containers/`id`/`unpause`)
- `ALLOW_EXEC` (containers/`id`/`exec`) — required IN ADDITION to `CONTAINERS=1` to create
new exec instances. Without it, even with `CONTAINERS=1 POST=1`, `docker exec` is denied.
- `ALLOW_KILL` (containers/`id`/`kill`) — required IN ADDITION to `CONTAINERS=1` to kill a
container. Without it, `docker kill` is denied even with `CONTAINERS=1 POST=1`.
- `ALLOW_DELETE` (`DELETE` containers/`id`) — required IN ADDITION to `CONTAINERS=1` to
remove a container. Without it, `docker rm` is denied even with `CONTAINERS=1 POST=1`.
- `DISTRIBUTION`
- `EXEC`
- `EXEC` — controls only `/exec/{id}/start|resize|inspect` (operations on
*existing* exec sessions). Creation of new exec instances via
`POST /containers/{id}/exec` is controlled by `ALLOW_EXEC` (above).
- `GRPC`
- `IMAGES`
- `INFO`
Expand Down
8 changes: 8 additions & 0 deletions haproxy.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ frontend dockerfrontend
bind ${BIND_CONFIG}
http-request deny unless METH_GET || { env(POST) -m bool }

# Granular container-mutating operations. Checked BEFORE generic CONTAINERS
# to keep them disabled by default even when CONTAINERS=1 is set — same
# pattern as the existing ALLOW_START / ALLOW_STOP / ALLOW_RESTARTS. See
# https://github.com/Tecnativa/docker-socket-proxy/issues/114.
http-request deny if { path,url_dec -m reg -i ^(/v[\d\.]+)?/containers/[a-zA-Z0-9_.-]+/exec } !{ env(ALLOW_EXEC) -m bool }
http-request deny if { path,url_dec -m reg -i ^(/v[\d\.]+)?/containers/[a-zA-Z0-9_.-]+/kill } !{ env(ALLOW_KILL) -m bool }
http-request deny if METH_DELETE { path,url_dec -m reg -i ^(/v[\d\.]+)?/containers/[a-zA-Z0-9_.-]+$ } !{ env(ALLOW_DELETE) -m bool }

# Allowed endpoints
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/containers/[a-zA-Z0-9_.-]+/((stop)|(restart)|(kill)) } { env(ALLOW_RESTARTS) -m bool }
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/containers/[a-zA-Z0-9_.-]+/start } { env(ALLOW_START) -m bool }
Expand Down
40 changes: 39 additions & 1 deletion tests/test_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,47 @@ def test_network_post_permissions(proxy_factory):


def test_exec_permissions(proxy_factory):
with proxy_factory(CONTAINERS=1, EXEC=1, POST=1) as container_id:
# ALLOW_EXEC=1 is required IN ADDITION to CONTAINERS+EXEC+POST to actually
# create new exec sessions, see issue #114. EXEC controls only operations
# on already-created exec sessions (/exec/<id>/start|resize|inspect).
with proxy_factory(CONTAINERS=1, EXEC=1, POST=1, ALLOW_EXEC=1) as container_id:
allowed_calls = [
("exec", container_id, "ls"),
]
forbidden_calls = []
_check_permissions(allowed_calls, forbidden_calls)


def test_exec_denied_without_allow_exec(proxy_factory):
"""CONTAINERS=1 + POST=1 must NOT be enough to create new exec sessions.

Regression test for https://github.com/Tecnativa/docker-socket-proxy/issues/114.
EXEC controls /exec/<id>/start (existing sessions); creating a new exec
instance via POST /containers/<id>/exec requires ALLOW_EXEC.
"""
with proxy_factory(CONTAINERS=1, EXEC=1, POST=1) as container_id:
forbidden_calls = [
("exec", container_id, "ls"),
]
_check_permissions((), forbidden_calls)


def test_delete_denied_without_allow_delete(proxy_factory):
"""CONTAINERS=1 + POST=1 must NOT allow DELETE /containers/<id>.

Regression test for https://github.com/Tecnativa/docker-socket-proxy/issues/114.
"""
with proxy_factory(CONTAINERS=1, POST=1) as container_id:
forbidden_calls = [
("rm", "-f", container_id),
]
_check_permissions((), forbidden_calls)


def test_kill_denied_without_allow_kill(proxy_factory):
"""CONTAINERS=1 + POST=1 must NOT allow `docker kill`."""
with proxy_factory(CONTAINERS=1, POST=1) as container_id:
forbidden_calls = [
("kill", container_id),
]
_check_permissions((), forbidden_calls)