Sargon authorizes requests to a dockerd
daemon using access control
lists defined in a LDAP database or in its configuration file.
Used with due proficiency, Sargon mitigates security risks that appear when
running dockerd
in a multi-user environment.
After cloning the repository, change to the source directory and run
make
To install the created binary, run (as root):
make install
By default, the sargon binary is installed to /usr/local/bin
. To
select another installation directory, use the BINDIR
or PREFIX
variable. The BINDIR
variable specifies the directory to install
sargon to. E.g. to install it to /usr/bin
, do
make install BINDIR=/usr/bin
Alternatively, you may use the PREFIX
variable, which specifies the
directory where bin
is located, e.g.:
make install PREFIX=/usr
Sargon provides sufficiently sane defaults that allow it to be run without explicit configuration file. These defaults are:
-
Upon startup, the PID of the running process is stored in file
/var/run/sargon.pid
. -
ACLs are stored in an LDAP database.
-
The LDAP configuration file is looked up in the following locations:
/etc/ldap.conf
/etc/ldap/ldap.conf
/etc/openldap/ldap.conf
- Anonymous user name is
ANONYMOUS
.
If these don't suit your needs, create the file /etc/docker/sargon.json
.
Using the configuration file description,
edit the Sargon settings to your liking.
The above defaults correspond to the following sargon.json
file:
{
"PidFile":"/var/run/sargon.pid",
"LdapConf":"/etc/ldap.conf:/etc/ldap/ldap.conf:/etc/openldap/ldap.conf",
"AnonymousUser":"ANONYMOUS"
}
In the discussion below we assume you will be using the LDAP database to store Sargon ACLs. If it's not the case and your intent is to keep all ACLs in the configuration file and disable LDAP altogether, use the following configuration file:
{
"LdapConf":""
}
(setting LdapConf
to an empty string disables LDAP).
In this case you can skip the section that follows.
Include the Sargon schema into your slapd
configuration.
Most slapd
installations nowadays use dynamic configuration
system. In
that case, import the sargon.ldif
file into your configuration:
ldapadd -f sargon.ldif
Depending on your setup, you may need additional options to ldapadd
If using the legacy configuration,
copy the file sargon.schema
to the schema subdirectory of your slapd
configuration directory. Depending on the installation, it is
/etc/openldap/schema
or /etc/ldap/schema
. Include it in your
slapd
configuration, by adding the following statement to your
slapd.conf
:
include /etc/openldap/schema/sargon.schema
Restart slapd
.
Create the root object for Sargon ACL hierarchy:
ldapadd <<EOF
dn: ou=sargon,dc=example,dc=com
objectClass: organizationalUnit
ou: sargon
description: root for Sargon (Docker ACL) objects.
EOF
Create the default policy entry. It's an ACL entry that will be used when no other object matches the request. The built-in Sargon defaults are very (perhaps even overly) strict, that's why such an entry is needed so that you can continue using docker while building and tuning your ACLs:
ldapadd <<EOF
dn: cn=default-policy,ou=sargon,dc=example,dc=com
cn: default-policy
objectClass: sargonACL
sargonUser: ANONYMOUS
sargonAllow: ALL
sargonOrder: 100
EOF
(don't forget to replace dc=example,dc=org
with your actual base dn.)
This entry allows anonymous users to issue any docker requests. The
sargonOrder
attribute ensures it will be placed at
the end of the constructed access control list.
Of course it is only a minimal example. You can modify this entry as needed or even remove it altogether.
An alternative to keeping the default in the LDAP database is storing it in the configuration file.
If you prefer to keep the default policy entry, or even entire ACL, in
the configuration file, use the ACL
attribute. The value of this
attribute is a list of access control entries stored as JSON object.
For example, the default policy discussed above is stored in the
configuration file as:
{
"ACL":[
{
"Id":"default policy",
"User":["ANONYMOUS"],
"Allow":["ALL"],
"Order":100
}
]
}
The rules for converting ACL objects from LDAP to JSON are:
- Replace
cn
attribute withId
. - Replace each
sargon*
attribute with its name without thesargon
prefix. E.g.sargonUser
becomesUser
, etc. Notice that LDAP attribute names are case-insensitive, whereas JSON attribute names aren't. Use the canonical attribute names when performing conversion. - Use scalar value if attribute is marked as (single) in the attribute list, otherwise use array.
- Upper-case
TRUE
andFALSE
become lower-case (e.g. for thesargonAllowPrivileged
attribute). - Ensure proper ordering of objects in the
ACL
array, either by usingOrder
attributes or by explicitly ordering the entries. Remember that Sargon will use the first entry that matches the request.
Add the following attribute to the file /etc/docker/daemon.json
:
"authorization-plugins": ["sargon"]
Otherwise, add the following option to your dockerd
command line:
--authorization-plugin=sargon
If you opt for the latter variant, the exact place where to apply it
depends on how dockerd
is started on your system. If your system
uses systemd
(which is the case for most modern GNU/Linux
distributions, such as Ubuntu, Debian, etc), first extract the actual
dockerd
command line:
systemctl show --property=ExecStart --value docker | sed -e 's/.*argv\[\]=//' -e 's/;.*//'
Then run
systemctl edit docker
In the text editor it starts, type the following:
[Service]
ExecStart=
ExecStart=COMMAND --authorization-plugin=sargon
where COMMAND is the command line you obtained in the previous step.
Notice the empty ExecStart=
at the start of the section. It is
mandatory.
Save your changes and exit the editor.
To illustrate the effect of Sargon ACLs on docker, let's add to the
LDAP database an object that will control what directories anonymous
users are allowed to mount in containers. Using your editor, create
the following ldif
file:
dn: cn=anon,ou=sargon,dc=example,dc=com
cn: anon
objectClass: sargonACL
sargonUser: ANONYMOUS
sargonMount: /var/lib/mounts/*
The sargonUser
attribute declares that this entry applies to
anonymous users only. The sargonMount
attribute says that only
subdirectories of /var/lib/mounts
on host machine are allowed to be
mounted in containers.
Add your changes to the database:
ldapadd -f file.ldif
Now, if you try to run
docker run -v /etc:/usr/local/etc debian:10
you will get the following error:
docker: Error response from daemon: authorization denied by plugin sargon: mounting /etc is not allowed.
If sargon
is running in trace mode (--trace
option), you will see
the following in its logs:
[TRACE] ANONYMOUS: binding to /etc is rejected by default policy
On the other hand, running
docker run -v /var/lib/mounts/src:/usr/src debian:10
will succeed. Sargon trace in this case will contain:
[TRACE] ANONYMOUS: binding to /var/lib/mounts/src is accepted by cn=anon,ou=sargon,dc=example,dc=com
When started, the program reads its configuration file, disconnects itself
from the controlling terminal and continues running in the background. Error
reporting goes to the syslog facility daemon
. Both short
(single-dash) and GNU-style long (double-dash) options are supported.
The following options are recognized:
-
-f
,--foreground
Run in the foreground. Send diagnostic output to the stderr.
-
-c
,-config=
FILERead configuration from FILE instead of the default
/etc/docker/sargon.json
-
-t
,--trace
Trace ACL entries and their effect.
-
-d
,--debug
Enable verbose debugging output.
-
-h
,--help
Produce a short command line usage summary and exit.
-
-v
,--version
Print program version, short copying information, and exit.
Sargon configuration is kept in JSON format in file /etc/docker/sargon.json
.
The following keywords are recognized:
-
PidFile
Name of the PID file. Defaults to
/var/run/sargon.pid
. -
LdapConf
Colon-separated list of LDAP configuration files to look for. The first of them that exists will be read. The default is
/etc/ldap.conf:/etc/ldap/ldap.conf:/etc/openldap/ldap.conf
To disable LDAP, set this attribute to an empty string.
-
LdapUser
Bind to LDAP using this DN. Defaults to empty string. This value overrides the
binddn
setting inldap.conf
file, which is the preferred way of configuring bind DN. -
LdapPass
Bind to LDAP with this password. Defaults to empty string. This value overrides the password obtained from the
bindpwfile
setting inldap.conf
file. -
LdapTLS
If
true
, start LDAP TLS session. -
AnonymousUser
If docker connection is not authenticated, use this string as the user name.
-
ACL
A list of ACL entries stored in JSON format. This list will be appended to the list obtained from LDAP before final sorting of entries, or used alone if LDAP database is disabled or is unreachable. This makes it useful for storing default policies.
After reading its main configuration file, sargon scans the LDAP
configuration path (see the LdapConf
variable above). The first file that
exists and is readable is read. The format of the file is described in
detail in ldap.conf(5).
The following keywords are recognized:
-
URI
ldap[si]://[name[:port]]URI of an LDAP server to which sargon should connect.
-
BASE
baseSpecifies the default base DN to use when performing ldap queries.
-
BINDDN
dnSpecifies the default bind DN to use when performing ldap operations.
-
BINDPWFILE
filenameUse the content of filename as the password for simple authentication. Note, that the file is read verbatim and is not parsed in any way. In particular, beware of trailing newlines.
-
TLS_CACERT
filenameSpecifies the file that contains certificates for all of the Certificate Authorities the client will recognize.
-
TLS_CACERTDIR
dirnameSpecifies the path of a directory that contains Certificate Authority certificates in separate individual files. The
TLS_CACERT
is always used beforeTLS_CACERTDIR
. -
TLS_CERT
filenameSpecifies the file that contains client certificate.
-
TLS_KEY
filenameSpecifies the file that contains the private key for the certificate stored in the TLS_CERT file.
-
TLS_RANDFILE
filenameSpecifies the file to obtain random bits from, instead of the default
/dev/urandom
or/dev/random
. -
TLS_REQCERT
levelSpecifies what checks to perform on server certificates in a TLS session, if any. The level is one of:
never
,allow
,try
,demand
, ortry
.
Privileges for docker users are defined in the LDAP database in form of
sargonACL
objects. Each such object defines privileges for a set of
users performing certain docker action on a set of servers. Each sargonACL
object must have the cn
attribute, uniquely identifying the object.
It may also have one or more of the following attributes. Except as marked
with (single), multiple attribute instances are allowed.
-
sargonUser
User to whom this entry applies. If the value begins with a percent sign, the rest of characters is treated as the name of a user group and the entry applies to all users in this group.
-
sargonHost
Host on which this entry takes effect. If the value starts with a plus sign, it is treated as the name of the NIS netgroup.
-
sargonAllow
Allowed action. The value must be one of the docker action keywords, or the word
ALL
(uppercase) matching all actions.
-
sargonDeny
Denied action. The value must be one of the docker action keywords, or the word
ALL
(uppercase) matching all actions. See below for a detailed discussion on howsargonAllow
andsargonDeny
policies operate.
-
sargonOrder
(single)An integer used to order multiple
sargonACL
entries. If not present, 0 is assumed.
-
sargonMount
Name of the directory on the host filesystem that is allowed for mounting inside a container. The value of this attribute is treated as a globbing pattern. Before use, it undergoes variable expansion: any variable references in form
$
V or${
V}
are replaced with the actual value of variable V. The following variables are defined:Variable Expands to uid
User ID gid
Group ID name
User name home
ordir
Home directory Undefined variables are left unexpanded.
For example:
-
sargonMount:/var/lib/mounts
Allow to mount only
/var/lib/mounts
-
sargonMount:/var/lib/mounts/*
Allow to mount any directories under
/var/lib/mounts
The value can end with a list of flags in parentheses. The following flags are recognized:
-
ro
Allow read-only mounting. Attempts to mount the directory for writing will be rejected.
-
globlex
Use lexical globbing: the
*
wildcard matches any sequence of characters, including directory separators (slashes) and the?
wildcard matches any character, including slash. This is the default. -
globpath
Use pathname globbing:
*
and?
don't match slash. -
globstar
Use star globbing. As with
globpath
neither*
nor?
will match a slash character. The**
wildcard is provided, which matches zero or more arbitrary characters, including slashes.
Some more examples:
-
sargonMount:/var/lib/mounts/*(ro,globpath)
Allow to mount only subdirectories of
/var/lib/mounts
and only for reading. -
sargonMount:/var/*/mounts/**(globstar)
Allow to mount directories located at any depth under the
mounts
directory in any subdirectory of/var
. Thus, mounting/var/lib/mounts/foo/bar
will be allowed, whereas mounting/var/lib/sub/mounts/foo/bar
will not.
-
-
sargonAllowPrivileged
(single)The word
TRUE
if the object allows creation of privileged containers.FALSE
otherwise.
-
sargonMaxMemory
(single)Maximum size of the memory the container is allowed to use. The value is an integer optionally suffixed with
K
,M
, orG
(case-insensitive).
-
sargonMaxKernelMemory
(single)Limit on kernel memory usage. The value is an integer optionally suffixed with
K
,M
, orG
(case-insensitive).
-
sargonAllowCapability
Name of the linux capability that is allowed to use with the
--cap-add
docker option. See capabilities(7), for a list of capability names. Names listed in this attribute are case-insensitive. TheCAP_
prefix is optional.
-
sargonNotBefore
A timestamp in the form
yyyymmddHHMMSSZ
that provides a start date/time for when this entry will be valid. Notice, that the timestamp must be in UTC. -
sargonNotAfter
A timestamp in the form
yyyymmddHHMMSSZ
that provides an expiration date/time after which this entry ceases to be valid. Notice, that the timestamp must be in UTC.
The following values can be used in sargonAllow
and sargonDeny
attributes:
-
BuildPrune
Delete builder cache. -
ConfigCreate
Create a config. -
ConfigDelete
Delete a config. -
ConfigInspect
Inspect a config. -
ConfigList
List configs. -
ConfigUpdate
Update a config. -
ContainerArchive
Get an archive of a filesystem resource in a container. -
ContainerArchiveInfo
Get information about files in a container. -
ContainerAttach
Attach to a container. -
ContainerAttachWebsocket
Attach to a container via a websocket. -
ContainerChanges
Get changes on a container’s filesystem. -
ContainerCreate
Create a container. -
ContainerDelete
Remove a container. -
ContainerExec
Create an exec instance. -
ContainerExport
Export a container. -
ContainerInspect
Inspect a container. -
ContainerKill
Kill a container. -
ContainerList
List containers. -
ContainerLogs
Get container logs. -
ContainerPause
Pause a container. -
ContainerPrune
Delete stopped containers. -
ContainerRename
Rename a container. -
ContainerResize
Resize a container TTY. -
ContainerRestart
Restart a container. -
ContainerStart
Start a container. -
ContainerStats
Get container stats based on resource usage. -
ContainerStop
Stop a container. -
ContainerTop
List processes running inside a container. -
ContainerUnpause
Unpause a container. -
ContainerUpdate
Update a container. -
ContainerWait
Wait for a container. -
DistributionInspect
Get image information from the registry. -
ExecInspect
Inspect an exec instance. -
ExecResize
Resize an exec instance. -
ExecStart
Start an exec instance. -
GetPluginPrivileges
Get plugin privileges. -
ImageBuild
Build an image. -
ImageCommit
Create a new image from a container. -
ImageCreate
Create an image. -
ImageDelete
Remove an image. -
ImageGet
Export an image. -
ImageGetAll
Export several images. -
ImageHistory
Get the history of an image. -
ImageInspect
Inspect an image. -
ImageList
List Images. -
ImageLoad
Import images. -
ImagePrune
Delete unused images. -
ImagePush
Push an image. -
ImageSearch
Search images. -
ImageTag
Tag an image. -
NetworkConnect
Connect a container to a network. -
NetworkCreate
Create a network. -
NetworkDelete
Remove a network. -
NetworkDisconnect
Disconnect a container from a network. -
NetworkInspect
Inspect a network. -
NetworkList
List networks. -
NetworkPrune
Delete unused networks. -
NodeDelete
Delete a node. -
NodeInspect
Inspect a node. -
NodeList
List nodes. -
NodeUpdate
Update a node. -
PluginCreate
Create a plugin. -
PluginDelete
Remove a plugin. -
PluginDisable
Disable a plugin. -
PluginEnable
Enable a plugin. -
PluginInspect
Inspect a plugin. -
PluginList
List plugins. -
PluginPull
Install a plugin. -
PluginPush
Push a plugin. -
PluginSet
Configure a plugin. -
PluginUpgrade
Upgrade a plugin. -
PutContainerArchive
Extract an archive of files or folders to a directory in a container. -
SecretCreate
Create a secret. -
SecretDelete
Delete a secret. -
SecretInspect
Inspect a secret. -
SecretList
List secrets. -
SecretUpdate
Update a Secret. -
ServiceCreate
Create a service. -
ServiceDelete
Delete a service. -
ServiceInspect
Inspect a service. -
ServiceList
List services. -
ServiceLogs
Get service logs. -
ServiceUpdate
Update a service. -
Session
Initialize interactive session. -
SwarmInit
Initialize a new swarm. -
SwarmInspect
Inspect swarm. -
SwarmJoin
Join an existing swarm. -
SwarmLeave
Leave a swarm. -
SwarmUnlock
Unlock a locked manager. -
SwarmUnlockkey
Get the unlock key. -
SwarmUpdate
Update a swarm. -
SystemAuth
Check auth configuration. -
SystemDataUsage
Get data usage information. -
SystemEvents
Monitor events. -
SystemInfo
Get system information. -
SystemPing
Ping. -
SystemVersion
Get version. -
TaskInspect
Inspect a task. -
TaskList
List tasks. -
TaskLogs
Get task logs. -
VolumeCreate
Create a volume. -
VolumeDelete
Remove a volume. -
VolumeInspect
Inspect a volume. -
VolumeList
List volumes. -
VolumePrune
Delete unused volumes.
When authorizing incoming requests, sargon uses the following algorithm:
- Create LDAP filter with the user name and the names of the groups the
user belongs to.
For example, if the requesting user name is
smt
, and this user is member of the groupsstaff
,docker
, andwheel
, then the LDAP filter will be:
(&(objectClass=sargonACL)
(|(sargonUser=smt)
(sargonUser=ALL)
(sargonUser=%staff)
(sargonUser=%docker)
(sargonUser=%wheel)))
Notice, that (1) the filter string is split in multiple indented lines
for readability, and (2) the filter normally contains conditions that
control validity of the entry using the sargonNotBefore
and
sargonNotAfter
attributes. These conditions are omitted for clarity.
-
Execute LDAP query, get the response.
-
Iterate over the returned
sargonACL
objects, selecting only those with the value ofsargonHost
attribute matching the server hostname, or (if the value starts with+
) with the netgroup that matches the(host,user,domain)
triplet.To match the netgroup, the libc function innetgr(3) is used.
-
Sort the remaining entries by the value of their
sargonOrder
attribute in ascending order. -
Start with the first returned object.
-
If the requested docker action is explicitly listed in one of its
sargonAllow
attributes, go to step 9. -
Otherwise, if the object has one or more
sargonDeny
attributes and one of these contains the requested action or the meta-actionALL
, then deny the request. -
Advance to the next object, and restart from step 6.
-
Unless the requested action is
ContainerCreate
orVolumeCreate
, authorize the request. -
For
VolumeCreate
requests, check if the requested mountpoint satisfies thesargonMount
attribute. Authorize the request is so and reject it otherwise.
The steps below are followed when processing ContainerCreate
requests
-
If creation of a privileged container is requested, consult the
sargonAllowPrivileged
attribute. If its value isFALSE
, deny the request. Otherwise, advance to the next step. -
If any additional linux capabilities are requested, check if all of them are listed in
sargonAllowCapability
attributes. If not, deny the request. -
Check the requested binds and mounts. Check each source directory against each
sargonMount
attribute. If the directory matches, mounting is allowed. Otherwise, deny the request. -
If the requested maximum memory is greater than the value of the
sargonMaxMemory
attribute, the request is denied. -
If the requested maximum kernel memory is greater than the value of the
sargonMaxKernelMemory
attribute, the request is denied. -
Otherwise, the request is authorized.