Daemon to monitor parameters of docker containers and stop ones not obeying defined rules.
3.6K
Docker enforcer audits containers running on a shared docker host. The aim of docker enforcer is to stop containers running on a single host, but not obeying rules configured by the host's administrator. These rules may restrict values used as container's parameters or values reported by container's performance metrics.
It's the easiest to run docker enforcer as a container. Before starting, pay attention to a few facts:
Docker-enforcer works by checking a set of rules against the containers that are running on the docker
host. Each of the rules is applied to data about each container. Rules indicate which container should
be killed, so if any of the rules returns True, the container will be stopped (unless it's on the
white list).
The rules file must include a python list of dictionaries, where each of the dictionaries is a single
rule. The rule includes its name and a lambda function, which contains evaluation logic. Container's
data that can be checked by a rule includes the following properties:
position - the sequence number of the container on the list of all containers, sorted by the start
date,params - a dictionary of parameters used to start this container (for example with docker run),metrics - a dictionary of performance metrics reported by the docker daemon for the container.The rules file is evaluated against all running containers each CHECK_INTERVAL_S seconds. Also, all
rules are evaluated against a single container, when the container is being started.
The very basic rules file, that doesn't stop any container looks like this (this is also the default
list set):
rules = [
{
"name": "always false",
"rule": lambda c: False
}
]
This creates just a single rule, which has name "always false" and matches no container (so, no container will be ever stopped because of this rule).
{
"name": "must have memory limit",
"rule": lambda c: c.params['hostconfig']['memory'] == 0
}
{
"name": "must have CPU limit",
"rule": lambda c: c.params['hostconfig']['cpuquota'] == 0
}
{
"name": "no more than 3",
"rule": lambda c: c.position >= 3
}
/opt/mnt1 or /opt/mnt2 on the host: {
"name": "uses valid dirs for volumes",
"rule": lambda c: False if c.params['hostconfig']['binds'] is None else not all([b.startswith("/opt/mnt1") or b.startswith("/opt/mnt2") for b in c.params['hostconfig']['binds']])
}
rules = [
{
"name": "can't use over 1GB of RAM",
"rule": lambda c: c.metrics['memory_stats']['usage'] > 1 * 1024 ** 3
}
]
You can see an example of the full list of parameters available (except position) in the file
test_helpers.py.
All the configuration options are loaded from environment variables, which makes them pretty easy to
configure when running as docker container (by passing to docker with -e KEY=VAL added to the run
command above).
The following options are supported (values after '=' below are the defaults):
10.20.30.40:2375c.params propertyc.metrics property when this is disabled; using metrics based rules requires running in
periodic modeTrue and denies when FalseIf the above usage of WHITE_LIST and IMAGE_WHITE_LIST is still not elastic enough for your needs, you
can implement your custom evaluation rules for the whitelist, like for rules.
This time, the evaluation lambda takes 2 arguments: lambda container, violated_rule_name and it must
return bool. container is a full container info, like in Rules[], while the 2nd argument provides
the name of the violated rule. If any of custom whitelist lambdas returns True, the container won't be
stopped.
When a violation is detected, docker enforcer logs information about it and stops the container (only
in Kill mode). If you want to run some additional logic on this event, you can use a similar mechanism
as with the rules file. You can overwrite the file triggers/triggers.py and inject your own logic that
will be triggered on violation detection event. You can see an example trigger that does additional
logging in the default triggers/triggers.py file.
This feature is available only when running in Authz plugin mode.
When running in Authz plugin mode, Docker Enforcer receives information from docker about all API calls
made by users to docker daemon. Normally, only requests creating or changing containers are processed
and passed for validation with rules.py rules file. However, you can also use Docker Enforcer to
authorize any API call made to docker. To use this, you need to implement another set of python lambdas
working as validation rules triggered for each request. You can see the default
request_rules/request_rules.py file for the default "always OK" rule,
but you can override it and include some custom ones. For example, the one below (included in
tests) forbids any docker cp commands to containers with name starting with "test":
cp_request_rule_regexp = re.compile("^/v1\.[23]\d/containers/test[a-zA-Z0-9_]*/archive$")
cp_request_rule = {"name": "cp not allowed", "rule": lambda r, x=cp_request_rule_regexp:
r['RequestMethod'] in ['GET', 'HEAD'] and x.match(r['ParsedUri'].path)}
Docker Enforcer supports different run modes. In general, the modes above can be mixed, but you shouldn't run "Events mode" and "Authz plugin mode" together. The supported modes are:
In this mode, Docker Enforcer has a configured time period. When it passes, the enforcer connects to docker daemon, fetches the list of all currently running containers and then runs all the rules against every container on the list.
In this mode, Docker Enforcer listens for container lifetime events from the docker daemon. Each time you run or modify your container, you pass it a set of configuration options. This situation is also reported by docker daemon for anyone willing to act on it. Docker enforcer listens for these events and then runs all rules against the single container related to the event signalled by the docker daemon.
In this mode, Docker Enforcer runs as a docker authorization plugin. As a result, your users won't even be able to complete an API call that has parameters that don't validate with your rules. In that case, an error message is returned to the user and the call is not executed by docker. This mode additionally allows you to use request rules for low level auditing of API calls. This requires additional configuration of docker daemon, as below:
/etc/docker/plugins/enforcer.spec with this single line:tcp://127.0.0.1:5000
/etc/docker/daemon.json and be sure it includes the following JSON line:{
"authorization-plugins": ["enforcer"]
}
In production, you might want a setup, that allows you to test new compliance rules on a production system, but without hurting anybody by mistake, while enforcing your battle tested rules at the same time. The solution is to run 2 docker enforcers at the same time:
Put your rules.py file into a directory, for example rules_dir. Be sure to check your permissions on
this file, the code inside will be executed inside the enforcer's container! Then, run (minimal command):
docker run -d --name docker_enforcer \
-p 8888:8888 \
--privileged \
-v /rules_dir:/opt/docker_enforcer/rules \
-v /var/run:/var/run \
tailoredcloud/docker-enforcer
After the successful run, a simple web API will be exposed to show current rules and status (see below).
You can access http://localhost:8888/rules to see the list of rules configured. This should be in sync
with the rules file you passed to the container.
Please follow the following steps:
/opt/de)pip install -r requirements-prod.txtrules.py, triggers.py and request_rules.py files with your custom rules/opt/de, create a file named environment.conf and put there any required
configuration options formatted one option per line as OPTION=value/etc/systemd/system/docker_enforcer.service using
this template. Be sure to adjust DE_PATH to your service location
(/opt/de in our example) and GUNICORN_PATH to the place where pip installed gunicorn for you.docker.service in systemd know, that now docker-enforcer is "wanted" to be
running before docker starts. Create a directory /etc/systemd/system/docker.service.d/ and a file
docker.conf in it. Put this into docker.conf file:[Unit]
Wants=docker_enforcer.service
systemctl daemon-reload
systemctl start docker_enforcer
systemctl restart docker
Docker enforcer exposes a simple HTTP API on the port 8888. If the "Accept:" header in client's request includes HTML, a human-friendly JSON will be returned. Otherwise, plain text JSON is sent in response. This currently includes the following endpoints:
/ - shows statistics about containers stopped by docker enforcer; shows all detections since starting
the service/recent - shows statistics about containers stopped by docker enforcer in the most recent periodic
run; makes sense only when "RUN_PERIODIC" is True/metrics - exposes the number of containers stopped since launch in the
prometheus data format,/config - shows the current version and configuration options of the daemon/rules - allows you to view the configured set of rules,/request_rules - allows you to view the configured set of request rules,/triggers - allows you to view the configured set of triggers.Additionally, for / and /recent endpoints, you can append the following options in the URL (like:
http://localhost:8888/?show_all_violated_rules=1&show_image_and_labels=1):
Content type
Image
Digest
Size
60.7 MB
Last updated
about 7 years ago
docker pull tailoredcloud/docker-enforcer