Commit 8dd4c1c4 authored by Richard Hughes's avatar Richard Hughes
Browse files

Allow restricting firmware updates for enterprise use

parent 640faed9
......@@ -72,6 +72,28 @@ To clear the local history of updates:
Only updates that were distributed from the LVFS will be reported to the LVFS.
Enterprise Use
--------------
The flow of updates can be controlled in the enterprise using the
"approved updates" feature. This allows the domain administrator to filter
the possible updates from a central server (e.g. the LVFS, or a mirror)
to only firmware that have been tested specifically in your organisation.
The list of approved updates can be enabled by adding `ApprovalRequired=true`
to the remote configuration file, e.g. `lvfs.conf`. Once enabled, the
list of approved updates can be set in `daemon.conf` using a comma delimited list.
For example:
ApprovedFirmware=foo,bar
Where `foo,bar` refers to the container checksums that would correspond
to two updates in the metadata file.
Additionally, the list of approved firmware can be supplemented using
`fwupdmgr set-approved-firmware baz` or using the D-Bus interface.
Other frontends
-------------------
......
......@@ -6,6 +6,7 @@ _fwupdmgr_cmd_list=(
'disable-remote'
'downgrade'
'enable-remote'
'get-approved-firmware'
'get-details'
'get-devices'
'get-history'
......@@ -19,6 +20,7 @@ _fwupdmgr_cmd_list=(
'modify-remote'
'refresh'
'report-history'
'set-approved-firmware'
'unlock'
'update'
'verify'
......
......@@ -5,3 +5,4 @@ Enabled=true
Title=Core
Keyring=none
MetadataURI=file://@datadir@/fwupd/remotes.d/fwupd/metadata.xml
ApprovalRequired=false
......@@ -9,3 +9,4 @@ ReportURI=https://fwupd.org/lvfs/firmware/report
Username=
Password=
OrderBefore=lvfs,fwupd
ApprovalRequired=false
......@@ -7,3 +7,4 @@ Keyring=gpg
MetadataURI=https://cdn.fwupd.org/downloads/firmware.xml.gz
ReportURI=https://fwupd.org/lvfs/firmware/report
OrderBefore=fwupd
ApprovalRequired=false
......@@ -5,3 +5,4 @@ Enabled=false
Title=Vendor (Automatic)
Keyring=none
MetadataURI=file://@datadir@/fwupd/remotes.d/vendor/firmware
ApprovalRequired=false
......@@ -5,3 +5,4 @@ Enabled=false
Title=Vendor
Keyring=none
MetadataURI=file://@datadir@/fwupd/remotes.d/vendor/vendor.xml.gz
ApprovalRequired=false
[fwupd]
ArchiveSizeMax=5
ApprovedFirmware=deadbeef
[fwupd Remote]
Enabled=true
MetadataURI=file:///tmp/fwupd-self-test/testing.xml
ApprovalRequired=true
......@@ -1364,6 +1364,98 @@ fwupd_client_get_remotes (FwupdClient *client, GCancellable *cancellable, GError
return fwupd_client_parse_remotes_from_data (val);
}
/**
* fwupd_client_get_approved_firmware:
* @client: A #FwupdClient
* @cancellable: the #GCancellable, or %NULL
* @error: the #GError, or %NULL
*
* Gets the list of approved firmware.
*
* Returns: (transfer full): list of remotes, or %NULL
*
* Since: 1.2.6
**/
gchar **
fwupd_client_get_approved_firmware (FwupdClient *client,
GCancellable *cancellable,
GError **error)
{
FwupdClientPrivate *priv = GET_PRIVATE (client);
g_autoptr(GVariant) val = NULL;
gchar **retval = NULL;
g_return_val_if_fail (FWUPD_IS_CLIENT (client), NULL);
g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
/* connect */
if (!fwupd_client_connect (client, cancellable, error))
return NULL;
/* call into daemon */
val = g_dbus_proxy_call_sync (priv->proxy,
"GetApprovedFirmware",
NULL,
G_DBUS_CALL_FLAGS_NONE,
-1,
cancellable,
error);
if (val == NULL) {
if (error != NULL)
fwupd_client_fixup_dbus_error (*error);
return NULL;
}
g_variant_get (val, "(^as)", &retval);
return retval;
}
/**
* fwupd_client_set_approved_firmware:
* @client: A #FwupdClient
* @checksums: Array of checksums
* @cancellable: the #GCancellable, or %NULL
* @error: the #GError, or %NULL
*
* Sets the list of approved firmware.
*
* Returns: %TRUE for success
*
* Since: 1.2.6
**/
gboolean
fwupd_client_set_approved_firmware (FwupdClient *client,
gchar **checksums,
GCancellable *cancellable,
GError **error)
{
FwupdClientPrivate *priv = GET_PRIVATE (client);
g_autoptr(GVariant) val = NULL;
g_return_val_if_fail (FWUPD_IS_CLIENT (client), FALSE);
g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
/* connect */
if (!fwupd_client_connect (client, cancellable, error))
return FALSE;
/* call into daemon */
val = g_dbus_proxy_call_sync (priv->proxy,
"SetApprovedFirmware",
g_variant_new ("(^as)", checksums),
G_DBUS_CALL_FLAGS_NONE,
-1,
cancellable,
error);
if (val == NULL) {
if (error != NULL)
fwupd_client_fixup_dbus_error (*error);
return FALSE;
}
return TRUE;
}
/**
* fwupd_client_modify_remote:
* @client: A #FwupdClient
......
......@@ -131,4 +131,12 @@ FwupdRemote *fwupd_client_get_remote_by_id (FwupdClient *client,
GCancellable *cancellable,
GError **error);
gchar **fwupd_client_get_approved_firmware (FwupdClient *client,
GCancellable *cancellable,
GError **error);
gboolean fwupd_client_set_approved_firmware (FwupdClient *client,
gchar **checksums,
GCancellable *cancellable,
GError **error);
G_END_DECLS
......@@ -391,6 +391,8 @@ fwupd_release_flag_to_string (FwupdReleaseFlags release_flag)
return "is-downgrade";
if (release_flag == FWUPD_RELEASE_FLAG_BLOCKED_VERSION)
return "blocked-version";
if (release_flag == FWUPD_RELEASE_FLAG_BLOCKED_APPROVAL)
return "blocked-approval";
return NULL;
}
......@@ -417,5 +419,7 @@ fwupd_release_flag_from_string (const gchar *release_flag)
return FWUPD_RELEASE_FLAG_IS_DOWNGRADE;
if (g_strcmp0 (release_flag, "blocked-version") == 0)
return FWUPD_RELEASE_FLAG_BLOCKED_VERSION;
if (g_strcmp0 (release_flag, "blocked-approval") == 0)
return FWUPD_RELEASE_FLAG_BLOCKED_APPROVAL;
return FWUPD_RELEASE_FLAG_NONE;
}
......@@ -124,6 +124,7 @@ typedef guint64 FwupdDeviceFlags;
* @FWUPD_RELEASE_FLAG_IS_UPGRADE: Is newer than the device version
* @FWUPD_RELEASE_FLAG_IS_DOWNGRADE: Is older than the device version
* @FWUPD_RELEASE_FLAG_BLOCKED_VERSION: Blocked as below device version-lowest
* @FWUPD_RELEASE_FLAG_BLOCKED_APPROVAL: Blocked as release not approved
*
* The release flags.
**/
......@@ -133,6 +134,7 @@ typedef guint64 FwupdDeviceFlags;
#define FWUPD_RELEASE_FLAG_IS_UPGRADE (1u << 2) /* Since: 1.2.6 */
#define FWUPD_RELEASE_FLAG_IS_DOWNGRADE (1u << 3) /* Since: 1.2.6 */
#define FWUPD_RELEASE_FLAG_BLOCKED_VERSION (1u << 4) /* Since: 1.2.6 */
#define FWUPD_RELEASE_FLAG_BLOCKED_APPROVAL (1u << 5) /* Since: 1.2.6 */
#define FWUPD_RELEASE_FLAG_UNKNOWN G_MAXUINT64 /* Since: 1.2.6 */
typedef guint64 FwupdReleaseFlags;
......
......@@ -42,6 +42,7 @@ typedef struct {
gchar *filename_cache_sig;
gchar *filename_source;
gboolean enabled;
gboolean approval_required;
gint priority;
guint64 mtime;
gchar **order_after;
......@@ -52,6 +53,7 @@ enum {
PROP_0,
PROP_ID,
PROP_ENABLED,
PROP_APPROVAL_REQUIRED,
PROP_LAST
};
......@@ -410,6 +412,7 @@ fwupd_remote_load_from_filename (FwupdRemote *self,
/* extract data */
priv->enabled = g_key_file_get_boolean (kf, group, "Enabled", NULL);
priv->approval_required = g_key_file_get_boolean (kf, group, "ApprovalRequired", NULL);
priv->title = g_key_file_get_string (kf, group, "Title", NULL);
/* reporting is optional */
......@@ -910,6 +913,25 @@ fwupd_remote_get_enabled (FwupdRemote *self)
return priv->enabled;
}
/**
* fwupd_remote_get_approval_required:
* @self: A #FwupdRemote
*
* Gets if firmware from the remote should be checked against the list
* of a approved checksums.
*
* Returns: a #TRUE if the remote is restricted
*
* Since: 1.2.6
**/
gboolean
fwupd_remote_get_approval_required (FwupdRemote *self)
{
FwupdRemotePrivate *priv = GET_PRIVATE (self);
g_return_val_if_fail (FWUPD_IS_REMOTE (self), FALSE);
return priv->approval_required;
}
/**
* fwupd_remote_get_id:
* @self: A #FwupdRemote
......@@ -969,6 +991,8 @@ fwupd_remote_set_from_variant_iter (FwupdRemote *self, GVariantIter *iter)
fwupd_remote_set_checksum (self, g_variant_get_string (value, NULL));
} else if (g_strcmp0 (key, "Enabled") == 0) {
priv->enabled = g_variant_get_boolean (value);
} else if (g_strcmp0 (key, "ApprovalRequired") == 0) {
priv->approval_required = g_variant_get_boolean (value);
} else if (g_strcmp0 (key, "Priority") == 0) {
priv->priority = g_variant_get_int32 (value);
} else if (g_strcmp0 (key, "ModificationTime") == 0) {
......@@ -1061,6 +1085,8 @@ fwupd_remote_to_variant (FwupdRemote *self)
}
g_variant_builder_add (&builder, "{sv}", "Enabled",
g_variant_new_boolean (priv->enabled));
g_variant_builder_add (&builder, "{sv}", "ApprovalRequired",
g_variant_new_boolean (priv->approval_required));
return g_variant_new ("a{sv}", &builder);
}
......@@ -1075,6 +1101,9 @@ fwupd_remote_get_property (GObject *obj, guint prop_id,
case PROP_ENABLED:
g_value_set_boolean (value, priv->enabled);
break;
case PROP_APPROVAL_REQUIRED:
g_value_set_boolean (value, priv->approval_required);
break;
case PROP_ID:
g_value_set_string (value, priv->id);
break;
......@@ -1095,6 +1124,9 @@ fwupd_remote_set_property (GObject *obj, guint prop_id,
case PROP_ENABLED:
priv->enabled = g_value_get_boolean (value);
break;
case PROP_APPROVAL_REQUIRED:
priv->approval_required = g_value_get_boolean (value);
break;
case PROP_ID:
fwupd_remote_set_id (self, g_value_get_string (value));
break;
......@@ -1134,6 +1166,18 @@ fwupd_remote_class_init (FwupdRemoteClass *klass)
pspec = g_param_spec_boolean ("enabled", NULL, NULL,
FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_NAME);
g_object_class_install_property (object_class, PROP_ENABLED, pspec);
/**
* FwupdRemote:approval-required:
*
* If firmware from the remote should be checked against the system
* list of approved firmware.
*
* Since: 1.2.6
*/
pspec = g_param_spec_boolean ("approval-required", NULL, NULL,
FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_NAME);
g_object_class_install_property (object_class, PROP_APPROVAL_REQUIRED, pspec);
}
static void
......
......@@ -61,6 +61,7 @@ const gchar *fwupd_remote_get_report_uri (FwupdRemote *self);
const gchar *fwupd_remote_get_metadata_uri (FwupdRemote *self);
const gchar *fwupd_remote_get_metadata_uri_sig (FwupdRemote *self);
gboolean fwupd_remote_get_enabled (FwupdRemote *self);
gboolean fwupd_remote_get_approval_required (FwupdRemote *self);
gint fwupd_remote_get_priority (FwupdRemote *self);
guint64 fwupd_remote_get_age (FwupdRemote *self);
FwupdRemoteKind fwupd_remote_get_kind (FwupdRemote *self);
......
......@@ -324,6 +324,8 @@ LIBFWUPD_1.2.5 {
LIBFWUPD_1.2.6 {
global:
fwupd_client_activate;
fwupd_client_get_approved_firmware;
fwupd_client_set_approved_firmware;
fwupd_device_to_json;
fwupd_release_add_flag;
fwupd_release_flag_from_string;
......@@ -333,5 +335,6 @@ LIBFWUPD_1.2.6 {
fwupd_release_remove_flag;
fwupd_release_set_flags;
fwupd_release_to_json;
fwupd_remote_get_approval_required;
local: *;
} LIBFWUPD_1.2.5;
......@@ -123,4 +123,15 @@
</defaults>
</action>
<action id="org.freedesktop.fwupd.set-approved-firmware">
<description>Sets the list of approved firmware</description>
<!-- TRANSLATORS: this is the PolicyKit modal dialog -->
<message>Authentication is required to set the list of approved firmware</message>
<defaults>
<allow_any>auth_admin</allow_any>
<allow_inactive>no</allow_inactive>
<allow_active>auth_admin_keep</allow_active>
</defaults>
</action>
</policyconfig>
......@@ -30,6 +30,7 @@ struct _FuConfig
GPtrArray *monitors;
GPtrArray *blacklist_devices;
GPtrArray *blacklist_plugins;
GPtrArray *approved_firmware;
guint64 archive_size_max;
guint idle_timeout;
XbSilo *silo;
......@@ -357,6 +358,7 @@ fu_config_load_from_file (FuConfig *self, const gchar *config_file,
GFileMonitor *monitor;
guint64 archive_size_max;
guint idle_timeout;
g_auto(GStrv) approved_firmware = NULL;
g_auto(GStrv) devices = NULL;
g_auto(GStrv) plugins = NULL;
g_autoptr(GFile) file = NULL;
......@@ -364,6 +366,7 @@ fu_config_load_from_file (FuConfig *self, const gchar *config_file,
/* ensure empty in case we're called from a monitor change */
g_ptr_array_set_size (self->blacklist_devices, 0);
g_ptr_array_set_size (self->blacklist_plugins, 0);
g_ptr_array_set_size (self->approved_firmware, 0);
g_ptr_array_set_size (self->monitors, 0);
g_ptr_array_set_size (self->remotes, 0);
......@@ -407,6 +410,19 @@ fu_config_load_from_file (FuConfig *self, const gchar *config_file,
}
}
/* get approved firmware */
approved_firmware = g_key_file_get_string_list (self->keyfile,
"fwupd",
"ApprovedFirmware",
NULL, /* length */
NULL);
if (approved_firmware != NULL) {
for (guint i = 0; approved_firmware[i] != NULL; i++) {
g_ptr_array_add (self->approved_firmware,
g_strdup (approved_firmware[i]));
}
}
/* get maximum archive size, defaulting to something sane */
archive_size_max = g_key_file_get_uint64 (self->keyfile,
"fwupd",
......@@ -553,6 +569,13 @@ fu_config_get_blacklist_plugins (FuConfig *self)
return self->blacklist_plugins;
}
GPtrArray *
fu_config_get_approved_firmware (FuConfig *self)
{
g_return_val_if_fail (FU_IS_CONFIG (self), NULL);
return self->approved_firmware;
}
static void
fu_config_class_init (FuConfigClass *klass)
{
......@@ -567,6 +590,7 @@ fu_config_init (FuConfig *self)
self->keyfile = g_key_file_new ();
self->blacklist_devices = g_ptr_array_new_with_free_func (g_free);
self->blacklist_plugins = g_ptr_array_new_with_free_func (g_free);
self->approved_firmware = g_ptr_array_new_with_free_func (g_free);
self->remotes = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
self->monitors = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
}
......@@ -583,6 +607,7 @@ fu_config_finalize (GObject *obj)
g_key_file_unref (self->keyfile);
g_ptr_array_unref (self->blacklist_devices);
g_ptr_array_unref (self->blacklist_plugins);
g_ptr_array_unref (self->approved_firmware);
g_ptr_array_unref (self->remotes);
g_ptr_array_unref (self->monitors);
......
......@@ -23,6 +23,7 @@ guint64 fu_config_get_archive_size_max (FuConfig *self);
guint fu_config_get_idle_timeout (FuConfig *self);
GPtrArray *fu_config_get_blacklist_devices (FuConfig *self);
GPtrArray *fu_config_get_blacklist_plugins (FuConfig *self);
GPtrArray *fu_config_get_approved_firmware (FuConfig *self);
GPtrArray *fu_config_get_remotes (FuConfig *self);
FwupdRemote *fu_config_get_remote_by_id (FuConfig *self,
const gchar *remote_id);
......
......@@ -71,6 +71,7 @@ struct _FuEngine
FuQuirks *quirks;
GHashTable *runtime_versions;
GHashTable *compile_versions;
GHashTable *approved_firmware;
gboolean loaded;
};
......@@ -2697,6 +2698,19 @@ fu_engine_sort_releases_cb (gconstpointer a, gconstpointer b)
fwupd_release_get_version (rel_a));
}
static gboolean
fu_engine_check_release_is_approved (FuEngine *self, FwupdRelease *rel)
{
GPtrArray *csums = fwupd_release_get_checksums (rel);
for (guint i = 0; i < csums->len; i++) {
const gchar *csum = g_ptr_array_index (csums, i);
g_debug ("checking %s against approved list", csum);
if (g_hash_table_lookup (self->approved_firmware, csum) != NULL)
return TRUE;
}
return FALSE;
}
static gboolean
fu_engine_add_releases_for_device_component (FuEngine *self,
FuDevice *device,
......@@ -2726,6 +2740,7 @@ fu_engine_add_releases_for_device_component (FuEngine *self,
}
for (guint i = 0; i < releases_tmp->len; i++) {
XbNode *release = g_ptr_array_index (releases_tmp, i);
const gchar *remote_id;
const gchar *update_message;
gint vercmp;
GPtrArray *checksums;
......@@ -2760,6 +2775,17 @@ fu_engine_add_releases_for_device_component (FuEngine *self,
fwupd_release_add_flag (rel, FWUPD_RELEASE_FLAG_BLOCKED_VERSION);
}
/* check if remote is whitelisting firmware */
remote_id = fwupd_release_get_remote_id (rel);
if (remote_id != NULL) {
FwupdRemote *remote = fu_engine_get_remote_by_id (self, remote_id, NULL);
if (remote != NULL &&
fwupd_remote_get_approval_required (remote) &&
!fu_engine_check_release_is_approved (self, rel)) {
fwupd_release_add_flag (rel, FWUPD_RELEASE_FLAG_BLOCKED_APPROVAL);
}
}
/* add update message if exists but device doesn't already have one */
update_message = fwupd_release_get_update_message (rel);
if (fwupd_device_get_update_message (FWUPD_DEVICE (device)) == NULL &&
......@@ -2995,6 +3021,24 @@ fu_engine_get_downgrades (FuEngine *self, const gchar *device_id, GError **error
return g_steal_pointer (&releases);
}
GPtrArray *
fu_engine_get_approved_firmware (FuEngine *self)
{
GPtrArray *checksums = g_ptr_array_new_with_free_func (g_free);
g_autoptr(GList) keys = g_hash_table_get_keys (self->approved_firmware);
for (GList *l = keys; l != NULL; l = l->next) {
const gchar *csum = l->data;
g_ptr_array_add (checksums, g_strdup (csum));
}
return checksums;
}
void
fu_engine_add_approved_firmware (FuEngine *self, const gchar *checksum)
{
g_hash_table_add (self->approved_firmware, g_strdup (checksum));
}
/**
* fu_engine_get_upgrades:
* @self: A #FuEngine
......@@ -3060,6 +3104,17 @@ fu_engine_get_upgrades (FuEngine *self, const gchar *device_id, GError **error)
fu_device_get_version (device));
continue;
}
/* not approved */
if (fwupd_release_has_flag (rel_tmp, FWUPD_RELEASE_FLAG_BLOCKED_APPROVAL)) {
g_string_append_printf (error_str, "%s=not-approved, ",
fwupd_release_get_version (rel_tmp));
g_debug ("ignoring %s as not approved as required by %s",
fwupd_release_get_version (rel_tmp),
fwupd_release_get_remote_id (rel_tmp));
continue;
}
g_ptr_array_add (releases, g_object_ref (rel_tmp));
}
if (error_str->len > 2)
......@@ -4104,6 +4159,8 @@ fu_engine_udev_uevent_cb (GUdevClient *gudev_client,
gboolean
fu_engine_load (FuEngine *self, GError **error)
{
g_autoptr(GPtrArray) checksums = NULL;
g_return_val_if_fail (FU_IS_ENGINE (self), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
......@@ -4117,6 +4174,22 @@ fu_engine_load (FuEngine *self, GError **error)
return FALSE;
}
/* get hardcoded approved firmware */
checksums = fu_config_get_approved_firmware (self->config);
for (guint i = 0; i < checksums->len; i++) {
const gchar *csum = g_ptr_array_index (checksums, i);
fu_engine_add_approved_firmware (self, csum);
}
/* get extra firmware saved to the database */
checksums = fu_history_get_approved_firmware (self->history, error);
if (checksums == NULL)
return FALSE;
for (guint i = 0; i < checksums->len; i++) {
const gchar *csum = g_ptr_array_index (checksums, i);
fu_engine_add_approved_firmware (self, csum);
}
/* set up idle exit */
if ((self->app_flags & FU_APP_FLAGS_NO_IDLE_SOURCES) == 0)
fu_idle_set_timeout (self->idle, fu_config_get_idle_timeout (self->config));
......@@ -4276,6 +4349,7 @@ fu_engine_init (FuEngine *self)
self->udev_subsystems = g_ptr_array_new_with_free_func (g_free);
self->runtime_versions = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
self->compile_versions = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
self->approved_firmware = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
g_signal_connect (self->idle, "notify::status",
G_CALLBACK (fu_engine_idle_status_notify_cb), self);
......@@ -4328,6 +4402,7 @@ fu_engine_finalize (GObject *obj)
g_ptr_array_unref (self->udev_subsystems);
g_hash_table_unref (self->runtime_versions);
g_hash_table_unref (self->compile_versions);
g_hash_table_unref (self->approved_firmware);
g_object_unref (self->plugin_list);
G_OBJECT_CLASS (fu_engine_parent_class)->finalize (obj);
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment