mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2025-12-20 09:20:04 +01:00
libnm: add function to copy a certificate or key as user
Add a new public function nm_utils_copy_cert_as_user() to libnm. It reads a certificate or key file on behalf of the given user and writes it to a directory in /run/NetworkManager. It is useful for VPN plugins that run as root and need to verify that the user owning the connection (the one listed in the connection.permissions property) can access the file. (cherry picked from commit1a52bbe7c9) (cherry picked from commit3d85bace3d) (cherry picked from commit4587832735)
This commit is contained in:
parent
29d190ef95
commit
bb0d29a8f1
11 changed files with 378 additions and 6 deletions
2
NEWS
2
NEWS
|
|
@ -8,6 +8,8 @@ Overview of changes since NetworkManager-1.52.1
|
||||||
* For private connections (the ones that specify a user in the
|
* For private connections (the ones that specify a user in the
|
||||||
"connection.permissions" property), verify that the user can access
|
"connection.permissions" property), verify that the user can access
|
||||||
the 802.1X certificates and keys set in the connection.
|
the 802.1X certificates and keys set in the connection.
|
||||||
|
* Introduce a libnm function that can be used by VPN plugins to check
|
||||||
|
user permissions on certificate and keys.
|
||||||
|
|
||||||
=============================================
|
=============================================
|
||||||
NetworkManager-1.52.1
|
NetworkManager-1.52.1
|
||||||
|
|
|
||||||
|
|
@ -890,6 +890,7 @@ fi
|
||||||
%{_libexecdir}/nm-dispatcher
|
%{_libexecdir}/nm-dispatcher
|
||||||
%{_libexecdir}/nm-initrd-generator
|
%{_libexecdir}/nm-initrd-generator
|
||||||
%{_libexecdir}/nm-daemon-helper
|
%{_libexecdir}/nm-daemon-helper
|
||||||
|
%{_libexecdir}/nm-libnm-helper
|
||||||
%{_libexecdir}/nm-priv-helper
|
%{_libexecdir}/nm-priv-helper
|
||||||
%dir %{_libdir}/%{name}
|
%dir %{_libdir}/%{name}
|
||||||
%dir %{nmplugindir}
|
%dir %{nmplugindir}
|
||||||
|
|
|
||||||
|
|
@ -2051,5 +2051,6 @@ global:
|
||||||
|
|
||||||
libnm_1_52_2 {
|
libnm_1_52_2 {
|
||||||
global:
|
global:
|
||||||
|
nm_utils_copy_cert_as_user;
|
||||||
nm_vpn_plugin_info_supports_safe_private_file_access;
|
nm_vpn_plugin_info_supports_safe_private_file_access;
|
||||||
} libnm_1_52_0;
|
} libnm_1_52_0;
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ test_units = [
|
||||||
'test-nm-client',
|
'test-nm-client',
|
||||||
'test-remote-settings-client',
|
'test-remote-settings-client',
|
||||||
'test-secret-agent',
|
'test-secret-agent',
|
||||||
|
'test-copy-cert-as-user'
|
||||||
]
|
]
|
||||||
|
|
||||||
foreach test_unit: test_units
|
foreach test_unit: test_units
|
||||||
|
|
@ -37,12 +38,15 @@ foreach test_unit: test_units
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# test-copy-cert-as-user is a manual test, don't run it automatically
|
||||||
|
if test_unit != 'test-copy-cert-as-user'
|
||||||
test(
|
test(
|
||||||
'src/libnm-client-impl/tests/' + test_unit,
|
'src/libnm-client-impl/tests/' + test_unit,
|
||||||
test_script,
|
test_script,
|
||||||
timeout: 90,
|
timeout: 90,
|
||||||
args: test_args + [exe.full_path()],
|
args: test_args + [exe.full_path()],
|
||||||
)
|
)
|
||||||
|
endif
|
||||||
endforeach
|
endforeach
|
||||||
|
|
||||||
if enable_introspection
|
if enable_introspection
|
||||||
|
|
|
||||||
32
src/libnm-client-impl/tests/test-copy-cert-as-user.c
Normal file
32
src/libnm-client-impl/tests/test-copy-cert-as-user.c
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is a program to manually test the
|
||||||
|
* nm_utils_copy_cert_as_user() libnm function.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "libnm-client-impl/nm-default-libnm.h"
|
||||||
|
|
||||||
|
#include "nm-utils.h"
|
||||||
|
|
||||||
|
int
|
||||||
|
main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
gs_free_error GError *error = NULL;
|
||||||
|
gs_free char *filename = NULL;
|
||||||
|
|
||||||
|
if (argc != 3) {
|
||||||
|
g_printerr("Usage: %s <FILE> <USER>\n", argv[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
filename = nm_utils_copy_cert_as_user(argv[1], argv[2], &error);
|
||||||
|
if (!filename) {
|
||||||
|
g_printerr("Error: %s\n", error->message);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_print("%s\n", filename);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
@ -93,6 +93,7 @@ def syms_from_ver(verfile):
|
||||||
# hardcode it.
|
# hardcode it.
|
||||||
c_syms["nm_ethtool_optname_is_feature"] = "1.20"
|
c_syms["nm_ethtool_optname_is_feature"] = "1.20"
|
||||||
c_syms["nm_setting_bond_port_get_prio"] = "1.44"
|
c_syms["nm_setting_bond_port_get_prio"] = "1.44"
|
||||||
|
c_syms["nm_utils_copy_cert_as_user"] = "1.56"
|
||||||
c_syms["nm_vpn_plugin_info_supports_safe_private_file_access"] = "1.56"
|
c_syms["nm_vpn_plugin_info_supports_safe_private_file_access"] = "1.56"
|
||||||
|
|
||||||
return c_syms
|
return c_syms
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
#include <linux/pkt_sched.h>
|
#include <linux/pkt_sched.h>
|
||||||
#include <linux/if_infiniband.h>
|
#include <linux/if_infiniband.h>
|
||||||
|
|
||||||
|
#include "libnm-glib-aux/nm-io-utils.h"
|
||||||
#include "libnm-glib-aux/nm-uuid.h"
|
#include "libnm-glib-aux/nm-uuid.h"
|
||||||
#include "libnm-glib-aux/nm-json-aux.h"
|
#include "libnm-glib-aux/nm-json-aux.h"
|
||||||
#include "libnm-glib-aux/nm-str-buf.h"
|
#include "libnm-glib-aux/nm-str-buf.h"
|
||||||
|
|
@ -6195,3 +6196,257 @@ nm_utils_ensure_gtypes(void)
|
||||||
for (meta_type = 0; meta_type < _NM_META_SETTING_TYPE_NUM; meta_type++)
|
for (meta_type = 0; meta_type < _NM_META_SETTING_TYPE_NUM; meta_type++)
|
||||||
nm_meta_setting_infos[meta_type].get_setting_gtype();
|
nm_meta_setting_infos[meta_type].get_setting_gtype();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*****************************************************************************/
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
GPid pid;
|
||||||
|
GSource *child_watch_source;
|
||||||
|
GMainLoop *loop;
|
||||||
|
GError *error;
|
||||||
|
|
||||||
|
int child_stdout;
|
||||||
|
int child_stderr;
|
||||||
|
|
||||||
|
GSource *output_source;
|
||||||
|
GSource *error_source;
|
||||||
|
|
||||||
|
NMStrBuf output_buffer;
|
||||||
|
NMStrBuf error_buffer;
|
||||||
|
} HelperInfo;
|
||||||
|
|
||||||
|
static void
|
||||||
|
helper_complete(HelperInfo *info, GError *error_take)
|
||||||
|
{
|
||||||
|
if (error_take) {
|
||||||
|
if (!info->error)
|
||||||
|
info->error = error_take;
|
||||||
|
else
|
||||||
|
g_error_free(error_take);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info->output_source || info->error_source || info->pid != -1) {
|
||||||
|
/* Wait that the pipe is closed and process has terminated */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info->error && info->error_buffer.len > 0) {
|
||||||
|
/* Prefer the message from stderr as it's more informative */
|
||||||
|
g_error_free(info->error);
|
||||||
|
info->error = g_error_new(NM_CONNECTION_ERROR,
|
||||||
|
NM_CONNECTION_ERROR_FAILED,
|
||||||
|
"%s",
|
||||||
|
nm_str_buf_get_str(&info->error_buffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
g_main_loop_quit(info->loop);
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
helper_have_err_data(int fd, GIOCondition condition, gpointer user_data)
|
||||||
|
{
|
||||||
|
HelperInfo *info = user_data;
|
||||||
|
gssize n_read;
|
||||||
|
GError *error = NULL;
|
||||||
|
|
||||||
|
n_read = nm_utils_fd_read(fd, &info->error_buffer);
|
||||||
|
|
||||||
|
if (n_read > 0)
|
||||||
|
return G_SOURCE_CONTINUE;
|
||||||
|
|
||||||
|
nm_clear_g_source_inst(&info->error_source);
|
||||||
|
nm_clear_fd(&info->child_stderr);
|
||||||
|
|
||||||
|
if (n_read < 0) {
|
||||||
|
error = g_error_new(NM_UTILS_ERROR,
|
||||||
|
NM_UTILS_ERROR_UNKNOWN,
|
||||||
|
"read from process returned %d (%s)",
|
||||||
|
(int) -n_read,
|
||||||
|
nm_strerror_native((int) -n_read));
|
||||||
|
}
|
||||||
|
|
||||||
|
helper_complete(info, error);
|
||||||
|
return G_SOURCE_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
helper_have_data(int fd, GIOCondition condition, gpointer user_data)
|
||||||
|
{
|
||||||
|
HelperInfo *info = user_data;
|
||||||
|
gssize n_read;
|
||||||
|
GError *error = NULL;
|
||||||
|
|
||||||
|
n_read = nm_utils_fd_read(fd, &info->output_buffer);
|
||||||
|
|
||||||
|
if (n_read > 0)
|
||||||
|
return G_SOURCE_CONTINUE;
|
||||||
|
|
||||||
|
nm_clear_g_source_inst(&info->output_source);
|
||||||
|
nm_clear_fd(&info->child_stdout);
|
||||||
|
|
||||||
|
if (n_read < 0) {
|
||||||
|
error = g_error_new(NM_UTILS_ERROR,
|
||||||
|
NM_UTILS_ERROR_UNKNOWN,
|
||||||
|
"read from process returned %d (%s)",
|
||||||
|
(int) -n_read,
|
||||||
|
nm_strerror_native((int) -n_read));
|
||||||
|
}
|
||||||
|
|
||||||
|
helper_complete(info, error);
|
||||||
|
return G_SOURCE_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
helper_child_terminated(GPid pid, int status, gpointer user_data)
|
||||||
|
{
|
||||||
|
HelperInfo *info = user_data;
|
||||||
|
gs_free char *status_desc = NULL;
|
||||||
|
GError *error = NULL;
|
||||||
|
|
||||||
|
info->pid = -1;
|
||||||
|
nm_clear_g_source_inst(&info->child_watch_source);
|
||||||
|
|
||||||
|
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
|
||||||
|
if (!status_desc)
|
||||||
|
status_desc = nm_utils_get_process_exit_status_desc(status);
|
||||||
|
error =
|
||||||
|
g_error_new(NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, "helper process %s", status_desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
helper_complete(info, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define RUN_CERT_DIR NMRUNDIR "/cert"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* nm_utils_copy_cert_as_user:
|
||||||
|
* @filename: the file name of the certificate or key to copy
|
||||||
|
* @user: the user to impersonate when reading the file
|
||||||
|
* @error: (nullable): return location for a #GError, or %NULL
|
||||||
|
*
|
||||||
|
* Reads @filename on behalf of user @user and writes the
|
||||||
|
* content to a new file in /run/NetworkManager/cert/.
|
||||||
|
* The new file has permission 600 and is owned by root.
|
||||||
|
*
|
||||||
|
* This function is useful for VPN plugins that run as root and need
|
||||||
|
* to verify that the user owning the connection (the one listed in the
|
||||||
|
* connection.permissions property) can access the file.
|
||||||
|
*
|
||||||
|
* Returns: (transfer full): the name of the new temporary file. Or %NULL
|
||||||
|
* if an error occurred, including when the given user can't access the
|
||||||
|
* file.
|
||||||
|
*
|
||||||
|
* Since: 1.56, 1.52.2
|
||||||
|
*/
|
||||||
|
char *
|
||||||
|
nm_utils_copy_cert_as_user(const char *filename, const char *user, GError **error)
|
||||||
|
{
|
||||||
|
gs_unref_bytes GBytes *bytes = NULL;
|
||||||
|
char dst_path[] = RUN_CERT_DIR "/XXXXXX";
|
||||||
|
HelperInfo info = {
|
||||||
|
.child_stdout = -1,
|
||||||
|
.child_stderr = -1,
|
||||||
|
};
|
||||||
|
GMainContext *context;
|
||||||
|
int fd = -1;
|
||||||
|
|
||||||
|
g_return_val_if_fail(filename, NULL);
|
||||||
|
g_return_val_if_fail(user, NULL);
|
||||||
|
g_return_val_if_fail(!error || !*error, NULL);
|
||||||
|
|
||||||
|
if (geteuid() != 0) {
|
||||||
|
g_set_error_literal(error,
|
||||||
|
NM_CONNECTION_ERROR,
|
||||||
|
NM_CONNECTION_ERROR_INVALID_PROPERTY,
|
||||||
|
_("This function needs to be called by root"));
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!g_spawn_async_with_pipes(
|
||||||
|
"/",
|
||||||
|
(char **)
|
||||||
|
NM_MAKE_STRV(LIBEXECDIR "/nm-libnm-helper", "read-file-as-user", filename, user),
|
||||||
|
(char **) NM_MAKE_STRV(),
|
||||||
|
G_SPAWN_CLOEXEC_PIPES | G_SPAWN_DO_NOT_REAP_CHILD,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
&info.pid,
|
||||||
|
NULL,
|
||||||
|
&info.child_stdout,
|
||||||
|
&info.child_stderr,
|
||||||
|
error)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
context = g_main_context_new();
|
||||||
|
info.loop = g_main_loop_new(context, FALSE);
|
||||||
|
|
||||||
|
/* Watch process */
|
||||||
|
info.child_watch_source = nm_g_child_watch_source_new(info.pid,
|
||||||
|
G_PRIORITY_DEFAULT,
|
||||||
|
helper_child_terminated,
|
||||||
|
&info,
|
||||||
|
NULL);
|
||||||
|
g_source_attach(info.child_watch_source, context);
|
||||||
|
|
||||||
|
/* Watch stdout */
|
||||||
|
info.output_buffer = NM_STR_BUF_INIT(0, FALSE);
|
||||||
|
info.output_source = nm_g_unix_fd_source_new(info.child_stdout,
|
||||||
|
G_IO_IN | G_IO_ERR | G_IO_HUP,
|
||||||
|
G_PRIORITY_DEFAULT,
|
||||||
|
helper_have_data,
|
||||||
|
&info,
|
||||||
|
NULL);
|
||||||
|
g_source_attach(info.output_source, context);
|
||||||
|
|
||||||
|
/* Watch stderr */
|
||||||
|
info.error_buffer = NM_STR_BUF_INIT(0, FALSE);
|
||||||
|
info.error_source = nm_g_unix_fd_source_new(info.child_stderr,
|
||||||
|
G_IO_IN | G_IO_ERR | G_IO_HUP,
|
||||||
|
G_PRIORITY_DEFAULT,
|
||||||
|
helper_have_err_data,
|
||||||
|
&info,
|
||||||
|
NULL);
|
||||||
|
g_source_attach(info.error_source, context);
|
||||||
|
|
||||||
|
/* Wait termination */
|
||||||
|
g_main_loop_run(info.loop);
|
||||||
|
g_clear_pointer(&info.loop, g_main_loop_unref);
|
||||||
|
g_clear_pointer(&context, g_main_context_unref);
|
||||||
|
|
||||||
|
if (info.error) {
|
||||||
|
nm_str_buf_destroy(&info.output_buffer);
|
||||||
|
nm_str_buf_destroy(&info.error_buffer);
|
||||||
|
g_propagate_error(error, g_steal_pointer(&info.error));
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Write the data to a new file */
|
||||||
|
|
||||||
|
bytes = g_bytes_new(nm_str_buf_get_str_unsafe(&info.output_buffer), info.output_buffer.len);
|
||||||
|
nm_str_buf_destroy(&info.output_buffer);
|
||||||
|
nm_str_buf_destroy(&info.error_buffer);
|
||||||
|
|
||||||
|
mkdir(RUN_CERT_DIR, 0600);
|
||||||
|
fd = mkstemp(dst_path);
|
||||||
|
if (fd < 0) {
|
||||||
|
g_set_error_literal(error,
|
||||||
|
NM_CONNECTION_ERROR,
|
||||||
|
NM_CONNECTION_ERROR_INVALID_PROPERTY,
|
||||||
|
_("Failure creating the temporary file"));
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
nm_close(fd);
|
||||||
|
|
||||||
|
if (!nm_utils_file_set_contents(dst_path,
|
||||||
|
g_bytes_get_data(bytes, NULL),
|
||||||
|
g_bytes_get_size(bytes),
|
||||||
|
0600,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
error)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return g_strdup(dst_path);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -261,6 +261,9 @@ nm_utils_base64secret_decode(const char *base64_key, gsize required_key_len, gui
|
||||||
NM_AVAILABLE_IN_1_42
|
NM_AVAILABLE_IN_1_42
|
||||||
void nm_utils_ensure_gtypes(void);
|
void nm_utils_ensure_gtypes(void);
|
||||||
|
|
||||||
|
NM_AVAILABLE_IN_1_52_2
|
||||||
|
char *nm_utils_copy_cert_as_user(const char *filename, const char *user, GError **error);
|
||||||
|
|
||||||
G_END_DECLS
|
G_END_DECLS
|
||||||
|
|
||||||
#endif /* __NM_UTILS_H__ */
|
#endif /* __NM_UTILS_H__ */
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,14 @@ all the threads of the process).
|
||||||
|
|
||||||
This is not directly useful to the user.
|
This is not directly useful to the user.
|
||||||
|
|
||||||
|
nm-libnm-helper
|
||||||
|
---------------
|
||||||
|
|
||||||
|
A internal helper application that is spawned by libnm to perform
|
||||||
|
certain actions without impacting the calling process.
|
||||||
|
|
||||||
|
This is not directly useful to the user.
|
||||||
|
|
||||||
nm-priv-helper
|
nm-priv-helper
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,26 @@ executable(
|
||||||
install_dir: nm_libexecdir,
|
install_dir: nm_libexecdir,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# nm-libnm-helper
|
||||||
|
|
||||||
|
executable(
|
||||||
|
'nm-libnm-helper',
|
||||||
|
['nm-libnm-helper.c'],
|
||||||
|
include_directories : [
|
||||||
|
src_inc,
|
||||||
|
top_inc,
|
||||||
|
],
|
||||||
|
dependencies: [
|
||||||
|
glib_dep,
|
||||||
|
],
|
||||||
|
link_with: [
|
||||||
|
libnm_glib_aux,
|
||||||
|
libnm_std_aux,
|
||||||
|
],
|
||||||
|
install: true,
|
||||||
|
install_dir: nm_libexecdir,
|
||||||
|
)
|
||||||
|
|
||||||
# nm-priv-helper
|
# nm-priv-helper
|
||||||
|
|
||||||
configure_file(
|
configure_file(
|
||||||
|
|
|
||||||
45
src/nm-helpers/nm-libnm-helper.c
Normal file
45
src/nm-helpers/nm-libnm-helper.c
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||||
|
|
||||||
|
#include "libnm-std-aux/nm-default-std.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
enum {
|
||||||
|
RETURN_SUCCESS = 0,
|
||||||
|
RETURN_INVALID_CMD = 1,
|
||||||
|
RETURN_INVALID_ARGS = 2,
|
||||||
|
RETURN_ERROR = 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int
|
||||||
|
read_file_as_user(const char *filename, const char *user)
|
||||||
|
{
|
||||||
|
char error[1024];
|
||||||
|
|
||||||
|
if (!nm_utils_set_effective_user(user, error, sizeof(error))) {
|
||||||
|
fprintf(stderr, "Failed to set effective user '%s': %s", user, error);
|
||||||
|
return RETURN_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!nm_utils_read_file_to_stdout(filename, error, sizeof(error))) {
|
||||||
|
fprintf(stderr, "Failed to read file '%s' as user '%s': %s", filename, user, error);
|
||||||
|
return RETURN_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
return RETURN_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
if (argc <= 1)
|
||||||
|
return RETURN_INVALID_CMD;
|
||||||
|
|
||||||
|
if (nm_streq(argv[1], "read-file-as-user")) {
|
||||||
|
if (argc != 4)
|
||||||
|
return RETURN_INVALID_ARGS;
|
||||||
|
return read_file_as_user(argv[2], argv[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return RETURN_INVALID_CMD;
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue