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
6 changes: 5 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@ ENV ALLOW_RESTARTS=0 \
SYSTEM=0 \
TASKS=0 \
VERSION=1 \
VOLUMES=0
VOLUMES=0 \
DELETE=0 \
ALLOW_IMAGES_DELETE=0 \
ALLOW_NETWORKS_DELETE=1 \
ALLOW_CONTAINERS_DELETE=0
COPY docker-entrypoint.sh /usr/local/bin/
COPY haproxy.cfg /usr/local/etc/haproxy/haproxy.cfg.template
USER root
Expand Down
98 changes: 69 additions & 29 deletions haproxy.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -45,36 +45,76 @@ backend docker-events

frontend dockerfrontend
bind ${BIND_CONFIG}
http-request deny unless METH_GET || { env(POST) -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 }
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/containers/[a-zA-Z0-9_.-]+/stop } { env(ALLOW_STOP) -m bool }
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/auth } { env(AUTH) -m bool }
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/build } { env(BUILD) -m bool }
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/commit } { env(COMMIT) -m bool }
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/configs } { env(CONFIGS) -m bool }
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/containers } { env(CONTAINERS) -m bool }
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/distribution } { env(DISTRIBUTION) -m bool }
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/events } { env(EVENTS) -m bool }
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/exec } { env(EXEC) -m bool }
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/grpc } { env(GRPC) -m bool }
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/images } { env(IMAGES) -m bool }
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/info } { env(INFO) -m bool }
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/networks } { env(NETWORKS) -m bool }
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/nodes } { env(NODES) -m bool }
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/_ping } { env(PING) -m bool }
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/plugins } { env(PLUGINS) -m bool }
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/secrets } { env(SECRETS) -m bool }
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/services } { env(SERVICES) -m bool }
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/session } { env(SESSION) -m bool }
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/swarm } { env(SWARM) -m bool }
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/system } { env(SYSTEM) -m bool }
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/tasks } { env(TASKS) -m bool }
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/version } { env(VERSION) -m bool }
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/volumes } { env(VOLUMES) -m bool }
# --- Method ACLs ---
acl method_GET method GET
acl method_HEAD method HEAD
acl method_POST method POST
acl method_DELETE method DELETE
acl method_PUT method PUT
acl method_PATCH method PATCH

# --- Allow only enabled methods ---
http-request deny unless METH_GET || method_POST { env(POST) -m bool } || method_DELETE { env(DELETE) -m bool } || method_PUT { env(PUT) -m bool } || method_PATCH { env(PATCH) -m bool }

# --- Allowed endpoints ---
# GET, HEAD, PUT & PATCH
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/auth } { env(AUTH) -m bool } method_GET || method_HEAD || method_PUT || method_PATCH
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/build } { env(BUILD) -m bool } method_GET || method_HEAD || method_PUT || method_PATCH
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/commit } { env(COMMIT) -m bool } method_GET || method_HEAD || method_PUT || method_PATCH
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/configs } { env(CONFIGS) -m bool } method_GET || method_HEAD || method_PUT || method_PATCH
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/containers } { env(CONTAINERS) -m bool } method_GET || method_HEAD || method_PUT || method_PATCH
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/distribution } { env(DISTRIBUTION) -m bool } method_GET || method_HEAD || method_PUT || method_PATCH
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/events } { env(EVENTS) -m bool } method_GET || method_HEAD || method_PUT || method_PATCH
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/exec } { env(EXEC) -m bool } method_GET || method_HEAD || method_PUT || method_PATCH
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/grpc } { env(GRPC) -m bool } method_GET || method_HEAD || method_PUT || method_PATCH
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/images } { env(IMAGES) -m bool } method_GET || method_HEAD || method_PUT || method_PATCH
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/info } { env(INFO) -m bool } method_GET || method_HEAD || method_PUT || method_PATCH
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/networks } { env(NETWORKS) -m bool } method_GET || method_HEAD || method_PUT || method_PATCH
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/nodes } { env(NODES) -m bool } method_GET || method_HEAD || method_PUT || method_PATCH
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/_ping } { env(PING) -m bool } method_GET || method_HEAD || method_PUT || method_PATCH
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/plugins } { env(PLUGINS) -m bool } method_GET || method_HEAD || method_PUT || method_PATCH
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/secrets } { env(SECRETS) -m bool } method_GET || method_HEAD || method_PUT || method_PATCH
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/services } { env(SERVICES) -m bool } method_GET || method_HEAD || method_PUT || method_PATCH
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/session } { env(SESSION) -m bool } method_GET || method_HEAD || method_PUT || method_PATCH
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/swarm } { env(SWARM) -m bool } method_GET || method_HEAD || method_PUT || method_PATCH
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/system } { env(SYSTEM) -m bool } method_GET || method_HEAD || method_PUT || method_PATCH
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/tasks } { env(TASKS) -m bool } method_GET || method_HEAD || method_PUT || method_PATCH
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/version } { env(VERSION) -m bool } method_GET || method_HEAD || method_PUT || method_PATCH
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/volumes } { env(VOLUMES) -m bool } method_GET || method_HEAD || method_PUT || method_PATCH

# POST and DELETE
http-request allow if method_POST { 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 method_POST { path,url_dec -m reg -i ^(/v[\d\.]+)?/containers/[a-zA-Z0-9_.-]+/start } { env(ALLOW_START) -m bool }
http-request allow if method_POST { path,url_dec -m reg -i ^(/v[\d\.]+)?/containers/[a-zA-Z0-9_.-]+/stop } { env(ALLOW_STOP) -m bool }
http-request allow if method_POST { path,url_dec -m reg -i ^(/v[\d\.]+)?/auth } { env(AUTH) -m bool }
http-request allow if method_POST { path,url_dec -m reg -i ^(/v[\d\.]+)?/build } { env(BUILD) -m bool }
http-request allow if method_POST { path,url_dec -m reg -i ^(/v[\d\.]+)?/commit } { env(COMMIT) -m bool }
http-request allow if method_POST { path,url_dec -m reg -i ^(/v[\d\.]+)?/configs } { env(CONFIGS) -m bool }
http-request allow if method_POST { path,url_dec -m reg -i ^(/v[\d\.]+)?/containers } { env(CONTAINERS) -m bool }
http-request allow if method_DELETE { path,url_dec -m reg -i ^(/v[\d\.]+)?/containers }{ env(ALLOW_CONTAINERS_DELETE) -m bool } { env(CONTAINERS) -m bool }
http-request allow if method_POST { path,url_dec -m reg -i ^(/v[\d\.]+)?/distribution }{ env(DISTRIBUTION) -m bool }
http-request allow if method_POST { path,url_dec -m reg -i ^(/v[\d\.]+)?/events } { env(EVENTS) -m bool }
http-request allow if method_POST { path,url_dec -m reg -i ^(/v[\d\.]+)?/exec } { env(EXEC) -m bool }
http-request allow if method_POST { path,url_dec -m reg -i ^(/v[\d\.]+)?/grpc } { env(GRPC) -m bool }
http-request allow if method_POST { path,url_dec -m reg -i ^(/v[\d\.]+)?/images } { env(IMAGES) -m bool }
http-request allow if method_DELETE { path,url_dec -m reg -i ^(/v[\d\.]+)?/images } { env(ALLOW_IMAGES_DELETE) -m bool } { env(IMAGES) -m bool }
http-request allow if method_POST { path,url_dec -m reg -i ^(/v[\d\.]+)?/networks } { env(NETWORKS) -m bool }
http-request allow if method_DELETE { path,url_dec -m reg -i ^(/v[\d\.]+)?/networks } { env(ALLOW_NETWORKS_DELETE) -m bool } { env(NETWORKS) -m bool }
http-request allow if method_POST { path,url_dec -m reg -i ^(/v[\d\.]+)?/nodes } { env(NODES) -m bool }
http-request allow if method_POST { path,url_dec -m reg -i ^(/v[\d\.]+)?/_ping } { env(PING) -m bool }
http-request allow if method_POST { path,url_dec -m reg -i ^(/v[\d\.]+)?/plugins } { env(PLUGINS) -m bool }
http-request allow if method_POST { path,url_dec -m reg -i ^(/v[\d\.]+)?/secrets } { env(SECRETS) -m bool }
http-request allow if method_POST { path,url_dec -m reg -i ^(/v[\d\.]+)?/services } { env(SERVICES) -m bool }
http-request allow if method_POST { path,url_dec -m reg -i ^(/v[\d\.]+)?/session } { env(SESSION) -m bool }
http-request allow if method_POST { path,url_dec -m reg -i ^(/v[\d\.]+)?/swarm } { env(SWARM) -m bool }
http-request allow if method_POST { path,url_dec -m reg -i ^(/v[\d\.]+)?/system } { env(SYSTEM) -m bool }
http-request allow if method_POST { path,url_dec -m reg -i ^(/v[\d\.]+)?/tasks } { env(TASKS) -m bool }
http-request allow if method_POST { path,url_dec -m reg -i ^(/v[\d\.]+)?/version } { env(VERSION) -m bool }
http-request allow if method_POST { path,url_dec -m reg -i ^(/v[\d\.]+)?/volumes } { env(VOLUMES) -m bool }

# --- Default deny everything else ---
http-request deny
default_backend dockerbackend

default_backend dockerbackend
use_backend docker-events if { path,url_dec -m reg -i ^(/v[\d\.]+)?/events }
71 changes: 70 additions & 1 deletion tests/test_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,16 +72,85 @@ def test_network_post_permissions(proxy_factory):
allowed_calls = [
("network", "ls"),
("network", "create", "foo"),
("network", "rm", "foo"),
]
forbidden_calls = []
_check_permissions(allowed_calls, forbidden_calls)


def test_network_delete_permissions(proxy_factory):
with proxy_factory(NETWORKS=1, DELETE=1):
allowed_calls = [
("network", "rm", "foo"),
("network", "rm", "-f", "foobarfoo"),
]
forbidden_calls = [
("network", "create", "foobarfoo"),
]
_check_permissions(allowed_calls, forbidden_calls)


def test_network_delete_permissions_v2(proxy_factory):
with proxy_factory(NETWORKS=1, POST=1):
allowed_calls = [
("network", "create", "foobarfoo"),
]
forbidden_calls = [
("network", "rm", "foobarfoo"),
]
_check_permissions(allowed_calls, forbidden_calls)


def test_exec_permissions(proxy_factory):
with proxy_factory(CONTAINERS=1, EXEC=1, POST=1) as container_id:
allowed_calls = [
("exec", container_id, "ls"),
]
forbidden_calls = []
_check_permissions(allowed_calls, forbidden_calls)


def test_image_delete_permissions_v1(proxy_factory):
with proxy_factory(DELETE=1, ALLOW_IMAGES_DELETE=1, IMAGES=1, POST=1):
allowed_calls = [
("pull", "alpine"),
("image", "rmi", "alpine"),
]
forbidden_calls = []
_check_permissions(allowed_calls, forbidden_calls)


def test_image_delete_permissions_v2(proxy_factory):
with proxy_factory(IMAGES=1, POST=1, DELETE=1):
allowed_calls = [
("pull", "alpine"),
("image", "ls"),
("image", "inspect", "alpine"),
]
forbidden_calls = [
("image", "rmi", "alpine"),
("image", "rmi", "-f", "alpine"),
]
_check_permissions(allowed_calls, forbidden_calls)


def test_container_delete_permissions_v2(proxy_factory):
with proxy_factory(
CONTAINERS=1,
DELETE=1,
ALLOW_START=1,
ALLOW_CONTAINERS_DELETE=1,
IMAGES=1,
POST=1,
):
allowed_calls = [
("pull", "alpine"),
("container", "run", "-dt", "--rm", "--name", "alpine", "alpine"),
("container", "rm", "-f", "alpine"),
# ("image", "rmi", "alpine"),
# ("image", "rmi", "-f", "alpine"),
]
forbidden_calls = [
("image", "rmi", "alpine"),
("image", "rmi", "-f", "alpine"),
]
_check_permissions(allowed_calls, forbidden_calls)
Loading