diff --git a/HOWTO.md b/HOWTO.md index ce035bc5f..c8d7d205c 100644 --- a/HOWTO.md +++ b/HOWTO.md @@ -967,9 +967,18 @@ still required to be set by the user (`DI_SIGN_KEY_PATH`, `DI_HMAC_KEY_PATH`). If other devices do not have their `per-device serviceinfo` file under `device_specific_store_driver` they will get onboarded with settings from the main file, which is `serviceinfo-api-server.yml`. + Where: + - `initial_user`: the initial user from the per-device serviceinfo configuration will be chosen over the base serviceinfo configuration, if exists. + - `files`: add additional [files](#per-device-files) to be written next to the base files. + - `commands`: add additional commands to be executed after the base commands. + - `diskencryption_clevis`: unhandled + - `additional_serviceinfo`: unhandled + - `after_onboarding_reboot`: unhandled + 1. Initialize the device as mentioned in [How to generate an Ownership Voucher and Credential for a Device](#how-to-generate-an-ownership-voucher-ov-and-credential-for-a-device-device-initialization). 2. Dump the `device-credentials` + ```bash fdo-owner-tool dump-device-credential /path/to/device-credentials ``` @@ -979,3 +988,7 @@ still required to be set by the user (`DI_SIGN_KEY_PATH`, `DI_HMAC_KEY_PATH`). 4. You can refer to [per_device_serviceinfo.yml](https://github.com/fedora-iot/fido-device-onboard-rs/blob/main/examples/config/device_specific_serviceinfo.yml) as an example. 5. Follow the onboarding procedure and this particular device will get the serviceinfo settings as mentioned in the above file. + +#### Per device files + +Files will be written to the `path` location. Both the files from the base and per-device serviceinfo configuration will be written. If the same `path` is defined multiple times in a configuration, the latest `path` definition will be written. If the same `path` is defined in the base as well in the per-device serviceinfo configuration, the per device serviceinfo configuration will have precedence over the base serviceinfo configuration. diff --git a/examples/config/device_specific_serviceinfo.yml b/examples/config/device_specific_serviceinfo.yml index d6de355d5..e1d6d4501 100755 --- a/examples/config/device_specific_serviceinfo.yml +++ b/examples/config/device_specific_serviceinfo.yml @@ -2,8 +2,25 @@ initial_user: username: username_per_device sshkeys: - "testkeyperdevice" -files: null -commands: null +files: +- path: /var/lib/fdo/service-info-api/files/hosts + permissions: 644 + source_path: /server/local/etc/hosts +commands: +- command: ls + args: + - /etc/hosts + return_stdout: true + return_stderr: true +- command: ls + args: + - /etc/doesnotexist/whatever.foo + may_fail: true + return_stdout: true + return_stderr: true +- command: touch + args: + - /etc/command-testfile diskencryption_clevis: null additional_serviceinfo: null after_onboarding_reboot: false diff --git a/serviceinfo-api-server/src/main.rs b/serviceinfo-api-server/src/main.rs index 450ee1861..7779c4c85 100644 --- a/serviceinfo-api-server/src/main.rs +++ b/serviceinfo-api-server/src/main.rs @@ -261,64 +261,66 @@ async fn serviceinfo_handler( .contains(&FedoraIotServiceInfoModule::BinaryFile.into()) { if let Some(files) = &user_data.service_info_configuration.settings.files { - for file in files { - reply.add_extra(FedoraIotServiceInfoModule::BinaryFile, "name", &file.path); - reply.add_extra( - FedoraIotServiceInfoModule::BinaryFile, - "length", - &file.contents_len, - ); - if let Some(parsed_permissions) = &file.parsed_permissions { - reply.add_extra( - FedoraIotServiceInfoModule::BinaryFile, - "mode", - &parsed_permissions, - ); - } - reply.add_extra( - FedoraIotServiceInfoModule::BinaryFile, - "data001|hex", - &file.contents_hex, - ); - reply.add_extra( - FedoraIotServiceInfoModule::BinaryFile, - "sha-384|hex", - &file.hash_hex, - ); - } + add_binary_files_to_reply(files, &mut reply); + + log::debug!("Added base configuration binary files"); } + + // precedence is given to 'per_device' settings over base serviceinfo_api_server.yml config + match settings_per_device(&query_info.device_guid.to_string().replace('\"', "")) { + Ok(config) => { + let per_device_settings = config; + match ServiceInfoConfiguration::from_settings(per_device_settings){ + Ok(per_device_service_info_configuration) => { + if let Some(files) = &per_device_service_info_configuration.settings.files { + add_binary_files_to_reply(files, &mut reply); + + log::debug!("Added per-device configuration binary files"); + } + } + Err(e) => { + log::error!("{}", e); + } + }; + } + Err(_) => { + log::info!("No per-device configuration file found"); + } + }; } if query_info .modules .contains(&FedoraIotServiceInfoModule::Command.into()) { + // Add base configuration commands if let Some(commands) = &user_data.service_info_configuration.settings.commands { - for command in commands { - reply.add_extra( - FedoraIotServiceInfoModule::Command, - "command", - &command.command, - ); - reply.add_extra(FedoraIotServiceInfoModule::Command, "args", &command.args); - reply.add_extra( - FedoraIotServiceInfoModule::Command, - "may_fail", - &command.may_fail, - ); - reply.add_extra( - FedoraIotServiceInfoModule::Command, - "return_stdout", - &command.return_stdout, - ); - reply.add_extra( - FedoraIotServiceInfoModule::Command, - "return_stderr", - &command.return_stderr, - ); - reply.add_extra(FedoraIotServiceInfoModule::Command, "execute", &true); - } + add_commands_to_reply(commands, &mut reply); + + log::debug!("Added base configuration commands"); } + + // Add device specific commands to reply + match settings_per_device(&query_info.device_guid.to_string().replace('\"', "")) { + Ok(config) => { + let per_device_settings = config; + match ServiceInfoConfiguration::from_settings(per_device_settings){ + Ok(per_device_service_info_configuration) => { + if let Some(commands) = &per_device_service_info_configuration.settings.commands { + add_commands_to_reply(commands, &mut reply); + + log::debug!("Added per-device configuration commands"); + } + } + Err(e) => { + log::error!("{}", e); + } + }; + } + Err(_) => { + log::info!("No per-device configuration file found"); + } + }; } if query_info @@ -391,6 +393,61 @@ async fn serviceinfo_handler( Ok(warp::reply::json(&reply.reply)) } +fn add_binary_files_to_reply(files: &Vec, reply: &mut ServiceInfoApiReplyBuilder) { + for file in files { + reply.add_extra(FedoraIotServiceInfoModule::BinaryFile, "name", &file.path); + reply.add_extra( + FedoraIotServiceInfoModule::BinaryFile, + "length", + &file.contents_len, + ); + if let Some(parsed_permissions) = &file.parsed_permissions { + reply.add_extra( + FedoraIotServiceInfoModule::BinaryFile, + "mode", + &parsed_permissions, + ); + } + reply.add_extra( + FedoraIotServiceInfoModule::BinaryFile, + "data001|hex", + &file.contents_hex, + ); + reply.add_extra( + FedoraIotServiceInfoModule::BinaryFile, + "sha-384|hex", + &file.hash_hex, + ); + } +} + +fn add_commands_to_reply(commands: &Vec, reply: &mut ServiceInfoApiReplyBuilder) { + for command in commands { + reply.add_extra( + FedoraIotServiceInfoModule::Command, + "command", + &command.command, + ); + reply.add_extra(FedoraIotServiceInfoModule::Command, "args", &command.args); + reply.add_extra( + FedoraIotServiceInfoModule::Command, + "may_fail", + &command.may_fail, + ); + reply.add_extra( + FedoraIotServiceInfoModule::Command, + "return_stdout", + &command.return_stdout, + ); + reply.add_extra( + FedoraIotServiceInfoModule::Command, + "return_stderr", + &command.return_stderr, + ); + reply.add_extra(FedoraIotServiceInfoModule::Command, "execute", &true); + } +} + fn deserialize_from_str<'de, D>(deserializer: D) -> Result where D: serde::de::Deserializer<'de>, diff --git a/util/src/servers/mod.rs b/util/src/servers/mod.rs index 76a2ef0d9..6dc9b630b 100644 --- a/util/src/servers/mod.rs +++ b/util/src/servers/mod.rs @@ -90,7 +90,7 @@ pub fn settings_per_device(guid: &str) -> Result { log::debug!("Loaded device specific config from {path_per_device_store}"); let per_device_settings = config.try_deserialize::()?; log::debug!( - "device specific serviceinfosettings: initial_user: {:#?} username: {:#?} sshkeys {:#?}", + "device specific serviceinfosettings: initial_user: {:#?} username: {:#?} sshkeys {:#?} files {:#?} commands {:#?}", per_device_settings.initial_user, per_device_settings .initial_user @@ -99,7 +99,9 @@ pub fn settings_per_device(guid: &str) -> Result { per_device_settings .initial_user .as_ref() - .map(|user| &user.sshkeys) + .map(|user| &user.sshkeys), + per_device_settings.files, + per_device_settings.commands ); Ok(per_device_settings) }