system-settings: protect system connection secrets with PolicyKit

So that normal users who have PolicyKit authorization to edit system connections
can read secrets, move system connection secrets logic into the system connection
service from libnm-glib, and protect it with PolicyKit checks.  Convert the
ifcfg-rh plugin over to using NMSysconfigConnection so that it can take advantage
of the new PolicyKit protection.
This commit is contained in:
Dan Williams
2009-04-04 11:37:11 -04:00
parent 67ffcfab11
commit 63f2c0bfbe
9 changed files with 284 additions and 126 deletions

View File

@@ -375,103 +375,6 @@ impl_exported_connection_delete (NMExportedConnection *connection,
return success; return success;
} }
static GValue *
string_to_gvalue (const char *str)
{
GValue *val;
val = g_slice_new0 (GValue);
g_value_init (val, G_TYPE_STRING);
g_value_set_string (val, str);
return val;
}
static void
copy_one_secret (gpointer key, gpointer value, gpointer user_data)
{
const char *value_str = (const char *) value;
if (value_str) {
g_hash_table_insert ((GHashTable *) user_data,
g_strdup ((char *) key),
string_to_gvalue (value_str));
}
}
static void
add_secrets (NMSetting *setting,
const char *key,
const GValue *value,
GParamFlags flags,
gpointer user_data)
{
GHashTable *secrets = user_data;
if (!(flags & NM_SETTING_PARAM_SECRET))
return;
if (G_VALUE_HOLDS_STRING (value)) {
const char *tmp;
tmp = g_value_get_string (value);
if (tmp)
g_hash_table_insert (secrets, g_strdup (key), string_to_gvalue (tmp));
} else if (G_VALUE_HOLDS (value, DBUS_TYPE_G_MAP_OF_STRING)) {
/* Flatten the string hash by pulling its keys/values out */
g_hash_table_foreach (g_value_get_boxed (value), copy_one_secret, secrets);
}
}
static void
destroy_gvalue (gpointer data)
{
GValue *value = (GValue *) data;
g_value_unset (value);
g_slice_free (GValue, value);
}
static void
real_get_secrets (NMExportedConnection *exported,
const gchar *setting_name,
const gchar **hints,
gboolean request_new,
DBusGMethodInvocation *context)
{
NMConnection *connection;
GError *error = NULL;
GHashTable *settings = NULL;
GHashTable *secrets = NULL;
NMSetting *setting;
connection = nm_exported_connection_get_connection (exported);
setting = nm_connection_get_setting_by_name (connection, setting_name);
if (!setting) {
g_set_error (&error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
"%s.%d - Connection didn't have requested setting '%s'.",
__FILE__, __LINE__, setting_name);
dbus_g_method_return_error (context, error);
g_error_free (error);
return;
}
/* Returned secrets are a{sa{sv}}; this is the outer a{s...} hash that
* will contain all the individual settings hashes.
*/
settings = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, (GDestroyNotify) g_hash_table_destroy);
/* Add the secrets from this setting to the inner secrets hash for this setting */
secrets = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, destroy_gvalue);
nm_setting_enumerate_values (setting, add_secrets, secrets);
g_hash_table_insert (settings, g_strdup (setting_name), secrets);
dbus_g_method_return (context, settings);
g_hash_table_destroy (settings);
}
static void static void
impl_exported_connection_get_secrets (NMExportedConnection *connection, impl_exported_connection_get_secrets (NMExportedConnection *connection,
const gchar *setting_name, const gchar *setting_name,
@@ -490,10 +393,16 @@ impl_exported_connection_get_secrets (NMExportedConnection *connection,
return; return;
} }
if (!EXPORTED_CONNECTION_CLASS (connection)->service_get_secrets) if (!EXPORTED_CONNECTION_CLASS (connection)->service_get_secrets) {
real_get_secrets (connection, setting_name, hints, request_new, context); g_set_error (&error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_SECRETS_UNAVAILABLE,
else "%s.%d - Missing implementation for ConnectionSettings::GetSecrets.",
EXPORTED_CONNECTION_CLASS (connection)->service_get_secrets (connection, setting_name, hints, request_new, context); __FILE__, __LINE__);
dbus_g_method_return_error (context, error);
g_error_free (error);
return;
}
EXPORTED_CONNECTION_CLASS (connection)->service_get_secrets (connection, setting_name, hints, request_new, context);
} }
static void static void
@@ -567,7 +476,6 @@ nm_exported_connection_class_init (NMExportedConnectionClass *exported_connectio
object_class->dispose = nm_exported_connection_dispose; object_class->dispose = nm_exported_connection_dispose;
exported_connection_class->get_settings = real_get_settings; exported_connection_class->get_settings = real_get_settings;
exported_connection_class->service_get_secrets = real_get_secrets;
/* Properties */ /* Properties */
g_object_class_install_property g_object_class_install_property

View File

@@ -40,7 +40,7 @@
#include "reader.h" #include "reader.h"
#include "nm-inotify-helper.h" #include "nm-inotify-helper.h"
G_DEFINE_TYPE (NMIfcfgConnection, nm_ifcfg_connection, NM_TYPE_EXPORTED_CONNECTION) G_DEFINE_TYPE (NMIfcfgConnection, nm_ifcfg_connection, NM_TYPE_SYSCONFIG_CONNECTION)
#define NM_IFCFG_CONNECTION_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_IFCFG_CONNECTION, NMIfcfgConnectionPrivate)) #define NM_IFCFG_CONNECTION_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_IFCFG_CONNECTION, NMIfcfgConnectionPrivate))

View File

@@ -23,7 +23,8 @@
G_BEGIN_DECLS G_BEGIN_DECLS
#include <nm-settings.h> #include <NetworkManager.h>
#include <nm-sysconfig-connection.h>
#include "nm-system-config-hal-manager.h" #include "nm-system-config-hal-manager.h"
#define NM_TYPE_IFCFG_CONNECTION (nm_ifcfg_connection_get_type ()) #define NM_TYPE_IFCFG_CONNECTION (nm_ifcfg_connection_get_type ())
@@ -38,11 +39,11 @@ G_BEGIN_DECLS
#define NM_IFCFG_CONNECTION_UDI "udi" #define NM_IFCFG_CONNECTION_UDI "udi"
typedef struct { typedef struct {
NMExportedConnection parent; NMSysconfigConnection parent;
} NMIfcfgConnection; } NMIfcfgConnection;
typedef struct { typedef struct {
NMExportedConnectionClass parent; NMSysconfigConnectionClass parent;
} NMIfcfgConnectionClass; } NMIfcfgConnectionClass;
GType nm_ifcfg_connection_get_type (void); GType nm_ifcfg_connection_get_type (void);

View File

@@ -1337,8 +1337,8 @@ connection_from_file (const char *filename,
/* We don't write connections yet */ /* We don't write connections yet */
if (connection) { if (connection) {
s_con = (NMSettingConnection *) nm_connection_get_setting (connection, NM_TYPE_SETTING_CONNECTION); s_con = (NMSettingConnection *) nm_connection_get_setting (connection, NM_TYPE_SETTING_CONNECTION);
if (s_con) // if (s_con)
g_object_set (s_con, NM_SETTING_CONNECTION_READ_ONLY, TRUE, NULL); // g_object_set (s_con, NM_SETTING_CONNECTION_READ_ONLY, TRUE, NULL);
} }
/* Don't bother reading the connection fully if it's unmanaged */ /* Don't bother reading the connection fully if it's unmanaged */

View File

@@ -355,11 +355,17 @@ nm_sysconfig_settings_init (NMSysconfigSettings *self)
{ {
NMSysconfigSettingsPrivate *priv = NM_SYSCONFIG_SETTINGS_GET_PRIVATE (self); NMSysconfigSettingsPrivate *priv = NM_SYSCONFIG_SETTINGS_GET_PRIVATE (self);
char hostname[HOST_NAME_MAX + 2]; char hostname[HOST_NAME_MAX + 2];
GError *error = NULL;
priv->connections = g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, NULL); priv->connections = g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, NULL);
priv->unmanaged_devices = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); priv->unmanaged_devices = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
priv->pol_ctx = create_polkit_context (); priv->pol_ctx = create_polkit_context (&error);
if (!priv->pol_ctx) {
g_warning ("%s: failed to create PolicyKit context: %s",
__func__,
(error && error->message) ? error->message : "(unknown)");
}
/* Grab hostname on startup and use that if no plugins provide one */ /* Grab hostname on startup and use that if no plugins provide one */
memset (hostname, 0, sizeof (hostname)); memset (hostname, 0, sizeof (hostname));

View File

@@ -62,22 +62,25 @@ pk_io_remove_watch (PolKitContext *pk_context, int watch_id)
} }
PolKitContext * PolKitContext *
create_polkit_context (void) create_polkit_context (GError **error)
{ {
static PolKitContext *global_context = NULL; static PolKitContext *global_context = NULL;
PolKitError *err; PolKitError *pk_err = NULL;
if (G_LIKELY (global_context)) if (G_LIKELY (global_context))
return polkit_context_ref (global_context); return polkit_context_ref (global_context);
global_context = polkit_context_new (); global_context = polkit_context_new ();
polkit_context_set_io_watch_functions (global_context, pk_io_add_watch, pk_io_remove_watch); polkit_context_set_io_watch_functions (global_context, pk_io_add_watch, pk_io_remove_watch);
err = NULL; if (!polkit_context_init (global_context, &pk_err)) {
if (!polkit_context_init (global_context, &err)) { g_set_error (error, NM_SYSCONFIG_SETTINGS_ERROR,
g_warning ("Cannot initialize libpolkit: %s", NM_SYSCONFIG_SETTINGS_ERROR_GENERAL,
err ? polkit_error_get_error_message (err) : "unknown error"); "%s (%d): %s",
if (err) pk_err ? polkit_error_get_error_name (pk_err) : "(unknown)",
polkit_error_free (err); pk_err ? polkit_error_get_error_code (pk_err) : -1,
pk_err ? polkit_error_get_error_message (pk_err) : "(unknown)");
if (pk_err)
polkit_error_free (pk_err);
/* PK 0.6's polkit_context_init() unrefs the global_context on failure */ /* PK 0.6's polkit_context_init() unrefs the global_context on failure */
#if (POLKIT_VERSION_MAJOR == 0) && (POLKIT_VERSION_MINOR >= 7) #if (POLKIT_VERSION_MAJOR == 0) && (POLKIT_VERSION_MINOR >= 7)

View File

@@ -28,7 +28,7 @@
#define NM_SYSCONFIG_POLICY_ACTION "org.freedesktop.network-manager-settings.system.modify" #define NM_SYSCONFIG_POLICY_ACTION "org.freedesktop.network-manager-settings.system.modify"
PolKitContext *create_polkit_context (void); PolKitContext *create_polkit_context (GError **error);
gboolean check_polkit_privileges (DBusGConnection *dbus_connection, gboolean check_polkit_privileges (DBusGConnection *dbus_connection,
PolKitContext *pol_ctx, PolKitContext *pol_ctx,
DBusGMethodInvocation *context, DBusGMethodInvocation *context,

View File

@@ -20,7 +20,9 @@
#include <NetworkManager.h> #include <NetworkManager.h>
#include "nm-sysconfig-connection.h" #include "nm-sysconfig-connection.h"
#include "nm-system-config-error.h"
#include "nm-polkit-helpers.h" #include "nm-polkit-helpers.h"
#include "nm-dbus-glib-types.h"
G_DEFINE_ABSTRACT_TYPE (NMSysconfigConnection, nm_sysconfig_connection, NM_TYPE_EXPORTED_CONNECTION) G_DEFINE_ABSTRACT_TYPE (NMSysconfigConnection, nm_sysconfig_connection, NM_TYPE_EXPORTED_CONNECTION)
@@ -29,6 +31,8 @@ G_DEFINE_ABSTRACT_TYPE (NMSysconfigConnection, nm_sysconfig_connection, NM_TYPE_
typedef struct { typedef struct {
DBusGConnection *dbus_connection; DBusGConnection *dbus_connection;
PolKitContext *pol_ctx; PolKitContext *pol_ctx;
DBusGProxy *proxy;
} NMSysconfigConnectionPrivate; } NMSysconfigConnectionPrivate;
static gboolean static gboolean
@@ -57,6 +61,231 @@ do_delete (NMExportedConnection *exported, GError **err)
return check_polkit_privileges (priv->dbus_connection, priv->pol_ctx, context, err); return check_polkit_privileges (priv->dbus_connection, priv->pol_ctx, context, err);
} }
static GValue *
string_to_gvalue (const char *str)
{
GValue *val = g_slice_new0 (GValue);
g_value_init (val, G_TYPE_STRING);
g_value_set_string (val, str);
return val;
}
static void
copy_one_secret (gpointer key, gpointer value, gpointer user_data)
{
const char *value_str = (const char *) value;
if (value_str) {
g_hash_table_insert ((GHashTable *) user_data,
g_strdup ((char *) key),
string_to_gvalue (value_str));
}
}
static void
add_secrets (NMSetting *setting,
const char *key,
const GValue *value,
GParamFlags flags,
gpointer user_data)
{
GHashTable *secrets = user_data;
if (!(flags & NM_SETTING_PARAM_SECRET))
return;
if (G_VALUE_HOLDS_STRING (value)) {
const char *tmp;
tmp = g_value_get_string (value);
if (tmp)
g_hash_table_insert (secrets, g_strdup (key), string_to_gvalue (tmp));
} else if (G_VALUE_HOLDS (value, DBUS_TYPE_G_MAP_OF_STRING)) {
/* Flatten the string hash by pulling its keys/values out */
g_hash_table_foreach (g_value_get_boxed (value), copy_one_secret, secrets);
}
}
static void
destroy_gvalue (gpointer data)
{
GValue *value = (GValue *) data;
g_value_unset (value);
g_slice_free (GValue, value);
}
static GHashTable *
real_get_secrets (NMSysconfigConnection *self,
const gchar *setting_name,
const gchar **hints,
gboolean request_new,
GError **error)
{
NMConnection *connection;
GHashTable *settings = NULL;
GHashTable *secrets = NULL;
NMSetting *setting;
connection = nm_exported_connection_get_connection (NM_EXPORTED_CONNECTION (self));
setting = nm_connection_get_setting_by_name (connection, setting_name);
if (!setting) {
g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
"%s.%d - Connection didn't have requested setting '%s'.",
__FILE__, __LINE__, setting_name);
return NULL;
}
/* Returned secrets are a{sa{sv}}; this is the outer a{s...} hash that
* will contain all the individual settings hashes.
*/
settings = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, (GDestroyNotify) g_hash_table_destroy);
/* Add the secrets from this setting to the inner secrets hash for this setting */
secrets = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, destroy_gvalue);
nm_setting_enumerate_values (setting, add_secrets, secrets);
g_hash_table_insert (settings, g_strdup (setting_name), secrets);
return settings;
}
typedef struct {
NMSysconfigConnection *self;
char *setting_name;
DBusGMethodInvocation *context;
} GetUnixUserInfo;
static GetUnixUserInfo *
get_unix_user_info_new (NMSysconfigConnection *self,
const char *setting_name,
DBusGMethodInvocation *context)
{
GetUnixUserInfo *info;
g_return_val_if_fail (self != NULL, NULL);
g_return_val_if_fail (setting_name != NULL, NULL);
g_return_val_if_fail (context != NULL, NULL);
info = g_malloc0 (sizeof (GetUnixUserInfo));
info->self = self;
info->setting_name = g_strdup (setting_name);
info->context = context;
return info;
}
static void
get_unix_user_info_free (gpointer user_data)
{
GetUnixUserInfo *info = user_data;
g_free (info->setting_name);
g_free (info);
}
static void
get_unix_user_cb (DBusGProxy *proxy, DBusGProxyCall *call, gpointer user_data)
{
GetUnixUserInfo *info = user_data;
NMSysconfigConnection *self;
NMSysconfigConnectionPrivate *priv;
GError *error = NULL;
guint32 requestor_uid = G_MAXUINT32;
GHashTable *secrets;
g_return_if_fail (info != NULL);
self = info->self;
priv = NM_SYSCONFIG_CONNECTION_GET_PRIVATE (self);
if (!dbus_g_proxy_end_call (proxy, call, &error, G_TYPE_UINT, &requestor_uid, G_TYPE_INVALID))
goto error;
/* Non-root users need PolicyKit authorization */
if (requestor_uid != 0) {
if (!check_polkit_privileges (priv->dbus_connection, priv->pol_ctx, info->context, &error))
goto error;
}
secrets = real_get_secrets (self, info->setting_name, NULL, FALSE, &error);
if (secrets) {
/* success; return secrets to caller */
dbus_g_method_return (info->context, secrets);
g_hash_table_destroy (secrets);
return;
}
if (!error) {
/* Shouldn't happen, but... */
g_set_error (&error, NM_SYSCONFIG_SETTINGS_ERROR,
NM_SYSCONFIG_SETTINGS_ERROR_GENERAL,
"%s", "Could not get secrets from connection (unknown error ocurred)");
}
error:
dbus_g_method_return_error (info->context, error);
g_clear_error (&error);
}
static void
service_get_secrets (NMExportedConnection *exported,
const gchar *setting_name,
const gchar **hints,
gboolean request_new,
DBusGMethodInvocation *context)
{
NMSysconfigConnection *self = NM_SYSCONFIG_CONNECTION (exported);
NMSysconfigConnectionPrivate *priv = NM_SYSCONFIG_CONNECTION_GET_PRIVATE (self);
GetUnixUserInfo *info;
GError *error = NULL;
char *sender = NULL;
sender = dbus_g_method_get_sender (context);
if (!sender) {
g_set_error (&error, NM_SYSCONFIG_SETTINGS_ERROR,
NM_SYSCONFIG_SETTINGS_ERROR_GENERAL,
"%s", "Could not determine D-Bus requestor to authorize GetSecrets request");
goto out;
}
if (priv->proxy)
g_object_unref (priv->proxy);
priv->proxy = dbus_g_proxy_new_for_name (priv->dbus_connection,
DBUS_SERVICE_DBUS,
DBUS_PATH_DBUS,
DBUS_INTERFACE_DBUS);
if (!priv->proxy) {
g_set_error (&error, NM_SYSCONFIG_SETTINGS_ERROR,
NM_SYSCONFIG_SETTINGS_ERROR_GENERAL,
"%s", "Could not connect to D-Bus to authorize GetSecrets request");
goto out;
}
info = get_unix_user_info_new (self, setting_name, context);
if (!info) {
g_set_error (&error, NM_SYSCONFIG_SETTINGS_ERROR,
NM_SYSCONFIG_SETTINGS_ERROR_GENERAL,
"%s", "Not enough memory to authorize GetSecrets request");
goto out;
}
dbus_g_proxy_begin_call_with_timeout (priv->proxy, "GetConnectionUnixUser",
get_unix_user_cb,
info,
get_unix_user_info_free,
5000,
G_TYPE_STRING, sender,
G_TYPE_INVALID);
out:
if (error) {
dbus_g_method_return_error (context, error);
g_error_free (error);
}
}
/* GObject */ /* GObject */
static void static void
@@ -67,25 +296,35 @@ nm_sysconfig_connection_init (NMSysconfigConnection *self)
priv->dbus_connection = dbus_g_bus_get (DBUS_BUS_SYSTEM, &err); priv->dbus_connection = dbus_g_bus_get (DBUS_BUS_SYSTEM, &err);
if (err) { if (err) {
g_warning ("Could not get DBus connection: %s", err->message); g_warning ("%s: error getting D-Bus connection: %s",
__func__,
(err && err->message) ? err->message : "(unknown)");
g_error_free (err); g_error_free (err);
} }
priv->pol_ctx = create_polkit_context (); priv->pol_ctx = create_polkit_context (&err);
if (!priv->pol_ctx) {
g_warning ("%s: error creating PolicyKit context: %s",
__func__,
(err && err->message) ? err->message : "(unknown)");
}
} }
static void static void
finalize (GObject *object) dispose (GObject *object)
{ {
NMSysconfigConnectionPrivate *priv = NM_SYSCONFIG_CONNECTION_GET_PRIVATE (object); NMSysconfigConnectionPrivate *priv = NM_SYSCONFIG_CONNECTION_GET_PRIVATE (object);
if (priv->proxy)
g_object_unref (priv->proxy);
if (priv->pol_ctx) if (priv->pol_ctx)
polkit_context_unref (priv->pol_ctx); polkit_context_unref (priv->pol_ctx);
if (priv->dbus_connection) if (priv->dbus_connection)
dbus_g_connection_unref (priv->dbus_connection); dbus_g_connection_unref (priv->dbus_connection);
G_OBJECT_CLASS (nm_sysconfig_connection_parent_class)->finalize (object); G_OBJECT_CLASS (nm_sysconfig_connection_parent_class)->dispose (object);
} }
static void static void
@@ -97,8 +336,9 @@ nm_sysconfig_connection_class_init (NMSysconfigConnectionClass *sysconfig_connec
g_type_class_add_private (sysconfig_connection_class, sizeof (NMSysconfigConnectionPrivate)); g_type_class_add_private (sysconfig_connection_class, sizeof (NMSysconfigConnectionPrivate));
/* Virtual methods */ /* Virtual methods */
object_class->finalize = finalize; object_class->dispose = dispose;
connection_class->update = update; connection_class->update = update;
connection_class->do_delete = do_delete; connection_class->do_delete = do_delete;
connection_class->service_get_secrets = service_get_secrets;
} }

View File

@@ -12,9 +12,9 @@
<allow send_destination="org.freedesktop.NetworkManagerSystemSettings"/> <allow send_destination="org.freedesktop.NetworkManagerSystemSettings"/>
<!-- Only root can get secrets --> <!-- The org.freedesktop.NetworkManagerSettings.Connection.Secrets
<deny send_destination="org.freedesktop.NetworkManagerSystemSettings" interface is secured via PolicyKit.
send_interface="org.freedesktop.NetworkManagerSettings.Connection.Secrets"/> -->
</policy> </policy>
<limit name="max_replies_per_connection">512</limit> <limit name="max_replies_per_connection">512</limit>