Skip to content

Commit

Permalink
Implement 8021x (EAP) in network settings
Browse files Browse the repository at this point in the history
  • Loading branch information
jcronenberg committed Sep 10, 2024
1 parent 9d30030 commit b81068a
Show file tree
Hide file tree
Showing 5 changed files with 647 additions and 1 deletion.
75 changes: 75 additions & 0 deletions rust/agama-lib/share/profile.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,81 @@
}
}
}
},
"ieee_8021x": {
"type": "object",
"properties": {
"eap": {
"type": "array",
"items": [
{
"type": "string",
"enum": [
"leap",
"md5",
"tls",
"peap",
"ttls",
"pwd",
"fast"
]
}
]
},
"phase2_auth": {
"type": "string",
"enum": [
"pap",
"chap",
"mschap",
"mschapv2",
"gtc",
"otp",
"md5",
"tls"
]
},
"identity": {
"type": "string"
},
"password": {
"type": "string"
},
"ca_cert": {
"title": "Path to ca_cert",
"type": "string"
},
"ca_cert_password": {
"type": "string"
},
"client_cert": {
"title": "Path to client_cert",
"type": "string"
},
"client_cert_password": {
"type": "string"
},
"private_key": {
"title": "Path to private_key",
"type": "string"
},
"private_key_password": {
"type": "string"
},
"anonymous_identity": {
"type": "string"
},
"peap_version": {
"type": "string",
"enum": [
"0",
"1"
]
},
"peap_label": {
"type": "boolean"
}
}
}
}
}
Expand Down
32 changes: 32 additions & 0 deletions rust/agama-lib/src/network/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,36 @@ impl Default for BondSettings {
}
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct IEEE8021XSettings {
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub eap: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub phase2_auth: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub identity: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub password: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ca_cert: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ca_cert_password: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub client_cert: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub client_cert_password: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub private_key: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub private_key_password: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub anonymous_identity: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub peap_version: Option<String>,
#[serde(skip_serializing_if = "std::ops::Not::not")]
pub peap_label: bool,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct NetworkDevice {
pub id: String,
Expand Down Expand Up @@ -106,6 +136,8 @@ pub struct NetworkConnection {
pub status: Option<Status>,
#[serde(skip_serializing_if = "is_zero", default)]
pub mtu: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub ieee_8021x: Option<IEEE8021XSettings>,
}

fn is_zero(u: &u32) -> bool {
Expand Down
4 changes: 4 additions & 0 deletions rust/agama-server/src/network/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ pub enum NetworkStateError {
InvalidWEPAuthAlg(String),
#[error("Invalid WEP key type: '{0}'")]
InvalidWEPKeyType(u32),
#[error("Invalid EAP method: '{0}'")]
InvalidEAPMethod(String),
#[error("Invalid phase2 authentication method: '{0}'")]
InvalidPhase2AuthMethod(String),
}

impl From<NetworkStateError> for zbus::fdo::Error {
Expand Down
196 changes: 195 additions & 1 deletion rust/agama-server/src/network/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
//! * This module contains the types that represent the network concepts. They are supposed to be
//! agnostic from the real network service (e.g., NetworkManager).
use crate::network::error::NetworkStateError;
use agama_lib::network::settings::{BondSettings, NetworkConnection, WirelessSettings};
use agama_lib::network::settings::{
BondSettings, IEEE8021XSettings, NetworkConnection, WirelessSettings,
};
use agama_lib::network::types::{BondMode, DeviceState, DeviceType, Status, SSID};
use cidr::IpInet;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -490,6 +492,7 @@ pub struct Connection {
pub port_config: PortConfig,
pub match_config: MatchConfig,
pub config: ConnectionConfig,
pub ieee_8021x_config: Option<IEEE8021XConfig>,
}

impl Connection {
Expand Down Expand Up @@ -560,6 +563,7 @@ impl Default for Connection {
port_config: Default::default(),
match_config: Default::default(),
config: Default::default(),
ieee_8021x_config: Default::default(),
}
}
}
Expand Down Expand Up @@ -599,6 +603,10 @@ impl TryFrom<NetworkConnection> for Connection {
connection.config = config.into();
}

if let Some(ieee_8021x_config) = conn.ieee_8021x {
connection.ieee_8021x_config = Some(IEEE8021XConfig::try_from(ieee_8021x_config)?);
}

connection.ip_config.addresses = conn.addresses;
connection.ip_config.nameservers = conn.nameservers;
connection.ip_config.dns_searchlist = conn.dns_searchlist;
Expand Down Expand Up @@ -629,6 +637,9 @@ impl TryFrom<Connection> for NetworkConnection {
let interface = conn.interface;
let status = Some(conn.status);
let mtu = conn.mtu;
let ieee_8021x: Option<IEEE8021XSettings> = conn
.ieee_8021x_config
.and_then(|x| IEEE8021XSettings::try_from(x).ok());

let mut connection = NetworkConnection {
id,
Expand All @@ -644,6 +655,7 @@ impl TryFrom<Connection> for NetworkConnection {
interface,
addresses,
mtu,
ieee_8021x,
..Default::default()
};

Expand Down Expand Up @@ -1353,3 +1365,185 @@ pub enum NetworkChange {
/// device gets renamed.
DeviceUpdated(String, Device),
}

#[derive(Default, Debug, PartialEq, Clone, Serialize)]
pub struct IEEE8021XConfig {
pub eap: Vec<EAPMethod>,
pub phase2_auth: Option<Phase2AuthMethod>,
pub identity: Option<String>,
pub password: Option<String>,
pub ca_cert: Option<String>,
pub ca_cert_password: Option<String>,
pub client_cert: Option<String>,
pub client_cert_password: Option<String>,
pub private_key: Option<String>,
pub private_key_password: Option<String>,
pub anonymous_identity: Option<String>,
pub peap_version: Option<String>,
pub peap_label: bool,
}

impl TryFrom<IEEE8021XSettings> for IEEE8021XConfig {
type Error = NetworkStateError;

fn try_from(value: IEEE8021XSettings) -> Result<Self, Self::Error> {
let eap = value
.eap
.iter()
.map(|x| {
EAPMethod::from_str(x)
.map_err(|_| NetworkStateError::InvalidEAPMethod(x.to_string()))
})
.collect::<Result<Vec<EAPMethod>, NetworkStateError>>()?;
let phase2_auth =
if let Some(phase2_auth) = &value.phase2_auth {
Some(Phase2AuthMethod::from_str(phase2_auth).map_err(|_| {
NetworkStateError::InvalidPhase2AuthMethod(phase2_auth.to_string())
})?)
} else {
None
};

Ok(IEEE8021XConfig {
eap,
phase2_auth,
identity: value.identity,
password: value.password,
ca_cert: value.ca_cert,
ca_cert_password: value.ca_cert_password,
client_cert: value.client_cert,
client_cert_password: value.client_cert_password,
private_key: value.private_key,
private_key_password: value.private_key_password,
anonymous_identity: value.anonymous_identity,
peap_version: value.peap_version,
peap_label: value.peap_label,
})
}
}

impl TryFrom<IEEE8021XConfig> for IEEE8021XSettings {
type Error = NetworkStateError;

fn try_from(value: IEEE8021XConfig) -> Result<Self, Self::Error> {
let eap = value
.eap
.iter()
.map(|x| x.to_string())
.collect::<Vec<String>>();
let phase2_auth = value.phase2_auth.map(|phase2_auth| phase2_auth.to_string());

Ok(IEEE8021XSettings {
eap,
phase2_auth,
identity: value.identity,
password: value.password,
ca_cert: value.ca_cert,
ca_cert_password: value.ca_cert_password,
client_cert: value.client_cert,
client_cert_password: value.client_cert_password,
private_key: value.private_key,
private_key_password: value.private_key_password,
anonymous_identity: value.anonymous_identity,
peap_version: value.peap_version,
peap_label: value.peap_label,
})
}
}

#[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<Self, Self::Err> {
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)
}
}

#[derive(Debug, Error)]
#[error("Invalid phase2-auth method: {0}")]
pub struct InvalidPhase2AuthMethod(String);

#[derive(Debug, PartialEq, Clone, Serialize)]
pub enum Phase2AuthMethod {
PAP,
CHAP,
MSCHAP,
MSCHAPV2,
GTC,
OTP,
MD5,
TLS,
}

impl FromStr for Phase2AuthMethod {
type Err = InvalidPhase2AuthMethod;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"pap" => Ok(Self::PAP),
"chap" => Ok(Self::CHAP),
"mschap" => Ok(Self::MSCHAP),
"mschapv2" => Ok(Self::MSCHAPV2),
"gtc" => Ok(Self::GTC),
"otp" => Ok(Self::OTP),
"md5" => Ok(Self::MD5),
"tls" => Ok(Self::TLS),
_ => Err(InvalidPhase2AuthMethod(s.to_string())),
}
}
}

impl fmt::Display for Phase2AuthMethod {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let value = match self {
Self::PAP => "pap",
Self::CHAP => "chap",
Self::MSCHAP => "mschap",
Self::MSCHAPV2 => "mschapv2",
Self::GTC => "gtc",
Self::OTP => "otp",
Self::MD5 => "md5",
Self::TLS => "tls",
};
write!(f, "{}", value)
}
}
Loading

0 comments on commit b81068a

Please sign in to comment.