mirror of
https://github.com/troglobit/finit.git
synced 2026-10-03 06:23:11 +07:00
Merge pull request #458 from aanderse/master
Add support for Linux capabilities
This commit is contained in:
@@ -34,7 +34,7 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get -y update
|
||||
sudo apt-get -y install pkg-config tree jq libuev-dev libite-dev
|
||||
sudo apt-get -y install pkg-config tree jq libuev-dev libite-dev libcap-dev
|
||||
- uses: actions/checkout@v4
|
||||
- name: Static Finit
|
||||
run: |
|
||||
|
||||
@@ -72,6 +72,10 @@ AC_ARG_ENABLE(cgroup,
|
||||
AS_HELP_STRING([--disable-cgroup], [Disable cgroup v2 support, default: autodetect from /sys/fs/cgroup]),,[
|
||||
enable_cgroup=yes])
|
||||
|
||||
AC_ARG_ENABLE(libcap,
|
||||
AS_HELP_STRING([--disable-libcap], [Disable Linux capabilities support]),,[
|
||||
enable_libcap=yes])
|
||||
|
||||
AC_ARG_ENABLE(redirect,
|
||||
AS_HELP_STRING([--disable-redirect], [Disable redirection of service output to /dev/null]),,[
|
||||
enable_redirect=yes])
|
||||
@@ -204,6 +208,16 @@ AS_IF([test "x$enable_kernel_logging" = "xyes"], [
|
||||
AS_IF([test "x$enable_cgroup" = "xyes"], [
|
||||
AC_DEFINE(CGROUP2_ENABLED, 1, [Autodetect cgroup v2 support from /sys/fs/cgroup])])
|
||||
|
||||
AS_IF([test "x$enable_libcap" = "xyes"], [
|
||||
AC_CHECK_LIB([cap], [cap_from_text], [
|
||||
AC_DEFINE(HAVE_LIBCAP, 1, [Have libcap for Linux capabilities support])
|
||||
LIBS="$LIBS -lcap"
|
||||
], [
|
||||
AC_MSG_WARN([libcap not found, capabilities support disabled])
|
||||
enable_libcap=no
|
||||
])
|
||||
])
|
||||
|
||||
AS_IF([test "x$enable_fastboot" = "xyes"], [
|
||||
AC_DEFINE(FAST_BOOT, 1, [Skip fsck check on filesystems listed in /etc/fstab])])
|
||||
|
||||
@@ -419,6 +433,7 @@ Optional features:
|
||||
Built-in logrotate....: $enable_logrotate
|
||||
Replacement libsystemd: $with_libsystemd
|
||||
Use cgroup v2.........: $enable_cgroup
|
||||
Use libcap............: $enable_libcap
|
||||
Parse kernel cmdline..: $enable_kernel_cmdline
|
||||
Keep kernel logging...: $enable_kernel_logging
|
||||
Skip fsck check.......: $enable_fastboot
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
Linux Capabilities
|
||||
==================
|
||||
|
||||
Finit supports Linux capabilities, allowing services to run with minimal
|
||||
required privileges instead of running as root. This significantly improves
|
||||
system security by following the principle of least privilege.
|
||||
|
||||
## Overview
|
||||
|
||||
Linux capabilities divide the traditional root privileges into distinct units
|
||||
that can be independently granted to processes. For example, a web server only
|
||||
needs the capability to bind to privileged ports (< 1024), not full root access.
|
||||
|
||||
Finit uses the modern IAB (Inheritable, Ambient, Bounding) API from libcap,
|
||||
which is the same approach used by other modern service managers like dinit.
|
||||
|
||||
## Basic Usage
|
||||
|
||||
Capabilities are specified using the `caps:` directive in service configuration:
|
||||
|
||||
```conf
|
||||
service [2345] name:nginx \
|
||||
@www-data:www-data \
|
||||
caps:^cap_net_bind_service \
|
||||
/usr/sbin/nginx -g 'daemon off;' \
|
||||
-- Web server
|
||||
```
|
||||
|
||||
This example allows nginx to bind to privileged ports (like 80 and 443) while
|
||||
running as the unprivileged `www-data` user.
|
||||
|
||||
## IAB Format
|
||||
|
||||
The capability string uses the IAB (Inheritable, Ambient, Bounding) format
|
||||
with the following prefixes:
|
||||
|
||||
- `^` **Ambient** (and Inheritable) - **Recommended for most use cases**
|
||||
- Capabilities survive across `exec()` calls
|
||||
- Automatically raised to effective after exec
|
||||
- Example: `^cap_net_bind_service`
|
||||
|
||||
- `%` **Inheritable** only
|
||||
- Requires the executed binary to have matching file capabilities
|
||||
- Less common, more complex setup
|
||||
- Example: `%cap_net_admin`
|
||||
|
||||
- `!` **Bounding** - Block capability from bounding set
|
||||
- Prevents the service from ever acquiring this capability
|
||||
- Useful for security hardening
|
||||
- Example: `!cap_sys_admin`
|
||||
|
||||
Multiple capabilities can be specified as a comma-separated list:
|
||||
|
||||
```conf
|
||||
caps:^cap_net_raw,^cap_net_admin,^cap_net_bind_service
|
||||
```
|
||||
|
||||
## Common Use Cases
|
||||
|
||||
### Web Server (Privileged Ports)
|
||||
|
||||
Allow a web server to bind to ports 80 and 443 without running as root:
|
||||
|
||||
```conf
|
||||
service [2345] name:webserver \
|
||||
@www-data:www-data \
|
||||
caps:^cap_net_bind_service \
|
||||
/usr/sbin/nginx -g 'daemon off;'
|
||||
```
|
||||
|
||||
### Network Monitoring (Raw Sockets)
|
||||
|
||||
Allow packet capture without root privileges:
|
||||
|
||||
```conf
|
||||
service [2345] name:tcpdump \
|
||||
@tcpdump \
|
||||
caps:^cap_net_raw,^cap_net_admin \
|
||||
/usr/sbin/tcpdump -i eth0 -w /var/log/capture.pcap
|
||||
```
|
||||
|
||||
### NTP Daemon (System Time)
|
||||
|
||||
Allow time synchronization without full root:
|
||||
|
||||
```conf
|
||||
service [2345] name:ntpd \
|
||||
@ntp \
|
||||
caps:^cap_sys_time,^cap_sys_nice \
|
||||
/usr/sbin/ntpd -n
|
||||
```
|
||||
|
||||
## Available Capabilities
|
||||
|
||||
Common capabilities include (see `man 7 capabilities` for the complete list):
|
||||
|
||||
- `cap_chown` - Make arbitrary changes to file UIDs and GIDs
|
||||
- `cap_dac_override` - Bypass file read, write, and execute permission checks
|
||||
- `cap_dac_read_search` - Bypass file read permission checks
|
||||
- `cap_fowner` - Bypass permission checks on operations that normally require filesystem UID
|
||||
- `cap_kill` - Bypass permission checks for sending signals
|
||||
- `cap_net_admin` - Perform various network-related operations
|
||||
- `cap_net_bind_service` - Bind to privileged ports (< 1024)
|
||||
- `cap_net_raw` - Use RAW and PACKET sockets
|
||||
- `cap_setgid` - Make arbitrary manipulations of process GIDs
|
||||
- `cap_setuid` - Make arbitrary manipulations of process UIDs
|
||||
- `cap_sys_admin` - Perform system administration operations (very powerful!)
|
||||
- `cap_sys_module` - Load and unload kernel modules
|
||||
- `cap_sys_nice` - Raise process nice value and change scheduling
|
||||
- `cap_sys_time` - Set system clock
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
1. **Use the minimum required capabilities**
|
||||
- Only grant what the service actually needs
|
||||
- Don't grant `cap_sys_admin` unless absolutely necessary
|
||||
|
||||
2. **Always specify a user**
|
||||
- Always use `@user` to drop to a non-root user
|
||||
- Capabilities work best when combined with user separation
|
||||
|
||||
3. **Use ambient capabilities (`^`)**
|
||||
- The `^` prefix ensures capabilities survive exec()
|
||||
- Simpler than setting file capabilities on binaries
|
||||
|
||||
4. **Block dangerous capabilities**
|
||||
- Use `!` to explicitly block capabilities you don't want
|
||||
- Example: `!cap_sys_admin,!cap_sys_module`
|
||||
|
||||
5. **Test with `getpcaps`**
|
||||
- After starting a service, verify its capabilities:
|
||||
```bash
|
||||
getpcaps $(pidof nginx)
|
||||
```
|
||||
- Should show only the capabilities you granted
|
||||
|
||||
## Verification
|
||||
|
||||
After configuring a service with capabilities, verify it works correctly:
|
||||
|
||||
```bash
|
||||
# Start the service
|
||||
initctl start webserver
|
||||
|
||||
# Check the process capabilities
|
||||
getpcaps $(pidof nginx)
|
||||
|
||||
# Should show something like:
|
||||
# 12345: cap_net_bind_service=eip
|
||||
|
||||
# Verify the user
|
||||
ps -o user,pid,cmd -p $(pidof nginx)
|
||||
|
||||
# Should show the service running as the specified user
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
- Linux kernel 4.3+ (for ambient capabilities support)
|
||||
- libcap library installed
|
||||
- Finit built with `--enable-libcap`
|
||||
|
||||
## Limitations
|
||||
|
||||
- Capabilities are only applied when both `@user` and `caps:` are specified
|
||||
- The service must drop to a non-root user for capabilities to be effective
|
||||
- Some very old binaries may not work correctly with ambient capabilities
|
||||
- File system capabilities are not managed by Finit (use `setcap` for that)
|
||||
|
||||
## See Also
|
||||
|
||||
- `man 7 capabilities` - Linux capabilities overview
|
||||
- `man 3 cap_iab` - IAB capability API documentation
|
||||
- `man 8 setcap` - Set file capabilities
|
||||
- `man 8 getcap` - Query file capabilities
|
||||
- `man 1 capsh` - Capability shell wrapper
|
||||
@@ -42,6 +42,7 @@ started at any time by running `initctl start <service>`.
|
||||
|
||||
Other run/task/service options are:
|
||||
|
||||
* `caps:...` -- see the [Linux Capabilities](capabilities.md) section
|
||||
* `cgroups:...` -- see the [Cgroups](cgroups.md) section
|
||||
* `env:[-]/path/to/env` -- see the [Service Environment](service-env.md) section
|
||||
* `log:...` -- see [Redirecting Output](logging.md#redirecting-output)
|
||||
|
||||
@@ -130,6 +130,38 @@ unnecessary overhead, which can be removed at build-time using:
|
||||
configure --enable-auto-reload
|
||||
|
||||
|
||||
**Linux Capabilities**
|
||||
|
||||
Finit supports Linux capabilities, allowing services to run with minimal
|
||||
required privileges instead of running as root. This improves security by
|
||||
following the principle of least privilege.
|
||||
|
||||
```conf
|
||||
service [2345] name:nginx \
|
||||
www-data:www-data \
|
||||
caps:^cap_net_bind_service \
|
||||
/usr/sbin/nginx -g 'daemon off;'
|
||||
```
|
||||
|
||||
In this example, nginx runs as the unprivileged `www-data` user but retains
|
||||
the ability to bind to privileged ports (80, 443) through the
|
||||
`cap_net_bind_service` capability.
|
||||
|
||||
The `caps:` directive uses the IAB (Inheritable, Ambient, Bounding) format:
|
||||
- `^` = Ambient (recommended) - capabilities survive exec()
|
||||
- `%` = Inheritable only - requires file capabilities
|
||||
- `!` = Bounding - block from acquiring capability
|
||||
|
||||
Multiple capabilities can be specified as comma-separated:
|
||||
|
||||
```conf
|
||||
caps:^cap_net_raw,^cap_net_admin,!cap_sys_admin
|
||||
```
|
||||
|
||||
See the [Linux Capabilities](config/capabilities.md) section for detailed
|
||||
information, examples, and security best practices.
|
||||
|
||||
|
||||
**Cgroups**
|
||||
|
||||
Finit supports cgroups v2 and comes with the following default groups in
|
||||
|
||||
+41
-2
@@ -39,6 +39,9 @@
|
||||
# include <lite/lite.h>
|
||||
#endif
|
||||
#include <wordexp.h>
|
||||
#ifdef HAVE_LIBCAP
|
||||
# include <sys/capability.h>
|
||||
#endif
|
||||
|
||||
#include "cgroup.h"
|
||||
#include "client.h"
|
||||
@@ -521,6 +524,36 @@ static void compose_cmdline(svc_t *svc, char *buf, size_t len)
|
||||
}
|
||||
}
|
||||
|
||||
static void set_uid(uid_t uid, svc_t *svc)
|
||||
{
|
||||
#ifdef HAVE_LIBCAP
|
||||
if (cap_setuid(uid)) {
|
||||
err(1, "%s: failed cap_setuid(%d)", svc_ident(svc, NULL, 0), uid);
|
||||
return;
|
||||
}
|
||||
|
||||
/* After dropping privileges, set the specific capabilities we need */
|
||||
if (svc->capabilities[0]) {
|
||||
cap_iab_t cap_iab = cap_iab_from_text(svc->capabilities);
|
||||
if (!cap_iab) {
|
||||
err(1, "%s: failed parsing capabilities '%s'",
|
||||
svc_ident(svc, NULL, 0), svc->capabilities);
|
||||
return;
|
||||
}
|
||||
|
||||
if (cap_iab_set_proc(cap_iab) != 0) {
|
||||
cap_free(cap_iab);
|
||||
err(1, "%s: failed setting capabilities",
|
||||
svc_ident(svc, NULL, 0));
|
||||
}
|
||||
cap_free(cap_iab);
|
||||
}
|
||||
#else
|
||||
if (setuid(uid))
|
||||
err(1, "%s: failed setuid(%d)", svc_ident(svc, NULL, 0), uid);
|
||||
#endif
|
||||
}
|
||||
|
||||
static pid_t service_fork(svc_t *svc)
|
||||
{
|
||||
pid_t pid;
|
||||
@@ -552,8 +585,7 @@ static pid_t service_fork(svc_t *svc)
|
||||
}
|
||||
|
||||
if (uid >= 0) {
|
||||
if (setuid(uid))
|
||||
err(1, "%s: failed setuid(%d)", svc_ident(svc, NULL, 0), uid);
|
||||
set_uid(uid, svc);
|
||||
|
||||
/* Set default path for regular users */
|
||||
if (uid > 0)
|
||||
@@ -1666,6 +1698,7 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
|
||||
char *ready_script = NULL, *conflict = NULL;
|
||||
char *reload_script = NULL, *stop_script = NULL;
|
||||
char *cleanup_script = NULL;
|
||||
char *caps = NULL;
|
||||
char ident[MAX_IDENT_LEN];
|
||||
char *ifstmt = NULL;
|
||||
char *notify = NULL;
|
||||
@@ -1781,6 +1814,8 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
|
||||
stop_script = arg;
|
||||
else if (MATCH_CMD(cmd, "env:", arg))
|
||||
env = arg;
|
||||
else if (MATCH_CMD(cmd, "caps:", arg))
|
||||
caps = arg;
|
||||
/* catch both cgroup: and cgroup. handled in parse_cgroup() */
|
||||
else if (MATCH_CMD(cmd, "cgroup", arg))
|
||||
cgroup = arg;
|
||||
@@ -2004,6 +2039,10 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
|
||||
parse_env(svc, env);
|
||||
else
|
||||
memset(svc->env, 0, sizeof(svc->env));
|
||||
if (caps)
|
||||
strlcpy(svc->capabilities, caps, sizeof(svc->capabilities));
|
||||
else
|
||||
memset(svc->capabilities, 0, sizeof(svc->capabilities));
|
||||
if (file)
|
||||
strlcpy(svc->file, file, sizeof(svc->file));
|
||||
else
|
||||
|
||||
Reference in New Issue
Block a user