Skip to content

Commit

Permalink
Merge pull request #491 from sarmahaj/default_if_serialnumber
Browse files Browse the repository at this point in the history
feat(manufacturing-client): use default ipv4 iface route if not provided
  • Loading branch information
7flying authored Jul 24, 2023
2 parents 4d4be28 + 76d567d commit c630de4
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 29 deletions.
29 changes: 16 additions & 13 deletions HOWTO.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.

Expand All @@ -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
Expand Down
49 changes: 49 additions & 0 deletions integration-tests/tests/di_diun.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}
108 changes: 92 additions & 16 deletions manufacturing-client/src/main.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -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
Expand All @@ -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");
}
}
}
}
}
}
Expand Down Expand Up @@ -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
Expand All @@ -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");
}
}
}
}
}
Expand Down Expand Up @@ -944,3 +998,25 @@ impl KeyReference {
}
}
}

const IPV4_DEFAULT: &str = "00000000";

fn get_default_network_iface() -> Result<Option<String>, 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)
}

0 comments on commit c630de4

Please sign in to comment.