Add helper to run external commands without additional privileges. (#20990)
Co-authored-by: Costa Tsaousis <costa@netdata.cloud>
Austin S. Hemmelgarn committed
Sep 29, 2025 at 08:19 UTC
4f34cf80eef21419387030e2d1cd3ff5c3ab1d4a
11 files changed
+404
-48
CMakeLists.txt
+55
-37
@@ -148,6 +148,27 @@ else()
148
message(FATAL_ERROR "Unknown/unsupported platform: ${CMAKE_SYSTEM_NAME} (Supported platforms: FreeBSD, Linux, macOS, Windows)")
149
endif()
150
151
+if(NOT NETDATA_RUNTIME_PREFIX STREQUAL "")
152
+ string(REGEX REPLACE "/$" "" NETDATA_RUNTIME_PREFIX "${NETDATA_RUNTIME_PREFIX}")
153
+endif()
154
+
155
+set(CACHE_DIR "${NETDATA_RUNTIME_PREFIX}/var/cache/netdata")
156
+set(CONFIG_DIR "${NETDATA_RUNTIME_PREFIX}/etc/netdata")
157
+set(LIBCONFIG_DIR "${NETDATA_RUNTIME_PREFIX}/usr/lib/netdata/conf.d")
158
+set(LOG_DIR "${NETDATA_RUNTIME_PREFIX}/var/log/netdata")
159
+set(PLUGINS_DIR "${NETDATA_RUNTIME_PREFIX}/usr/libexec/netdata/plugins.d")
160
+set(VARLIB_DIR "${NETDATA_RUNTIME_PREFIX}/var/lib/netdata")
161
+set(NETDATA_BIN_DIR "${NETDATA_RUNTIME_PREFIX}/${BINDIR}")
162
+
163
+# A non-default value is only used when building Debian packages (/var/lib/netdata/www)
164
+if(NOT DEFINED WEB_DIR)
165
+ set(WEB_DIR "usr/share/netdata/web")
166
+else()
167
+ string(REGEX REPLACE "^/" "" WEB_DIR "${WEB_DIR}")
168
+endif()
169
+set(WEB_DEST "${WEB_DIR}")
170
+set(WEB_DIR "${NETDATA_RUNTIME_PREFIX}/${WEB_DEST}")
171
+
172
include(NetdataCompilerFlags)
173
174
check_c_compiler_flag("-fexceptions" HAVE_FEXCEPTIONS)
@@ -474,9 +495,6 @@ check_include_file("sys/socket.h" HAVE_SYS_SOCKET_H)
495
check_include_file("sys/wait.h" HAVE_SYS_WAIT_H)
496
check_include_file("sys/un.h" HAVE_SYS_UN_H)
497
check_include_file("spawn.h" HAVE_SPAWN_H)
477
-if(OS_LINUX)
478
- check_include_file("sys/capability.h" HAVE_SYS_CAPABILITY_H)
479
-endif()
498
499
#
500
# check symbols
@@ -490,6 +508,10 @@ check_symbol_exists(strerror_r "string.h" HAVE_STRERROR_R)
508
check_symbol_exists(finite "math.h" HAVE_FINITE)
509
check_symbol_exists(isfinite "math.h" HAVE_ISFINITE)
510
check_symbol_exists(dlsym "dlfcn.h" HAVE_DLSYM)
511
+check_symbol_exists(clearenv "stdlib.h" HAVE_CLEARENV)
512
+
513
+check_function_exists(setresuid HAVE_SETRESUID)
514
+check_function_exists(setresgid HAVE_SETRESGID)
515
516
check_function_exists(pthread_getthreadid_np HAVE_PTHREAD_GETTHREADID_NP)
517
check_function_exists(pthread_threadid_np HAVE_PTHREAD_THREADID_NP)
@@ -2471,14 +2493,35 @@ netdata_protoc_generate_cpp("${CMAKE_SOURCE_DIR}/src/aclk/aclk-schemas"
2493
list(APPEND ACLK_FILES ${ACLK_PROTO_BUILT_SRCS}
2494
${ACLK_PROTO_BUILT_HDRS})
2495
2496
+#
2497
+# nd-run helper program
2498
+#
2499
+
2500
+if(OS_LINUX)
2501
+ pkg_check_modules(CAP IMPORTED_TARGET libcap)
2502
+else()
2503
+ set(CAP_FOUND False)
2504
+endif()
2505
+
2506
+if(CAP_FOUND)
2507
+ set(HAVE_CAPABILITY True)
2508
+endif()
2509
+
2510
+set(NDRUN_FILES src/collectors/utils/nd-run.c)
2511
+add_executable(nd-run ${NDRUN_FILES})
2512
+if(CAP_FOUND)
2513
+ target_link_libraries(nd-run PRIVATE PkgConfig::CAP)
2514
+endif()
2515
+target_include_directories(nd-run PRIVATE ${CONFIG_H_DIR})
2516
+install(TARGETS nd-run
2517
+ COMPONENT netdata
2518
+ DESTINATION "${BINDIR}")
2519
+
2520
#
2521
# build plugins
2522
#
2523
2524
if(ENABLE_PLUGIN_DEBUGFS)
2479
- # Check for libcap (optional)
2480
- pkg_check_modules(CAP QUIET libcap)
2481
-
2525
# Define debugfs.plugin source files
2526
set(DEBUGFS_PLUGIN_FILES
2527
src/collectors/debugfs.plugin/debugfs_plugin.c
@@ -2533,8 +2576,6 @@ add_executable(spawn-tester src/libnetdata/spawn_server/spawn-tester.c)
2576
target_link_libraries(spawn-tester libnetdata)
2577
2578
if(ENABLE_PLUGIN_APPS)
2536
- pkg_check_modules(CAP QUIET libcap)
2537
-
2579
set(APPS_PLUGIN_FILES
2580
src/collectors/apps.plugin/apps_plugin.c
2581
src/collectors/apps.plugin/apps_plugin.h
@@ -2577,10 +2618,6 @@ if(ENABLE_PLUGIN_APPS)
2618
endif()
2619
endif()
2620
2580
-if(CAP_FOUND)
2581
- set(HAVE_CAPABILITY True)
2582
-endif()
2583
-
2621
if(ENABLE_PLUGIN_FREEIPMI)
2622
pkg_check_modules(IPMI REQUIRED libipmimonitoring)
2623
@@ -3193,25 +3230,6 @@ endif()
3230
#
3231
# Generate config file
3232
#
3196
-if(NOT NETDATA_RUNTIME_PREFIX STREQUAL "")
3197
- string(REGEX REPLACE "/$" "" NETDATA_RUNTIME_PREFIX "${NETDATA_RUNTIME_PREFIX}")
3198
-endif()
3199
-
3200
-set(CACHE_DIR "${NETDATA_RUNTIME_PREFIX}/var/cache/netdata")
3201
-set(CONFIG_DIR "${NETDATA_RUNTIME_PREFIX}/etc/netdata")
3202
-set(LIBCONFIG_DIR "${NETDATA_RUNTIME_PREFIX}/usr/lib/netdata/conf.d")
3203
-set(LOG_DIR "${NETDATA_RUNTIME_PREFIX}/var/log/netdata")
3204
-set(PLUGINS_DIR "${NETDATA_RUNTIME_PREFIX}/usr/libexec/netdata/plugins.d")
3205
-set(VARLIB_DIR "${NETDATA_RUNTIME_PREFIX}/var/lib/netdata")
3206
-
3207
-# A non-default value is only used when building Debian packages (/var/lib/netdata/www)
3208
-if(NOT DEFINED WEB_DIR)
3209
- set(WEB_DIR "usr/share/netdata/web")
3210
-else()
3211
- string(REGEX REPLACE "^/" "" WEB_DIR "${WEB_DIR}")
3212
-endif()
3213
-set(WEB_DEST "${WEB_DIR}")
3214
-set(WEB_DIR "${NETDATA_RUNTIME_PREFIX}/${WEB_DEST}")
3233
3234
# Collect all compiler flags
3235
get_directory_property(NETDATA_COMPILE_OPTIONS COMPILE_OPTIONS)
@@ -3273,12 +3291,12 @@ install(DIRECTORY COMPONENT netdata DESTINATION ${WEB_DEST})
3291
set(libsysdir_POST "${NETDATA_RUNTIME_PREFIX}/usr/lib/netdata/system")
3292
set(pkglibexecdir_POST "${NETDATA_RUNTIME_PREFIX}/usr/libexec/netdata")
3293
set(localstatedir_POST "${NETDATA_RUNTIME_PREFIX}/var")
3276
-set(sbindir_POST "${NETDATA_RUNTIME_PREFIX}/${BINDIR}")
3277
-set(configdir_POST "${NETDATA_RUNTIME_PREFIX}/etc/netdata")
3278
-set(libconfigdir_POST "${NETDATA_RUNTIME_PREFIX}/usr/lib/netdata/conf.d")
3279
-set(cachedir_POST "${NETDATA_RUNTIME_PREFIX}/var/cache/netdata")
3280
-set(registrydir_POST "${NETDATA_RUNTIME_PREFIX}/var/lib/netdata/registry")
3281
-set(varlibdir_POST "${NETDATA_RUNTIME_PREFIX}/var/lib/netdata")
3294
+set(sbindir_POST "${NETDATA_BIN_DIR}")
3295
+set(configdir_POST "${CONFIG_DIR}")
3296
+set(libconfigdir_POST "${LIBCONFIG_DIR}")
3297
+set(cachedir_POST "${CACHE_DIR}")
3298
+set(varlibdir_POST "${VARLIB_DIR}")
3299
+set(registrydir_POST "${VARLIB_DIR}/registry")
3300
set(netdata_user_POST "${NETDATA_USER}")
3301
set(netdata_group_POST "${NETDATA_USER}")
3302
netdata.spec.in
+1
@@ -539,6 +539,7 @@ rm -rf "${RPM_BUILD_ROOT}"
539
%{_sbindir}/%{name}
540
%{_sbindir}/netdatacli
541
%{_sbindir}/nd-mcp
542
+%{_sbindir}/nd-run
543
%if %{_have_log2journal}
544
%{_sbindir}/log2journal
545
%endif
packaging/cmake/Modules/NetdataGoTools.cmake
+6
-2
@@ -2,11 +2,15 @@
2
# Macros and functions to assist in working with Go
3
4
if(CMAKE_BUILD_TYPE STREQUAL Debug)
5
- set(GO_LDFLAGS "-X github.com/netdata/netdata/go/plugins/pkg/buildinfo.Version=${NETDATA_VERSION_STRING}")
5
+ set(GO_LDFLAGS "")
6
else()
7
- set(GO_LDFLAGS "-w -s -X github.com/netdata/netdata/go/plugins/pkg/buildinfo.Version=${NETDATA_VERSION_STRING}")
7
+ set(GO_LDFLAGS "-w -s")
8
endif()
9
10
+set(GO_LDFLAGS "${GO_LDFLAGS} -X github.com/netdata/netdata/go/plugins/pkg/buildinfo.Version=${NETDATA_VERSION_STRING}")
11
+set(GO_LDFLAGS "${GO_LDFLAGS} -X github.com/netdata/netdata/go/plugins/pkg/buildinfo.NetdataBinDir=${NETDATA_BIN_DIR}")
12
+set(GO_LDFLAGS "${GO_LDFLAGS} -X github.com/netdata/netdata/go/plugins/pkg/buildinfo.PluginsDir=${PLUGINS_DIR}")
13
+
14
# add_go_target: Add a new target that needs to be built using the Go toolchain.
15
#
16
# Takes four arguments, the target name, the output artifact name, the
packaging/cmake/config.cmake.h.in
+5
-1
@@ -33,7 +33,6 @@
33
#cmakedefine HAVE_SYS_STATVFS_H
34
#cmakedefine HAVE_INTTYPES_H
35
#cmakedefine HAVE_STDINT_H
36
-#cmakedefine HAVE_SYS_CAPABILITY_H
36
#cmakedefine HAVE_ARPA_INET_H
37
#cmakedefine HAVE_NETINET_TCP_H
38
#cmakedefine HAVE_SYS_IOCTL_H
@@ -72,9 +71,14 @@
71
#cmakedefine HAVE_GETPRIORITY
72
#cmakedefine HAVE_SETENV
73
#cmakedefine HAVE_DLSYM
74
+#cmakedefine HAVE_CLEARENV
75
+
76
#cmakedefine HAVE_LIBCURL
77
#cmakedefine HAVE_LIBICONV
78
79
+#cmakedefine HAVE_SETRESUID
80
+#cmakedefine HAVE_SETRESGID
81
+
82
#cmakedefine HAVE_ARC4RANDOM_BUF
83
#cmakedefine HAVE_ARC4RANDOM_UNIFORM
84
#cmakedefine HAVE_RAND_S
src/collectors/apps.plugin/apps_plugin.c
+2
-2
@@ -603,7 +603,7 @@ static inline int am_i_running_as_root() {
603
return 0;
604
}
605
606
-#ifdef HAVE_SYS_CAPABILITY_H
606
+#ifdef HAVE_CAPABILITY
607
static inline int check_capabilities() {
608
cap_t caps = cap_get_proc();
609
if(!caps) {
@@ -718,7 +718,7 @@ int main(int argc, char **argv) {
718
#if !defined(OS_WINDOWS)
719
if(!check_capabilities() && !am_i_running_as_root() && !check_proc_1_io()) {
720
uid_t uid = getuid(), euid = geteuid();
721
-#ifdef HAVE_SYS_CAPABILITY_H
721
+#ifdef HAVE_CAPABILITY
722
netdata_log_error("apps.plugin should either run as root (now running with uid %u, euid %u) or have special capabilities. "
723
"Without these, apps.plugin cannot report disk I/O utilization of other processes. "
724
"To enable capabilities run: sudo setcap cap_dac_read_search,cap_sys_ptrace+ep %s; "
src/collectors/debugfs.plugin/debugfs_plugin.c
+2
-2
@@ -50,7 +50,7 @@ static struct debugfs_module {
50
{.name = NULL, .enabled = CONFIG_BOOLEAN_NO, .func = NULL}
51
};
52
53
-#ifdef HAVE_SYS_CAPABILITY_H
53
+#ifdef HAVE_CAPABILITY
54
static int debugfs_check_capabilities()
55
{
56
cap_t caps = cap_get_proc();
@@ -204,7 +204,7 @@ int main(int argc, char **argv)
204
// FIXME: remove debugfs_check_sys_permission() after https://github.com/netdata/netdata/issues/15048 is fixed
205
if (!debugfs_check_capabilities() && !debugfs_am_i_running_as_root() && !debugfs_check_sys_permission()) {
206
uid_t uid = getuid(), euid = geteuid();
207
-#ifdef HAVE_SYS_CAPABILITY_H
207
+#ifdef HAVE_CAPABILITY
208
netdata_log_error(
209
"debugfs.plugin should either run as root (now running with uid %u, euid %u) or have special capabilities. "
210
"Without these, debugfs.plugin cannot access /sys/kernel/debug. "
src/collectors/utils/nd-run.c
new
+216
@@ -0,0 +1,216 @@
1
+#include <unistd.h>
2
+#include <errno.h>
3
+#include <sys/types.h>
4
+#include <grp.h>
5
+#include <pwd.h>
6
+#include <stdio.h>
7
+#include <stdlib.h>
8
+#include <string.h>
9
+
10
+#include "config.h"
11
+
12
+#ifdef HAVE_CAPABILITY
13
+#include <sys/capability.h>
14
+#endif
15
+
16
+#define FALLBACK_USER "nobody"
17
+
18
+void show_help() {
19
+ fprintf(stdout, "\n");
20
+ fprintf(stdout, "nd-run\n");
21
+ fprintf(stdout, "\n");
22
+ fprintf(stdout, "Copyright 2025 Netdata Inc.\n");
23
+ fprintf(stdout, "\n");
24
+ fprintf(stdout, "A helper to run a command as an unprivileged user without any extra privileges\n");
25
+ fprintf(stdout, "\n");
26
+ fprintf(stdout, "Defaults to running the command as '%s', but will fall back to '%s' if '%s' is not found on the system.\n", NETDATA_USER, FALLBACK_USER, NETDATA_USER);
27
+ fprintf(stdout, "\n");
28
+ fprintf(stdout, "If it's not possible to switch users, the command will run as the current user instead.\n");
29
+ #ifdef HAVE_CAPABILITY
30
+ fprintf(stdout, "\n");
31
+ fprintf(stdout, "Regardless of whether it switched users, all capabilities will be dropped.\n");
32
+ #endif
33
+}
34
+
35
+static void fatal(const char *msg) {
36
+ perror(msg);
37
+ exit(EXIT_FAILURE);
38
+}
39
+
40
+#ifdef HAVE_CAPABILITY
41
+static void clear_caps() {
42
+ // Clear out all capabilities
43
+ //
44
+ // This does not require any special privileges since it is reducing
45
+ // the process’s privileges.
46
+ cap_t caps = cap_init();
47
+
48
+ if (caps == NULL) fatal("cap_init");
49
+
50
+ if (cap_clear(caps) == -1) {
51
+ cap_free(caps);
52
+ fatal("cap_clear");
53
+ }
54
+
55
+ if (cap_set_proc(caps) == -1) {
56
+ cap_free(caps);
57
+ fatal("cap_set_proc");
58
+ }
59
+
60
+ cap_free(caps);
61
+}
62
+#endif
63
+
64
+static void set_env_var(const char *name, const char *value) {
65
+ // Set an environment variable if the specified value is not a NULL pointer.
66
+ char buf[64];
67
+
68
+ if (value == NULL) {
69
+ return;
70
+ }
71
+
72
+ if (setenv(name, value, 1) != 0) {
73
+ snprintf(buf, 64, "setenv %s", name);
74
+ perror(buf);
75
+ }
76
+}
77
+
78
+static void clean_environment(struct passwd *pw) {
79
+ // Explicitly scrub the environment, only passing on a few things
80
+ // we know are needed to make things work correctly.
81
+
82
+ // First, save copies of the environment variables we want to keep.
83
+ // We must copy them before clearing the environment, as getenv()
84
+ // returns pointers into the environment block which will be invalidated.
85
+ char *saved_path = NULL;
86
+ char *saved_tz = NULL;
87
+ char *saved_tzdir = NULL;
88
+ char *saved_tmpdir = NULL;
89
+ char *saved_pwd = NULL;
90
+
91
+ const char *tmp;
92
+ if ((tmp = getenv("PATH")) != NULL) {
93
+ saved_path = strdup(tmp);
94
+ if (!saved_path) fatal("strdup PATH");
95
+ }
96
+ if ((tmp = getenv("TZ")) != NULL) {
97
+ saved_tz = strdup(tmp);
98
+ if (!saved_tz) fatal("strdup TZ");
99
+ }
100
+ if ((tmp = getenv("TZDIR")) != NULL) {
101
+ saved_tzdir = strdup(tmp);
102
+ if (!saved_tzdir) fatal("strdup TZDIR");
103
+ }
104
+ if ((tmp = getenv("TMPDIR")) != NULL) {
105
+ saved_tmpdir = strdup(tmp);
106
+ if (!saved_tmpdir) fatal("strdup TMPDIR");
107
+ }
108
+ if ((tmp = getenv("PWD")) != NULL) {
109
+ saved_pwd = strdup(tmp);
110
+ if (!saved_pwd) fatal("strdup PWD");
111
+ }
112
+
113
+ // Now clear the environment
114
+ #ifdef HAVE_CLEARENV
115
+ clearenv();
116
+ #else
117
+ extern char **environ;
118
+ environ = NULL;
119
+ #endif
120
+
121
+ // Set the new environment with our saved values
122
+ set_env_var("USER", pw->pw_name);
123
+ set_env_var("LOGNAME", pw->pw_name);
124
+ set_env_var("HOME", pw->pw_dir);
125
+ set_env_var("SHELL", "/bin/sh"); // Ignore user default shell
126
+ set_env_var("LC_ALL", "C"); // Force C locale
127
+ set_env_var("PATH", saved_path);
128
+ set_env_var("PWD", saved_pwd);
129
+ set_env_var("TZ", saved_tz);
130
+ set_env_var("TZDIR", saved_tzdir);
131
+ set_env_var("TMPDIR", (saved_tmpdir == NULL) ? "/tmp" : saved_tmpdir); // Use a sane default for TMPDIR if it wasn't set.
132
+
133
+ // Free the saved copies
134
+ free(saved_path);
135
+ free(saved_tz);
136
+ free(saved_tzdir);
137
+ free(saved_tmpdir);
138
+ free(saved_pwd);
139
+}
140
+
141
+int main(int argc, char *argv[]) {
142
+ if (argc < 2) {
143
+ show_help();
144
+ return EXIT_FAILURE;
145
+ }
146
+
147
+ uid_t euid = geteuid();
148
+
149
+ struct passwd *pw = getpwnam(NETDATA_USER);
150
+ if (!pw) {
151
+ fprintf(stderr, "User '%s' not found, falling back to '%s'\n",
152
+ NETDATA_USER, FALLBACK_USER);
153
+ pw = getpwnam(FALLBACK_USER);
154
+ if (!pw) {
155
+ fprintf(stderr, "Fallback user '%s' not found either\n", FALLBACK_USER);
156
+ return EXIT_FAILURE;
157
+ }
158
+ }
159
+
160
+ if (euid != pw->pw_uid) {
161
+ fprintf(stderr, "Attempting to run as user: %s (UID=%d, GID=%d)\n", pw->pw_name, (int)pw->pw_uid, (int)pw->pw_gid);
162
+
163
+ // Set supplementary groups for this user (must be done before dropping privs)
164
+ if (initgroups(pw->pw_name, pw->pw_gid) != 0) {
165
+ if (euid == 0) {
166
+ perror("initgroups");
167
+
168
+ if (setgroups(0, NULL) != 0) {
169
+ fatal("setgroups");
170
+ }
171
+ } else if (errno != EPERM) {
172
+ fatal("initgroups");
173
+ }
174
+ }
175
+
176
+ // Drop GID then UID. Prefer setres* when available to also drop saved IDs.
177
+ // Linux/BSD generally provide setresgid/setresuid; macOS does not.
178
+ #ifdef HAVE_SETRESGID
179
+ if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) != 0) {
180
+ if (euid == 0 || errno != EPERM) {
181
+ fatal("setresgid");
182
+ }
183
+ }
184
+ #else
185
+ if (setgid(pw->pw_gid) != 0) {
186
+ if (euid == 0 || errno != EPERM) {
187
+ fatal("setgid");
188
+ }
189
+ }
190
+ #endif
191
+
192
+ #ifdef HAVE_SETRESUID
193
+ if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) != 0) {
194
+ if (euid == 0 || errno != EPERM) {
195
+ fatal("setresuid");
196
+ }
197
+ }
198
+ #else
199
+ if (setuid(pw->pw_uid) != 0) {
200
+ if (euid == 0 || errno != EPERM) {
201
+ fatal("setuid");
202
+ }
203
+ }
204
+ #endif
205
+ }
206
+
207
+ #ifdef HAVE_CAPABILITY
208
+ clear_caps();
209
+ #endif
210
+
211
+ clean_environment(pw);
212
+
213
+ // Exec the requested command (replaces the current process on success)
214
+ execvp(argv[1], &argv[1]);
215
+ fatal("execvp"); // Only reached on error
216
+}
src/go/pkg/buildinfo/buildinfo.go
+9
-3
@@ -2,13 +2,19 @@
2
3
package buildinfo
4
5
-// Version stores the agent's version number. It's set during the build process using build flags.
5
+// The variables in this file are set during the build process using linker flags.
6
+
7
+// Version stores the agent's version number.
8
var Version = "v0.0.0"
9
10
// UserConfigDir stores the path to the user configuration directory.
9
-// This value is set during the build process using build flags.
11
var UserConfigDir = ""
12
13
// StockConfigDir stores the path to the stock (default) configuration directory.
13
-// This value is set during the build process using build flags.
14
var StockConfigDir = ""
15
+
16
+// PluginsDir stores the directory where pulgins were installed at build time.
17
+var PluginsDir = "/usr/libexec/netdata/plugins.d"
18
+
19
+// NetdataBinDir stores the directory where executables were installed at build time.
20
+var NetdataBinDir = "/usr/sbin"
src/go/plugin/go.d/pkg/README.md
+1
@@ -1,5 +1,6 @@
1
# Helper Packages
2
3
+- if you need to run an external command, please use [`ndexec`](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/pkg/ndexec).
4
- if you need IP ranges consider to
5
use [`iprange`](/src/go/plugin/go.d/pkg/iprange).
6
- if you parse an application log files, then [`log`](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/pkg/logs) is
src/go/plugin/go.d/pkg/ndexec/ndexec.go
new
+106
@@ -0,0 +1,106 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package ndexec
4
+
5
+import (
6
+ "context"
7
+ "fmt"
8
+ "os/exec"
9
+ "path/filepath"
10
+ "time"
11
+
12
+ "github.com/netdata/netdata/go/plugins/logger"
13
+ "github.com/netdata/netdata/go/plugins/pkg/buildinfo"
14
+)
15
+
16
+// CommandUnprivileged runs a command without any extra privileges and
17
+// returns the exec.Cmd instance.
18
+//
19
+// ctx is a context.Context to use to run the command. logger is a Logger
20
+// instance to use to log the command to be executed. timeout indicates
21
+// the timeout for the command. arg is a list of the command arguments,
22
+// with the first string in the slice being the command to run.
23
+//
24
+// This invokes the command and logs a debug message that the command
25
+// is being executed, and then returns the exec.Cmd object for the command.
26
+func CommandUnprivileged(ctx context.Context, logger *logger.Logger, arg ...string) *exec.Cmd {
27
+ ndrunPath := filepath.Join(buildinfo.NetdataBinDir, "nd-run")
28
+
29
+ cmd := exec.CommandContext(ctx, ndrunPath, arg...)
30
+ if logger != nil {
31
+ logger.Debugf("executing '%s'", cmd)
32
+ }
33
+
34
+ return cmd
35
+}
36
+
37
+// RunUnprivileged runs a command without any inherited privileges via
38
+// the nd-run helper.
39
+//
40
+// logger is a Logger instance to use to log the command to be executed.
41
+// timeout indicates the timeout for the command. arg is a list of the
42
+// command arguments, with the first string in the slice being the command
43
+// to run.
44
+//
45
+// This handles constructing the context for execution, logs a debug
46
+// message that the command is being executed, and checks for errors in
47
+// the command invocation, then returns the command output.
48
+func RunUnprivileged(logger *logger.Logger, timeout time.Duration, arg ...string) ([]byte, error) {
49
+ ctx, cancel := context.WithTimeout(context.Background(), timeout)
50
+ defer cancel()
51
+
52
+ cmd := CommandUnprivileged(ctx, logger, arg...)
53
+
54
+ bs, err := cmd.Output()
55
+ if err != nil {
56
+ return nil, fmt.Errorf("error on '%s': %v", cmd, err)
57
+ }
58
+
59
+ return bs, nil
60
+}
61
+
62
+// CommandNDSudo runs a command via the ndsudo helper and returns the exec.Cmd instance.
63
+//
64
+// ctx is a context.Context to use to run the command. logger is a Logger
65
+// instance to use to log the command to be executed. timeout indicates
66
+// the timeout for the command. arg is a list of the command arguments,
67
+// with the first string in the slice being the command to run.
68
+//
69
+// This invokes the command and logs a debug message that the command
70
+// is being executed, and then returns the exec.Cmd object for the command.
71
+func CommandNDSudo(ctx context.Context, logger *logger.Logger, arg ...string) *exec.Cmd {
72
+ ndsudoPath := filepath.Join(buildinfo.PluginsDir, "ndsudo")
73
+
74
+ cmd := exec.CommandContext(ctx, ndsudoPath, arg...)
75
+ if logger != nil {
76
+ logger.Debugf("executing '%s'", cmd)
77
+ }
78
+
79
+ return cmd
80
+}
81
+
82
+// RunNDSudo runs a command via the ndsudo helper.
83
+//
84
+// logger is a Logger instance to use to log the command to be executed.
85
+// timeout indicates the timeout for the command. arg is a list of the
86
+// command arguments, with the first string in the slice being the command
87
+// to run.
88
+//
89
+// This handles constructing the context for execution, logs a debug
90
+// message that the command is being executed, and checks for errors in
91
+// the command invocation, then returns the command output.
92
+//
93
+// The command to be run must also be properly handled by ndsudo.
94
+func RunNDSudo(logger *logger.Logger, timeout time.Duration, arg ...string) ([]byte, error) {
95
+ ctx, cancel := context.WithTimeout(context.Background(), timeout)
96
+ defer cancel()
97
+
98
+ cmd := CommandNDSudo(ctx, logger, arg...)
99
+
100
+ bs, err := cmd.Output()
101
+ if err != nil {
102
+ return nil, fmt.Errorf("error on '%s': %v", cmd, err)
103
+ }
104
+
105
+ return bs, nil
106
+}
src/libnetdata/common.h
+1
-1
@@ -254,7 +254,7 @@ typedef uint32_t uid_t;
254
255
#include <zlib.h>
256
257
-#ifdef HAVE_SYS_CAPABILITY_H
257
+#ifdef HAVE_CAPABILITY
258
#include <sys/capability.h>
259
#endif
260