mirror of
https://github.com/troglobit/finit.git
synced 2026-11-06 15:51:03 +00:00
Merge pull request #458 from aanderse/master
Add support for Linux capabilities
This commit is contained in:
7 files changed
+267
-3
No files matched your search
@@ -34,7 +34,7 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get -y update
|
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
|
- uses: actions/checkout@v4
|
||||||
- name: Static Finit
|
- name: Static Finit
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -72,6 +72,10 @@ AC_ARG_ENABLE(cgroup,
|
|||||||
AS_HELP_STRING([--disable-cgroup], [Disable cgroup v2 support, default: autodetect from /sys/fs/cgroup]),,[
|
AS_HELP_STRING([--disable-cgroup], [Disable cgroup v2 support, default: autodetect from /sys/fs/cgroup]),,[
|
||||||
enable_cgroup=yes])
|
enable_cgroup=yes])
|
||||||
|
|
||||||
|
AC_ARG_ENABLE(libcap,
|
||||||
|
AS_HELP_STRING([--disable-libcap], [Disable Linux capabilities support]),,[
|
||||||
|
enable_libcap=yes])
|
||||||
|
|
||||||
AC_ARG_ENABLE(redirect,
|
AC_ARG_ENABLE(redirect,
|
||||||
AS_HELP_STRING([--disable-redirect], [Disable redirection of service output to /dev/null]),,[
|
AS_HELP_STRING([--disable-redirect], [Disable redirection of service output to /dev/null]),,[
|
||||||
enable_redirect=yes])
|
enable_redirect=yes])
|
||||||
@@ -204,6 +208,16 @@ AS_IF([test "x$enable_kernel_logging" = "xyes"], [
|
|||||||
AS_IF([test "x$enable_cgroup" = "xyes"], [
|
AS_IF([test "x$enable_cgroup" = "xyes"], [
|
||||||
AC_DEFINE(CGROUP2_ENABLED, 1, [Autodetect cgroup v2 support from /sys/fs/cgroup])])
|
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"], [
|
AS_IF([test "x$enable_fastboot" = "xyes"], [
|
||||||
AC_DEFINE(FAST_BOOT, 1, [Skip fsck check on filesystems listed in /etc/fstab])])
|
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
|
Built-in logrotate....: $enable_logrotate
|
||||||
Replacement libsystemd: $with_libsystemd
|
Replacement libsystemd: $with_libsystemd
|
||||||
Use cgroup v2.........: $enable_cgroup
|
Use cgroup v2.........: $enable_cgroup
|
||||||
|
Use libcap............: $enable_libcap
|
||||||
Parse kernel cmdline..: $enable_kernel_cmdline
|
Parse kernel cmdline..: $enable_kernel_cmdline
|
||||||
Keep kernel logging...: $enable_kernel_logging
|
Keep kernel logging...: $enable_kernel_logging
|
||||||
Skip fsck check.......: $enable_fastboot
|
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:
|
Other run/task/service options are:
|
||||||
|
|
||||||
|
* `caps:...` -- see the [Linux Capabilities](capabilities.md) section
|
||||||
* `cgroups:...` -- see the [Cgroups](cgroups.md) section
|
* `cgroups:...` -- see the [Cgroups](cgroups.md) section
|
||||||
* `env:[-]/path/to/env` -- see the [Service Environment](service-env.md) section
|
* `env:[-]/path/to/env` -- see the [Service Environment](service-env.md) section
|
||||||
* `log:...` -- see [Redirecting Output](logging.md#redirecting-output)
|
* `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
|
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**
|
**Cgroups**
|
||||||
|
|
||||||
Finit supports cgroups v2 and comes with the following default groups in
|
Finit supports cgroups v2 and comes with the following default groups in
|
||||||
|
|||||||
+41
-2
@@ -39,6 +39,9 @@
|
|||||||
# include <lite/lite.h>
|
# include <lite/lite.h>
|
||||||
#endif
|
#endif
|
||||||
#include <wordexp.h>
|
#include <wordexp.h>
|
||||||
|
#ifdef HAVE_LIBCAP
|
||||||
|
# include <sys/capability.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "cgroup.h"
|
#include "cgroup.h"
|
||||||
#include "client.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)
|
static pid_t service_fork(svc_t *svc)
|
||||||
{
|
{
|
||||||
pid_t pid;
|
pid_t pid;
|
||||||
@@ -552,8 +585,7 @@ static pid_t service_fork(svc_t *svc)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (uid >= 0) {
|
if (uid >= 0) {
|
||||||
if (setuid(uid))
|
set_uid(uid, svc);
|
||||||
err(1, "%s: failed setuid(%d)", svc_ident(svc, NULL, 0), uid);
|
|
||||||
|
|
||||||
/* Set default path for regular users */
|
/* Set default path for regular users */
|
||||||
if (uid > 0)
|
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 *ready_script = NULL, *conflict = NULL;
|
||||||
char *reload_script = NULL, *stop_script = NULL;
|
char *reload_script = NULL, *stop_script = NULL;
|
||||||
char *cleanup_script = NULL;
|
char *cleanup_script = NULL;
|
||||||
|
char *caps = NULL;
|
||||||
char ident[MAX_IDENT_LEN];
|
char ident[MAX_IDENT_LEN];
|
||||||
char *ifstmt = NULL;
|
char *ifstmt = NULL;
|
||||||
char *notify = NULL;
|
char *notify = NULL;
|
||||||
@@ -1781,6 +1814,8 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
|
|||||||
stop_script = arg;
|
stop_script = arg;
|
||||||
else if (MATCH_CMD(cmd, "env:", arg))
|
else if (MATCH_CMD(cmd, "env:", arg))
|
||||||
env = arg;
|
env = arg;
|
||||||
|
else if (MATCH_CMD(cmd, "caps:", arg))
|
||||||
|
caps = arg;
|
||||||
/* catch both cgroup: and cgroup. handled in parse_cgroup() */
|
/* catch both cgroup: and cgroup. handled in parse_cgroup() */
|
||||||
else if (MATCH_CMD(cmd, "cgroup", arg))
|
else if (MATCH_CMD(cmd, "cgroup", arg))
|
||||||
cgroup = arg;
|
cgroup = arg;
|
||||||
@@ -2004,6 +2039,10 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
|
|||||||
parse_env(svc, env);
|
parse_env(svc, env);
|
||||||
else
|
else
|
||||||
memset(svc->env, 0, sizeof(svc->env));
|
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)
|
if (file)
|
||||||
strlcpy(svc->file, file, sizeof(svc->file));
|
strlcpy(svc->file, file, sizeof(svc->file));
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -191,6 +191,7 @@ typedef struct svc {
|
|||||||
/* Identity */
|
/* Identity */
|
||||||
char username[MAX_USER_LEN];
|
char username[MAX_USER_LEN];
|
||||||
char group[MAX_USER_LEN];
|
char group[MAX_USER_LEN];
|
||||||
|
char capabilities[MAX_CMD_LEN];
|
||||||
|
|
||||||
/* Command, arguments and service description */
|
/* Command, arguments and service description */
|
||||||
char cmd[MAX_CMD_LEN];
|
char cmd[MAX_CMD_LEN];
|
||||||
|
|||||||
Reference in new issue
Block a user