'setcap' keyword during switching from root user to a non-root.
Since version v3.1 haproxy also checks if capabilities given in 'setcap'
-keyword were set in its binary file permitted set by administrator
+keyword were set in its binary file Permitted set by administrator
(capget syscall). If this a case it performs transition of these capabilities
-in its process effective set (capset syscall), while running as a non-root
+in its process Effective set (capset syscall), while running as a non-root
user.
This was done to avoid all potential use cases when haproxy starts and runs as
root: transparent proxy mode, binding to privileged ports.
'setcap' keyword supports following network capabilities:
-- cap_net_admin
-- cap_net_raw (subset of cap_net_admin)
-- cap_net_bind_service
-
-Haproxy never does the transition of these capabilities from its permitted set
-to the effective, if they are not listed as 'setcap' argument. See more
+- cap_net_admin: transparent proxying, binding socket to a specific network
+ interface, using set-mark action;
+- cap_net_raw (subset of cap_net_admin): transparent proxying;
+- cap_net_bind_service: binding socket to a specific network interface;
+- cap_sys_admin: creating socket in a specific network namespace.
+
+Haproxy never does the transition of these capabilities from its Permitted set
+to the Effective, if they are not listed as 'setcap' argument. See more
information about 'setcap' keyword and supported capabilities in the chapter
3.1 Process management and security in the Configuration guide.
-Administrator may add needed capabilities in the haproxy binary file permitted
+Administrator may add needed capabilities in the haproxy binary file Permitted
set with the following command:
Example:
# setcap cap_net_admin,cap_net_bind_service=p /usr/local/sbin/haproxy
-Added capabilities will be seen in process permitted set after its start.
+Added capabilities will be seen in process Permitted set after its start.
If the same capabilities are the arguments of 'setcap' keyword, they could be
-also seen in the process effective set. This could be check with the following
+also seen in the process Effective set. This could be check with the following
command:
Example:
See more details about setcap and capabilities sets in Linux man pages
(capabilities(7)).
-In some cases like transparent proxying, binding socket to a specific network
-interface, using set-mark action, configuration file parser detects that
-cap_net_admin or cap_net_raw capabilities are needed. Then, during
-initialization stage, haproxy process checks, if these capabilities could be
-put in its effective set. If it's not possible due to capget or capset syscall
-failure (restrictions set on syscalls by some security modules like SELinux,
-Seccomp, etc), process emits diagnostic warnings (start with -dD).
+In some use cases like transparent proxying or creating socket in a specific
+network namespace, configuration file parser detects that cap_net_raw or
+cap_sys_admin or some other supported capabilities are needed. Then, during
+the initialization stage, haproxy process checks, if these capabilities could
+be put in its Effective set. If it's not possible due to capget or capset
+syscall failure (restrictions set on syscalls by some security modules like
+SELinux, Seccomp, etc), process emits diagnostic warnings (start with -dD).
Due to support of many different platforms with different system settings,
it's impossible for the parser to deduce from the configuration file, if
binding to privileged ports will be done. So, in the case of insufficient
privileges (run as non-root) process will terminate only with an alert
-message like below. It's up to a user to recheck its configuration and
-capabilities set for haproxy binary.
+message like below. It's up to a user to recheck its configuration and haproxy
+binary capabilities set.
Example:
$ haproxy -dD -f haproxy.cfg
#define MODE_DUMP_NB_L 0x10000 /* dump line numbers when the configuration file is dump */
/* list of last checks to perform, depending on config options */
-/* unused: 0x00000001 */
+#define LSTCHK_SYSADM 0x00000001 /* check that we have CAP_SYS_ADMIN */
#define LSTCHK_NETADM 0x00000002 /* check that we have CAP_NET_ADMIN */
/* Global tuning options */
ha_alert("Cannot open namespace '%s'.\n", args[cur_arg + 1]);
return ERR_ALERT | ERR_FATAL;
}
+ global.last_checks |= LSTCHK_SYSADM;
+
return 0;
}
#endif
if ((global.mode & (MODE_MWORKER | MODE_DAEMON)) == 0)
set_identity(argv[0]);
- /* set_identity() above might have dropped LSTCHK_NETADM if
- * it changed to a new UID while preserving enough permissions
- * to honnor LSTCHK_NETADM.
+ /* set_identity() above might have dropped LSTCHK_NETADM or/and
+ * LSTCHK_SYSADM if it changed to a new UID while preserving enough
+ * permissions to honnor LSTCHK_NETADM/LSTCHK_SYSADM.
*/
- if ((global.last_checks & LSTCHK_NETADM) && getuid()) {
+ if ((global.last_checks & (LSTCHK_NETADM|LSTCHK_SYSADM)) && getuid()) {
/* If global.uid is present in config, it is already set as euid
- * and ruid by set_identity() call just above, so it's better to
+ * and ruid by set_identity() just above, so it's better to
* remind the user to fix uncoherent settings.
*/
if (global.uid) {
#endif
#ifdef CAP_NET_BIND_SERVICE
{ CAP_NET_BIND_SERVICE, "cap_net_bind_service" },
+#endif
+#ifdef CAP_SYS_ADMIN
+ { CAP_SYS_ADMIN, "cap_sys_admin" },
#endif
/* must be last */
{ 0, 0 }
/* defaults to zero, i.e. we don't keep any cap after setuid() */
static uint32_t caplist;
-/* try to check if CAP_NET_ADMIN or CAP_NET_RAW are in the process effective
- * set in the case when euid is non-root. If there is a match,
- * LSTCHK_NETADM is unset from global.last_checks to avoid warning due to
- * global.last_checks verifications later in the init process.
- * If there is no CAP_NET_ADMIN, nor CAP_NET_RAW in the effective set, try to
- * check process permitted set. In this case we promote from permitted set to
- * effective only the capabilities, that were marked by user via 'capset'
- * keyword in the global section (caplist). If there is match with
- * caplist and CAP_NET_ADMIN or/and CAP_NET_RAW in this caplist, LSTCHK_NETADM
- * will be unset by the same reason.
+/* try to check if CAP_NET_ADMIN, CAP_NET_RAW or CAP_SYS_ADMIN are in the
+ * process Effective set in the case when euid is non-root. If there is a
+ * match, LSTCHK_NETADM or LSTCHK_SYSADM is unset respectively from
+ * global.last_checks to avoid warning due to global.last_checks verifications
+ * later at the process init stage.
+ * If there is no any supported by haproxy capability in the process Effective
+ * set, try to check the process Permitted set. In this case we promote from
+ * Permitted set to Effective only the capabilities, that were marked by user
+ * via 'capset' keyword in the global section (caplist). If there is match with
+ * caplist and CAP_NET_ADMIN/CAP_NET_RAW or CAP_SYS_ADMIN are in this list,
+ * LSTCHK_NETADM or/and LSTCHK_SYSADM will be unset by the same reason.
* We do this only if the current euid is non-root and there is no global.uid.
- * Otherwise the process will continue either to run under root, or it will do
+ * Otherwise, the process will continue either to run under root, or it will do
* a transition to unprivileged user later in prepare_caps_for_setuid(),
* which specially manages its capabilities in that case.
* Always returns 0. Diagnostic warnings will be emitted only, if
- * LSTCHK_NETADM is presented in LSTCHK_NETADM and some failures are
- * encountered.
+ * LSTCHK_NETADM/LSTCHK_SYSADM is presented in global.last_checks and some
+ * failures are encountered.
*/
int prepare_caps_from_permitted_set(int from_uid, int to_uid, const char *program_name)
{
* setcap, see capabilities man page for details.
*/
if (capget(&cap_hdr, &start_cap_data) == -1) {
- if (global.last_checks & LSTCHK_NETADM)
+ if (global.last_checks & (LSTCHK_NETADM | LSTCHK_SYSADM))
ha_diag_warning("Failed to get process capabilities using capget(): %s. "
"Can't use capabilities that might be set on %s binary "
"by administrator.\n", strerror(errno), program_name);
return 0;
}
+ if (start_cap_data.effective & ((1 << CAP_SYS_ADMIN))) {
+ global.last_checks &= ~LSTCHK_SYSADM;
+ return 0;
+ }
+
/* second, try to check process permitted set, in this case caplist is
* necessary. Allows to put cap_net_bind_service in process effective
* set, if it is in the caplist and also presented in the binary
if (capset(&cap_hdr, &start_cap_data) == 0) {
if (caplist & ((1 << CAP_NET_ADMIN)|(1 << CAP_NET_RAW)))
global.last_checks &= ~LSTCHK_NETADM;
- } else if (global.last_checks & LSTCHK_NETADM) {
+ if (caplist & (1 << CAP_SYS_ADMIN))
+ global.last_checks &= ~LSTCHK_SYSADM;
+ } else if (global.last_checks & (LSTCHK_NETADM|LSTCHK_SYSADM)) {
ha_diag_warning("Failed to put capabilities from caplist in %s "
- "process effective capabilities set using capset(): %s\n",
+ "process Effective capabilities set using capset(): %s\n",
program_name, strerror(errno));
}
}
* - set the effective and permitted caps again
* - then the caller can safely call setuid()
* On success LSTCHK_NETADM is unset from global.last_checks, if CAP_NET_ADMIN
- * or CAP_NET_RAW was found in the caplist from config.
+ * or CAP_NET_RAW was found in the caplist from config. Same for
+ * LSTCHK_SYSADM, if CAP_SYS_ADMIN was found in the caplist from config.
* We don't do this if the current euid is not zero or if the target uid
* is zero. Returns 0 on success, negative on failure. Alerts may be emitted.
*/
if (caplist & ((1 << CAP_NET_ADMIN)|(1 << CAP_NET_RAW)))
global.last_checks &= ~LSTCHK_NETADM;
+ if (caplist & (1 << CAP_SYS_ADMIN))
+ global.last_checks &= ~LSTCHK_SYSADM;
+
/* all's good */
return 0;
}
if (strcmp(arg, "*") == 0) {
/* Use the namespace associated with the connection (if present). */
newsrv->flags |= SRV_F_USE_NS_FROM_PP;
+ global.last_checks |= LSTCHK_SYSADM;
return 0;
}
memprintf(err, "Cannot open namespace '%s'", arg);
return ERR_ALERT | ERR_FATAL;
}
+ global.last_checks |= LSTCHK_SYSADM;
return 0;
#else