From c95491358e619419e530facaeb6d843ffd2e06c3 Mon Sep 17 00:00:00 2001 From: Jorik Cronenberg Date: Wed, 31 Jan 2024 14:01:18 +0100 Subject: [PATCH] WIP: 8021x --- rust/agama-server/src/network/model.rs | 63 +++++++++++ rust/agama-server/src/network/nm/dbus.rs | 105 ++++++++++++++++++ rust/migrate-wicked/src/wireless.rs | 105 ++++++++++++++++++ .../tests/wireless/wicked_xml/wireless.xml | 39 +++++++ 4 files changed, 312 insertions(+) diff --git a/rust/agama-server/src/network/model.rs b/rust/agama-server/src/network/model.rs index bbe2f8668d..40a4482a1c 100644 --- a/rust/agama-server/src/network/model.rs +++ b/rust/agama-server/src/network/model.rs @@ -490,6 +490,7 @@ pub struct Connection { pub port_config: PortConfig, pub match_config: MatchConfig, pub config: ConnectionConfig, + pub ieee_8021x_config: Option, } impl Connection { @@ -560,6 +561,7 @@ impl Default for Connection { port_config: Default::default(), match_config: Default::default(), config: Default::default(), + ieee_8021x_config: Default::default(), } } } @@ -1353,3 +1355,64 @@ pub enum NetworkChange { /// device gets renamed. DeviceUpdated(String, Device), } + +#[derive(Default, Debug, PartialEq, Clone, Serialize)] +pub struct IEEE8021XConfig { + pub eap: Vec, + pub identity: Option, + pub password: Option, + pub ca_cert: Option, + pub ca_cert_password: Option, + pub client_cert: Option, + pub client_cert_password: Option, + pub private_key: Option, + pub private_key_password: Option, + pub anonymous_identity: Option, +} + +#[derive(Debug, Error)] +#[error("Invalid eap method: {0}")] +pub struct InvalidEAPMethod(String); + +#[derive(Debug, PartialEq, Clone, Serialize)] +pub enum EAPMethod { + LEAP, + MD5, + TLS, + PEAP, + TTLS, + PWD, + FAST, +} + +impl FromStr for EAPMethod { + type Err = InvalidEAPMethod; + + fn from_str(s: &str) -> Result { + match s { + "leap" => Ok(Self::LEAP), + "md5" => Ok(Self::MD5), + "tls" => Ok(Self::TLS), + "peap" => Ok(Self::PEAP), + "ttls" => Ok(Self::TTLS), + "pwd" => Ok(Self::PWD), + "fast" => Ok(Self::FAST), + _ => Err(InvalidEAPMethod(s.to_string())), + } + } +} + +impl fmt::Display for EAPMethod { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let value = match &self { + Self::LEAP => "leap", + Self::MD5 => "md5", + Self::TLS => "tls", + Self::PEAP => "peap", + Self::TTLS => "ttls", + Self::PWD => "pwd", + Self::FAST => "fast", + }; + write!(f, "{}", value) + } +} diff --git a/rust/agama-server/src/network/nm/dbus.rs b/rust/agama-server/src/network/nm/dbus.rs index b86853a8ad..ff8a1990d3 100644 --- a/rust/agama-server/src/network/nm/dbus.rs +++ b/rust/agama-server/src/network/nm/dbus.rs @@ -25,6 +25,7 @@ const BRIDGE_KEY: &str = "bridge"; const BRIDGE_PORT_KEY: &str = "bridge-port"; const INFINIBAND_KEY: &str = "infiniband"; const TUN_KEY: &str = "tun"; +const IEEE_8021X_KEY: &str = "802-1x"; /// Converts a connection struct into a HashMap that can be sent over D-Bus. /// @@ -137,6 +138,10 @@ pub fn connection_to_dbus<'a>( PortConfig::None => {} } + if let Some(ieee_8021x_config) = &conn.ieee_8021x_config { + result.insert(IEEE_8021X_KEY, ieee_8021x_config_to_dbus(ieee_8021x_config)); + } + result.insert("connection", connection_dbus); result } @@ -151,6 +156,10 @@ pub fn connection_from_dbus(conn: OwnedNestedHash) -> Option { connection.port_config = PortConfig::Bridge(bridge_port_config); } + if let Some(ieee_8021x_config) = ieee_8021x_config_from_dbus(&conn) { + connection.ieee_8021x_config = Some(ieee_8021x_config); + } + if let Some(wireless_config) = wireless_config_from_dbus(&conn) { connection.config = ConnectionConfig::Wireless(wireless_config); return Some(connection); @@ -981,6 +990,102 @@ fn vlan_config_from_dbus(conn: &OwnedNestedHash) -> Option { }) } +fn ieee_8021x_config_to_dbus(config: &IEEE8021XConfig) -> HashMap<&str, zvariant::Value> { + let mut ieee_8021x_config: HashMap<&str, zvariant::Value> = HashMap::from([( + "eap", + config + .eap + .iter() + .map(|x| x.to_string()) + .collect::>() + .into(), + )]); + + if let Some(identity) = &config.identity { + ieee_8021x_config.insert("identity", identity.into()); + } + if let Some(password) = &config.password { + ieee_8021x_config.insert("password", password.into()); + } + if let Some(ca_cert) = &config.ca_cert { + ieee_8021x_config.insert("ca-cert", ca_cert.into()); + } + if let Some(ca_cert_password) = &config.ca_cert_password { + ieee_8021x_config.insert("ca-cert-password", ca_cert_password.into()); + } + if let Some(client_cert) = &config.client_cert { + ieee_8021x_config.insert("client-cert", client_cert.into()); + } + if let Some(client_cert_password) = &config.client_cert_password { + ieee_8021x_config.insert("client-cert-password", client_cert_password.into()); + } + if let Some(private_key) = &config.private_key { + ieee_8021x_config.insert("private-key", private_key.into()); + } + if let Some(private_key_password) = &config.private_key_password { + ieee_8021x_config.insert("private-key-password", private_key_password.into()); + } + if let Some(anonymous_identity) = &config.anonymous_identity { + ieee_8021x_config.insert("anonymous-identity", anonymous_identity.into()); + } + + ieee_8021x_config +} + +fn ieee_8021x_config_from_dbus(conn: &OwnedNestedHash) -> Option { + let ieee_8021x = conn.get(IEEE_8021X_KEY)?; + + let mut ieee_8021x_config = IEEE8021XConfig::default(); + + if let Some(eap) = ieee_8021x.get("eap") { + let eap: &zvariant::Array = eap.downcast_ref()?; + let eap: Vec<&str> = eap + .iter() + .map(|x| x.downcast_ref::()) + .collect::>>()?; + let eap: Vec = eap + .iter() + .map(|x| EAPMethod::from_str(x)) + .collect::, InvalidEAPMethod>>() + .ok()?; + ieee_8021x_config.eap = eap; + } + + if let Some(identity) = ieee_8021x.get("identity") { + ieee_8021x_config.identity = Some(identity.downcast_ref::()?.to_string()); + } + if let Some(password) = ieee_8021x.get("password") { + ieee_8021x_config.password = Some(password.downcast_ref::()?.to_string()); + } + if let Some(ca_cert) = ieee_8021x.get("ca_cert") { + ieee_8021x_config.ca_cert = Some(ca_cert.downcast_ref::()?.to_string()); + } + if let Some(ca_cert_password) = ieee_8021x.get("ca_cert_password") { + ieee_8021x_config.ca_cert_password = + Some(ca_cert_password.downcast_ref::()?.to_string()); + } + if let Some(client_cert) = ieee_8021x.get("client_cert") { + ieee_8021x_config.client_cert = Some(client_cert.downcast_ref::()?.to_string()); + } + if let Some(client_cert_password) = ieee_8021x.get("client_cert_password") { + ieee_8021x_config.client_cert_password = + Some(client_cert_password.downcast_ref::()?.to_string()); + } + if let Some(private_key) = ieee_8021x.get("private_key") { + ieee_8021x_config.private_key = Some(private_key.downcast_ref::()?.to_string()); + } + if let Some(private_key_password) = ieee_8021x.get("private_key_password") { + ieee_8021x_config.private_key_password = + Some(private_key_password.downcast_ref::()?.to_string()); + } + if let Some(anonymous_identity) = ieee_8021x.get("anonymous_identity") { + ieee_8021x_config.anonymous_identity = + Some(anonymous_identity.downcast_ref::()?.to_string()); + } + + Some(ieee_8021x_config) +} + /// Determines whether a value is empty. /// /// TODO: Generalize for other kind of values, like dicts or arrays. diff --git a/rust/migrate-wicked/src/wireless.rs b/rust/migrate-wicked/src/wireless.rs index 64b07917ed..bd24f43b7f 100644 --- a/rust/migrate-wicked/src/wireless.rs +++ b/rust/migrate-wicked/src/wireless.rs @@ -38,6 +38,8 @@ pub struct Network { #[serde(rename = "access-point")] pub access_point: Option, pub wep: Option, + #[serde(rename = "wpa-eap")] + pub wpa_eap: Option, } #[derive(Default, Debug, PartialEq, SerializeDisplay, DeserializeFromStr, EnumString, Display)] @@ -73,6 +75,107 @@ pub struct Wep { pub key: Vec, } +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub struct WpaEap { + pub method: EapMethod, + #[serde(rename = "auth-proto")] + pub auth_proto: EapAuthProto, + #[serde(rename = "pairwise-cipher")] + pub pairwise_cipher: EapPairwiseCipher, + #[serde(rename = "group-cipher")] + pub group_cipher: EapGroupCipher, + pub identity: String, + pub tls: Option, +} + +#[derive(Default, Debug, PartialEq, SerializeDisplay, DeserializeFromStr, EnumString, Display)] +#[strum(serialize_all = "kebab-case")] +pub enum EapMethod { + #[default] + TLS, + PEAP, + TTLS, +} + +#[derive(Default, Debug, PartialEq, SerializeDisplay, DeserializeFromStr, EnumString, Display)] +// TODO i don't think this is correct +// but tbh this is probably overkill anyway +#[strum(serialize_all = "kebab-case")] +pub enum EapAuthProto { + #[default] + WPA, + NONE, + MD5, + TLS, + PAP, + CHAP, + MSCHAP, + MSCHAPV2, + PEAP, + TTLS, + GTC, + OTP, + LEAP, + PSK, + PAX, + SAKE, + GPSK, + WSC, + IKEV2, + TNC, + FAST, + AKA, + AkaPrime, + SIM, +} + +// TODO will have to look into wicked code into what options the "inner" and "outer" get translated +impl TryFrom for model::EAPMethod { + type Error = anyhow::Error; + + fn try_from(value: EapAuthProto) -> Result { + match value { + EapAuthProto::LEAP => Ok(model::EAPMethod::LEAP), + EapAuthProto::MD5 => Ok(model::EAPMethod::MD5), + EapAuthProto::TLS => Ok(model::EAPMethod::TLS), + EapAuthProto::PEAP => Ok(model::EAPMethod::PEAP), + EapAuthProto::TTLS => Ok(model::EAPMethod::TTLS), + EapAuthProto::FAST => Ok(model::EAPMethod::FAST), + _ => Err(anyhow!("EAP auth-proto isn't supported by NetworkManager")), + } + } +} + +#[derive(Default, Debug, PartialEq, SerializeDisplay, DeserializeFromStr, EnumString, Display)] +#[strum(serialize_all = "UPPERCASE")] +pub enum EapPairwiseCipher { + #[default] + TKIP, + CCMP, +} + +#[derive(Default, Debug, PartialEq, SerializeDisplay, DeserializeFromStr, EnumString, Display)] +#[strum(serialize_all = "UPPERCASE")] +pub enum EapGroupCipher { + #[default] + TKIP, + CCMP, + WEP104, + WEP40, +} + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub struct WickedTLS { + #[serde(rename = "ca-cert")] + pub ca_cert: String, + #[serde(rename = "client-cert")] + pub client_cert: String, + #[serde(rename = "client-key")] + pub client_key: String, + #[serde(rename = "client-key-passwd")] + pub client_key_passwd: String, +} + fn unwrap_wireless_networks<'de, D>(deserializer: D) -> Result>, D::Error> where D: Deserializer<'de>, @@ -202,6 +305,7 @@ mod tests { key_management: vec!["wpa-psk".to_string()], access_point: None, wep: None, + wpa_eap: None, }]), ap_scan: 0, }), @@ -252,6 +356,7 @@ mod tests { default_key: 1, key: vec!["01020304ff".to_string(), "s:hello".to_string()], }), + wpa_eap: None, }]), ap_scan: 0, }), diff --git a/rust/migrate-wicked/tests/wireless/wicked_xml/wireless.xml b/rust/migrate-wicked/tests/wireless/wicked_xml/wireless.xml index 0edebaf1ce..e4ffc60cdc 100644 --- a/rust/migrate-wicked/tests/wireless/wicked_xml/wireless.xml +++ b/rust/migrate-wicked/tests/wireless/wicked_xml/wireless.xml @@ -75,3 +75,42 @@ false + + wlan2 + + manual + + + + 1 + + + test + true + ap + 12:34:56:78:9a:bc + wpa-eap + + tls + wpa + TKIP + TKIP + test + + /etc/sysconfig/network/./ca_cert + /etc/sysconfig/network/./client_cert + /etc/sysconfig/network/./client_key + testclientpw + + + + + + + + false + + + false + +