diff --git a/HOWTO.md b/HOWTO.md index 426643074..882887d02 100644 --- a/HOWTO.md +++ b/HOWTO.md @@ -652,18 +652,17 @@ Identification method to the device. 2. If the Manufacturing server is specifically configured with a `mfg_string_type` set to `MACAddress` in its `diun` configuration section it will ask the device to identify itself with a MAC Address, in order to do so - the device must set `DI_MFG_STRING_TYPE_MAC_IFACE` [optional] to a valid - interface. + the user has an option to set `DI_MFG_STRING_TYPE_MAC_IFACE` [optional] to + a valid interface. + Valid interfaces are those which do not result in a `00:00:00:00:00:00` MAC Address. Please note that this `DI_MFG_STRING_TYPE_MAC_IFACE` environment variable is *optional* and will only be read if the server requests a `MACAddress` - identification mode, but if the Manufacturing server is configured as - described above the Device Initialize protocol will result in an early error - if this environment variable is not set. - - In such case, the value `DI_MFG_STRING_TYPE_MAC_IFACE` must be set to any - interface name of the device that does not result in `00:00:00:00:00:00` as - the specified MAC Address. + identification mode. If the Manufacturing server is configured as + described and the environment variable `DI_MFG_STRING_TYPE_MAC_IFACE` is not set + then the default active network interface will be used. This is obtained from + kernel's routing table file (`/proc/net/route`). + 3. Run the client: `fdo-manufacturing-client`. @@ -675,10 +674,14 @@ identification with the Manufacturing server. 1. `DI_MFG_STRING_TYPE`: [optional] selects the Device Identification string type; by default `serial_number`, other possible value is `mac_address`. - If `mac_address` is selected as `DI_MFG_STRING_TYPE` then the user *must* - specify a valid interface to read the MAC Address from with - `DI_MFG_STRING_TYPE_MAC_IFACE`. Valid interfaces are those which do not - result in a `00:00:00:00:00:00` MAC Address. + If `mac_address` is selected as `DI_MFG_STRING_TYPE` then the user has an option + to specify a valid interface to read the MAC Address from with + `DI_MFG_STRING_TYPE_MAC_IFACE` [optional] env variable. + Valid interfaces are those which do not result in a `00:00:00:00:00:00` MAC Address. + If the user has not specified which network interface to be used then the default + active network interface will be used. This is obtained from kernel's routing table + file (`/proc/net/route`). + 2. `DI_KEY_STORAGE_TYPE`: selects the storage type for the Device Identification keys, valid values: `filesystem` (`tpm` not yet diff --git a/integration-tests/tests/di_diun.rs b/integration-tests/tests/di_diun.rs index 1caf78fa5..11ae30010 100644 --- a/integration-tests/tests/di_diun.rs +++ b/integration-tests/tests/di_diun.rs @@ -241,3 +241,52 @@ async fn test_device_credentials_with_tpm() -> Result<()> { } Ok(()) } + +#[tokio::test] +async fn test_device_credentials_generated_with_mac_address_no_user_given_iface() -> Result<()> { + let mut ctx = TestContext::new().context("Error building test context")?; + + let mfg_server = ctx + .start_test_server( + Binary::ManufacturingServer, + |cfg| { + Ok(cfg.prepare_config_file(None, |cfg| { + cfg.insert("rendezvous_port", "1337"); + cfg.insert("diun_key_type", "FileSystem"); + cfg.insert("device_identification_format", "MACAddress"); + Ok(()) + })?) + }, + |_| Ok(()), + ) + .context("Error creating manufacturing server")?; + ctx.wait_until_servers_ready() + .await + .context("Error waiting for servers to start")?; + + // Generate device credentials with MAC address as the device identification + // and where user has not provided which network interface to be used + + let client_result = ctx + .run_client( + Binary::ManufacturingClient, + Some(&mfg_server), + |cfg| { + cfg.env("DEVICE_CREDENTIAL_FILENAME", "devicecredential.dc") + .env("DI_MFG_STRING_TYPE", "mac_address") + .env("DIUN_PUB_KEY_INSECURE", "true"); + Ok(()) + }, + Duration::from_secs(5), + ) + .context("Error running manufacturing client")?; + client_result + .expect_success() + .context("Manufacturing client failed")?; + + let dc_path = client_result.client_path().join("devicecredential.dc"); + L.l(format!("Device Credential should be in {:?}", dc_path)); + assert!(Path::new(&dc_path).exists()); + + Ok(()) +} diff --git a/manufacturing-client/src/main.rs b/manufacturing-client/src/main.rs index 45209b34b..f9277754d 100644 --- a/manufacturing-client/src/main.rs +++ b/manufacturing-client/src/main.rs @@ -1,6 +1,7 @@ use anyhow::{bail, Context, Result}; use clap::{Args, Parser, Subcommand}; use regex::Regex; +use std::io::{BufRead, BufReader}; use std::path::Path; use std::{convert::TryFrom, fs}; use std::{convert::TryInto, env, str::FromStr}; @@ -324,11 +325,26 @@ async fn main() -> Result<()> { url = args.manufacturing_server_url; mfg_string_type = args.mfg_string_type; - // ensure that we are given the iface if MACAddress is selected - if mfg_string_type == MfgStringType::MACAddress && args.iface.is_none() { - bail!("When using MACAddress as the MfgStringType --iface is required"); + if mfg_string_type == MfgStringType::MACAddress { + // user provided iface + if args.iface.is_some() { + iface = args.iface; + } else { + // If user has not selected any specific iface then default iface will be used + match get_default_network_iface() { + Ok(Some(result)) => { + iface = Some(result); + log::info!("Default network interface found: {iface:#?}"); + } + Err(error) => { + bail!("Error retrieving default network interface: {error}"); + } + Ok(None) => { + bail!("Error retrieving default network interface, unknown reason"); + } + } + } } - iface = args.iface; keyref = KeyReference::str_key(args.key_ref) .await @@ -351,15 +367,31 @@ async fn main() -> Result<()> { } else { bail!("No DIUN root key verification methods set"); } - iface = args.iface; log::debug!("Performing DIUN"); client = ServiceClient::new(ProtocolVersion::Version1_1, &url); (keyref, mfg_string_type) = perform_diun(&mut client, diun_pub_key_verification) .await .context("Error performing DIUN")?; - if mfg_string_type == MfgStringType::MACAddress && iface.is_none() { - bail!("Server has requested mac_address as mfg_string_type and there is no iface set"); + if mfg_string_type == MfgStringType::MACAddress { + // user provided iface + if args.iface.is_some() { + iface = args.iface; + } else { + // If user has not selected any specific iface then default iface will be used + match get_default_network_iface() { + Ok(Some(result)) => { + iface = Some(result); + log::info!("Default network interface found: {iface:#?}"); + } + Err(error) => { + bail!("Error retrieving default network interface: {error}"); + } + Ok(None) => { + bail!("Error retrieving default network interface, unknown reason"); + } + } + } } } } @@ -388,9 +420,21 @@ async fn main() -> Result<()> { format!("Unsupported MFG string type {env_mfg_string_type} requested") })?; if mfg_string_type == MfgStringType::MACAddress { - iface = Some(env::var("DI_MFG_STRING_TYPE_MAC_IFACE").context( - "Please provide an iface for the MAC address with DI_MFG_STRING_TYPE_MAC_IFACE", - )?); + iface = match env::var("DI_MFG_STRING_TYPE_MAC_IFACE") { + Ok(iface) => Some(iface), + Err(_) => match get_default_network_iface() { + Ok(Some(result)) => { + log::info!("Default network interface found: {result:#?}"); + Some(result) + } + Err(error) => { + bail!("Error determining default network interface: {error}"); + } + Ok(None) => { + bail!("Error determining default network interface, reason unknown"); + } + }, + }; } keyref = KeyReference::env_key() .await @@ -400,17 +444,27 @@ async fn main() -> Result<()> { // since the mfg_string_type will be determined in the manufacturing server // and it might request MACAddress as the mfg_string_type. What it cannot do // is select the iface for the client, so we must set it ahead of time. - // Thereby, we are just getting this value if provided, hiding errors. - if env::var("DI_MFG_STRING_TYPE_MAC_IFACE").is_ok() { - iface = Some(env::var("DI_MFG_STRING_TYPE_MAC_IFACE").unwrap()); + // This can be by setting DI_MFG_STRING_TYPE_MAC_IFACE env variable to required interface + // or else default active network interface will be assigned. + if let Ok(iface_var) = env::var("DI_MFG_STRING_TYPE_MAC_IFACE") { + iface = Some(iface_var); } (keyref, mfg_string_type) = perform_diun(&mut client, diun_pub_key_verification) .await .context("Error performing DIUN")?; - // once diun has been performed we can error ahead of time if the - // server has requested MACaddress but we haven't set an iface. if mfg_string_type == MfgStringType::MACAddress && iface.is_none() { - bail!("Server has requested mac_address as mfg_string_type and there is no iface (DI_MFG_STRING_TYPE_MAC_IFACE) set"); + match get_default_network_iface() { + Ok(Some(result)) => { + iface = Some(result); + log::info!("Default network interface found: {iface:#?}"); + } + Err(error) => { + bail!("Error retrieving default network interface: {error}"); + } + Ok(None) => { + bail!("Error retrieving default network interface, unknown reason"); + } + } } } } @@ -944,3 +998,25 @@ impl KeyReference { } } } + +const IPV4_DEFAULT: &str = "00000000"; + +fn get_default_network_iface() -> Result, std::io::Error> { + // Check IPv4 addresses from /proc/net/route + let file = std::fs::File::open("/proc/net/route")?; + let reader = BufReader::new(file); + + for line in reader.lines().skip(1) { + let line = line?; + let fields: Vec<_> = line.split_whitespace().collect(); + if fields.is_empty() { + continue; + } + if fields[1] == IPV4_DEFAULT && fields[0] != "lo" { + let iface = fields[0].to_string(); + log::info!("Default network interface is ipv4 based {iface}"); + return Ok(Some(iface)); + } + } + Ok(None) +}