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
makeTo install the created binary, run (as root):
make installBy 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/binAlternatively, you may use the PREFIX variable, which specifies the
directory where bin is located, e.g.:
make install PREFIX=/usrSargon 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.ldifDepending 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.
EOFCreate 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
cnattribute withId. - Replace each
sargon*attribute with its name without thesargonprefix. E.g.sargonUserbecomesUser, 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
TRUEandFALSEbecome lower-case (e.g. for thesargonAllowPrivilegedattribute). - Ensure proper ordering of objects in the
ACLarray, either by usingOrderattributes 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=sargonIf 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 dockerIn 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.ldifNow, if you try to run
docker run -v /etc:/usr/local/etc debian:10you 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:10will 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,--foregroundRun in the foreground. Send diagnostic output to the stderr.
-
-c,-config=FILERead configuration from FILE instead of the default
/etc/docker/sargon.json -
-t,--traceTrace ACL entries and their effect.
-
-d,--debugEnable verbose debugging output.
-
-h,--helpProduce a short command line usage summary and exit.
-
-v,--versionPrint 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:
-
PidFileName of the PID file. Defaults to
/var/run/sargon.pid. -
LdapConfColon-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.confTo disable LDAP, set this attribute to an empty string.
-
LdapUserBind to LDAP using this DN. Defaults to empty string. This value overrides the
binddnsetting inldap.conffile, which is the preferred way of configuring bind DN. -
LdapPassBind to LDAP with this password. Defaults to empty string. This value overrides the password obtained from the
bindpwfilesetting inldap.conffile. -
LdapTLSIf
true, start LDAP TLS session. -
AnonymousUserIf docker connection is not authenticated, use this string as the user name.
-
ACLA 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:
-
URIldap[si]://[name[:port]]URI of an LDAP server to which sargon should connect.
-
BASEbaseSpecifies the default base DN to use when performing ldap queries.
-
BINDDNdnSpecifies the default bind DN to use when performing ldap operations.
-
BINDPWFILEfilenameUse 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_CACERTfilenameSpecifies the file that contains certificates for all of the Certificate Authorities the client will recognize.
-
TLS_CACERTDIRdirnameSpecifies the path of a directory that contains Certificate Authority certificates in separate individual files. The
TLS_CACERTis always used beforeTLS_CACERTDIR. -
TLS_CERTfilenameSpecifies the file that contains client certificate.
-
TLS_KEYfilenameSpecifies the file that contains the private key for the certificate stored in the TLS_CERT file.
-
TLS_RANDFILEfilenameSpecifies the file to obtain random bits from, instead of the default
/dev/urandomor/dev/random. -
TLS_REQCERTlevelSpecifies 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.
-
sargonUserUser 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.
-
sargonHostHost on which this entry takes effect. If the value starts with a plus sign, it is treated as the name of the NIS netgroup.
-
sargonAllowAllowed action. The value must be one of the docker action keywords, or the word
ALL(uppercase) matching all actions.
-
sargonDenyDenied 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 howsargonAllowandsargonDenypolicies operate.
-
sargonOrder(single)An integer used to order multiple
sargonACLentries. If not present, 0 is assumed.
-
sargonMountName 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 uidUser ID gidGroup ID nameUser name homeordirHome directory Undefined variables are left unexpanded.
For example:
-
sargonMount:/var/lib/mountsAllow 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:
-
roAllow read-only mounting. Attempts to mount the directory for writing will be rejected.
-
globlexUse 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. -
globpathUse pathname globbing:
*and?don't match slash. -
globstarUse star globbing. As with
globpathneither*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/mountsand only for reading. -
sargonMount:/var/*/mounts/**(globstar)Allow to mount directories located at any depth under the
mountsdirectory in any subdirectory of/var. Thus, mounting/var/lib/mounts/foo/barwill be allowed, whereas mounting/var/lib/sub/mounts/foo/barwill not.
-
-
sargonAllowPrivileged(single)The word
TRUEif the object allows creation of privileged containers.FALSEotherwise.
-
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).
-
sargonAllowCapabilityName of the linux capability that is allowed to use with the
--cap-adddocker option. See capabilities(7), for a list of capability names. Names listed in this attribute are case-insensitive. TheCAP_prefix is optional.
-
sargonNotBeforeA timestamp in the form
yyyymmddHHMMSSZthat provides a start date/time for when this entry will be valid. Notice, that the timestamp must be in UTC. -
sargonNotAfterA timestamp in the form
yyyymmddHHMMSSZthat 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:
-
BuildPruneDelete builder cache. -
ConfigCreateCreate a config. -
ConfigDeleteDelete a config. -
ConfigInspectInspect a config. -
ConfigListList configs. -
ConfigUpdateUpdate a config. -
ContainerArchiveGet an archive of a filesystem resource in a container. -
ContainerArchiveInfoGet information about files in a container. -
ContainerAttachAttach to a container. -
ContainerAttachWebsocketAttach to a container via a websocket. -
ContainerChangesGet changes on a container’s filesystem. -
ContainerCreateCreate a container. -
ContainerDeleteRemove a container. -
ContainerExecCreate an exec instance. -
ContainerExportExport a container. -
ContainerInspectInspect a container. -
ContainerKillKill a container. -
ContainerListList containers. -
ContainerLogsGet container logs. -
ContainerPausePause a container. -
ContainerPruneDelete stopped containers. -
ContainerRenameRename a container. -
ContainerResizeResize a container TTY. -
ContainerRestartRestart a container. -
ContainerStartStart a container. -
ContainerStatsGet container stats based on resource usage. -
ContainerStopStop a container. -
ContainerTopList processes running inside a container. -
ContainerUnpauseUnpause a container. -
ContainerUpdateUpdate a container. -
ContainerWaitWait for a container. -
DistributionInspectGet image information from the registry. -
ExecInspectInspect an exec instance. -
ExecResizeResize an exec instance. -
ExecStartStart an exec instance. -
GetPluginPrivilegesGet plugin privileges. -
ImageBuildBuild an image. -
ImageCommitCreate a new image from a container. -
ImageCreateCreate an image. -
ImageDeleteRemove an image. -
ImageGetExport an image. -
ImageGetAllExport several images. -
ImageHistoryGet the history of an image. -
ImageInspectInspect an image. -
ImageListList Images. -
ImageLoadImport images. -
ImagePruneDelete unused images. -
ImagePushPush an image. -
ImageSearchSearch images. -
ImageTagTag an image. -
NetworkConnectConnect a container to a network. -
NetworkCreateCreate a network. -
NetworkDeleteRemove a network. -
NetworkDisconnectDisconnect a container from a network. -
NetworkInspectInspect a network. -
NetworkListList networks. -
NetworkPruneDelete unused networks. -
NodeDeleteDelete a node. -
NodeInspectInspect a node. -
NodeListList nodes. -
NodeUpdateUpdate a node. -
PluginCreateCreate a plugin. -
PluginDeleteRemove a plugin. -
PluginDisableDisable a plugin. -
PluginEnableEnable a plugin. -
PluginInspectInspect a plugin. -
PluginListList plugins. -
PluginPullInstall a plugin. -
PluginPushPush a plugin. -
PluginSetConfigure a plugin. -
PluginUpgradeUpgrade a plugin. -
PutContainerArchiveExtract an archive of files or folders to a directory in a container. -
SecretCreateCreate a secret. -
SecretDeleteDelete a secret. -
SecretInspectInspect a secret. -
SecretListList secrets. -
SecretUpdateUpdate a Secret. -
ServiceCreateCreate a service. -
ServiceDeleteDelete a service. -
ServiceInspectInspect a service. -
ServiceListList services. -
ServiceLogsGet service logs. -
ServiceUpdateUpdate a service. -
SessionInitialize interactive session. -
SwarmInitInitialize a new swarm. -
SwarmInspectInspect swarm. -
SwarmJoinJoin an existing swarm. -
SwarmLeaveLeave a swarm. -
SwarmUnlockUnlock a locked manager. -
SwarmUnlockkeyGet the unlock key. -
SwarmUpdateUpdate a swarm. -
SystemAuthCheck auth configuration. -
SystemDataUsageGet data usage information. -
SystemEventsMonitor events. -
SystemInfoGet system information. -
SystemPingPing. -
SystemVersionGet version. -
TaskInspectInspect a task. -
TaskListList tasks. -
TaskLogsGet task logs. -
VolumeCreateCreate a volume. -
VolumeDeleteRemove a volume. -
VolumeInspectInspect a volume. -
VolumeListList volumes. -
VolumePruneDelete 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
sargonACLobjects, selecting only those with the value ofsargonHostattribute 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
sargonOrderattribute in ascending order. -
Start with the first returned object.
-
If the requested docker action is explicitly listed in one of its
sargonAllowattributes, go to step 9. -
Otherwise, if the object has one or more
sargonDenyattributes 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
ContainerCreateorVolumeCreate, authorize the request. -
For
VolumeCreaterequests, check if the requested mountpoint satisfies thesargonMountattribute. 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
sargonAllowPrivilegedattribute. 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
sargonAllowCapabilityattributes. If not, deny the request. -
Check the requested binds and mounts. Check each source directory against each
sargonMountattribute. If the directory matches, mounting is allowed. Otherwise, deny the request. -
If the requested maximum memory is greater than the value of the
sargonMaxMemoryattribute, the request is denied. -
If the requested maximum kernel memory is greater than the value of the
sargonMaxKernelMemoryattribute, the request is denied. -
Otherwise, the request is authorized.