nmstate/ifaces/
ipsec.rs

1// SPDX-License-Identifier: Apache-2.0
2
3use serde::{Deserialize, Deserializer, Serialize};
4
5use crate::{BaseInterface, InterfaceType, NetworkState};
6
7/// The libreswan Ipsec interface.
8///
9/// This interface does not exist in kernel space but only exist in user space
10/// tools.  This is the example yaml output of [crate::NetworkState] with a
11/// libreswan ipsec connection:
12/// ```yaml
13/// ---
14/// interfaces:
15/// - name: hosta_conn
16///   type: ipsec
17///   ipv4:
18///     enabled: true
19///     dhcp: true
20///   libreswan:
21///     right: 192.0.2.252
22///     rightid: '@hostb.example.org'
23///     left: 192.0.2.251
24///     leftid: '%fromcert'
25///     leftcert: hosta.example.org
26///     ikev2: insist
27/// ```
28#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
29#[non_exhaustive]
30pub struct IpsecInterface {
31    #[serde(flatten)]
32    pub base: BaseInterface,
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub libreswan: Option<LibreswanConfig>,
35}
36
37impl Default for IpsecInterface {
38    fn default() -> Self {
39        Self {
40            base: BaseInterface {
41                iface_type: InterfaceType::Ipsec,
42                ..Default::default()
43            },
44            libreswan: None,
45        }
46    }
47}
48
49impl IpsecInterface {
50    pub fn new() -> Self {
51        Self::default()
52    }
53
54    pub(crate) fn hide_secrets(&mut self) {
55        if let Some(c) = self.libreswan.as_mut() {
56            if c.psk.is_some() {
57                c.psk = Some(NetworkState::PASSWORD_HID_BY_NMSTATE.to_string());
58            }
59        }
60    }
61
62    // * IPv4 `dhcp: false` with empty static address list should be considered
63    //   as IPv4 disabled.
64    pub(crate) fn sanitize(&mut self, is_desired: bool) {
65        if let Some(ipv4_conf) = self.base.ipv4.as_mut() {
66            if ipv4_conf.dhcp == Some(false)
67                && ipv4_conf.enabled
68                && ipv4_conf.enabled_defined
69            {
70                if is_desired {
71                    log::info!(
72                        "Treating IPv4 `dhcp: false` for IPSec interface {} \
73                         as IPv4 disabled",
74                        self.base.name.as_str()
75                    );
76                }
77                ipv4_conf.enabled = false;
78                ipv4_conf.dhcp = None;
79            }
80        }
81    }
82}
83
84#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
85#[serde(deny_unknown_fields)]
86#[non_exhaustive]
87pub struct LibreswanConfig {
88    pub right: String,
89    #[serde(skip_serializing_if = "Option::is_none")]
90    pub rightid: Option<String>,
91    #[serde(skip_serializing_if = "Option::is_none")]
92    pub rightrsasigkey: Option<String>,
93    #[serde(skip_serializing_if = "Option::is_none")]
94    pub rightcert: Option<String>,
95    #[serde(skip_serializing_if = "Option::is_none")]
96    pub left: Option<String>,
97    #[serde(skip_serializing_if = "Option::is_none")]
98    pub leftid: Option<String>,
99    #[serde(skip_serializing_if = "Option::is_none")]
100    pub leftrsasigkey: Option<String>,
101    #[serde(skip_serializing_if = "Option::is_none")]
102    pub leftcert: Option<String>,
103    #[serde(skip_serializing_if = "Option::is_none")]
104    pub ikev2: Option<String>,
105    /// PSK authentication, if not defined, will use X.509 PKI authentication
106    #[serde(skip_serializing_if = "Option::is_none")]
107    pub psk: Option<String>,
108    #[serde(skip_serializing_if = "Option::is_none")]
109    pub ikelifetime: Option<String>,
110    #[serde(skip_serializing_if = "Option::is_none")]
111    pub salifetime: Option<String>,
112    #[serde(skip_serializing_if = "Option::is_none")]
113    pub ike: Option<String>,
114    #[serde(skip_serializing_if = "Option::is_none")]
115    pub esp: Option<String>,
116    #[serde(
117        skip_serializing_if = "Option::is_none",
118        default,
119        deserialize_with = "crate::deserializer::option_u64_or_string"
120    )]
121    pub dpddelay: Option<u64>,
122    #[serde(
123        skip_serializing_if = "Option::is_none",
124        default,
125        deserialize_with = "crate::deserializer::option_u64_or_string"
126    )]
127    pub dpdtimeout: Option<u64>,
128    #[serde(skip_serializing_if = "Option::is_none")]
129    pub dpdaction: Option<String>,
130    #[serde(
131        skip_serializing_if = "Option::is_none",
132        rename = "ipsec-interface",
133        default,
134        deserialize_with = "parse_ipsec_iface"
135    )]
136    pub ipsec_interface: Option<String>,
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub authby: Option<String>,
139    #[serde(skip_serializing_if = "Option::is_none")]
140    pub rightsubnet: Option<String>,
141    #[serde(skip_serializing_if = "Option::is_none")]
142    pub leftsubnet: Option<String>,
143    #[serde(skip_serializing_if = "Option::is_none")]
144    pub leftmodecfgclient: Option<bool>,
145    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
146    pub kind: Option<LibreswanConnectionType>,
147    #[serde(skip_serializing_if = "Option::is_none")]
148    pub hostaddrfamily: Option<LibreswanAddressFamily>,
149    #[serde(skip_serializing_if = "Option::is_none")]
150    pub clientaddrfamily: Option<LibreswanAddressFamily>,
151    #[serde(
152        skip_serializing_if = "Option::is_none",
153        rename = "require-id-on-certificate"
154    )]
155    pub require_id_on_certificate: Option<bool>,
156}
157
158impl LibreswanConfig {
159    pub fn new() -> Self {
160        Self::default()
161    }
162}
163
164impl std::fmt::Debug for LibreswanConfig {
165    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
166        f.debug_struct("LibreswanConfig")
167            .field("right", &self.right)
168            .field("rightid", &self.rightid)
169            .field("rightrsasigkey", &self.rightrsasigkey)
170            .field("rightcert", &self.rightcert)
171            .field("left", &self.left)
172            .field("leftid", &self.leftid)
173            .field("leftrsasigkey", &self.leftrsasigkey)
174            .field("leftcert", &self.leftcert)
175            .field("ikev2", &self.ikev2)
176            .field(
177                "psk",
178                &Some(NetworkState::PASSWORD_HID_BY_NMSTATE.to_string()),
179            )
180            .field("ikelifetime", &self.ikelifetime)
181            .field("salifetime", &self.salifetime)
182            .field("ike", &self.ike)
183            .field("esp", &self.esp)
184            .field("dpddelay", &self.dpddelay)
185            .field("dpdtimeout", &self.dpdtimeout)
186            .field("dpdaction", &self.dpdaction)
187            .field("ipsec_interface", &self.ipsec_interface)
188            .field("authby", &self.authby)
189            .field("rightsubnet", &self.rightsubnet)
190            .field("leftsubnet", &self.leftsubnet)
191            .field("leftmodecfgclient", &self.leftmodecfgclient)
192            .field("kind", &self.kind)
193            .field("hostaddrfamily", &self.hostaddrfamily)
194            .field("clientaddrfamily", &self.clientaddrfamily)
195            .field("require_id_on_certificate", &self.require_id_on_certificate)
196            .finish()
197    }
198}
199
200fn parse_ipsec_iface<'de, D>(
201    deserializer: D,
202) -> Result<Option<String>, D::Error>
203where
204    D: Deserializer<'de>,
205{
206    let v = serde_json::Value::deserialize(deserializer)?;
207
208    match v {
209        serde_json::Value::Number(d) => {
210            if let Some(d) = d.as_u64() {
211                Ok(Some(d.to_string()))
212            } else {
213                Err(serde::de::Error::custom(
214                    "Invalid ipsec-interface value, should be unsigned \
215                     integer, string 'yes' or 'no'",
216                ))
217            }
218        }
219        serde_json::Value::String(s) => match s.as_str() {
220            "yes" | "no" => Ok(Some(s)),
221            _ => {
222                if s.parse::<u32>().is_ok() {
223                    Ok(Some(s))
224                } else {
225                    Err(serde::de::Error::custom(
226                        "Invalid ipsec-interface value, should be unsigned \
227                         integer, string 'yes' or 'no'",
228                    ))
229                }
230            }
231        },
232        _ => Err(serde::de::Error::custom(
233            "Invalid ipsec-interface value, should be unsigned integer, \
234             string 'yes' or 'no'",
235        )),
236    }
237}
238
239#[derive(
240    Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default,
241)]
242#[non_exhaustive]
243#[serde(rename_all = "lowercase")]
244pub enum LibreswanConnectionType {
245    #[default]
246    Tunnel,
247    Transport,
248}
249
250impl std::fmt::Display for LibreswanConnectionType {
251    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
252        write!(
253            f,
254            "{}",
255            match self {
256                Self::Tunnel => "tunnel",
257                Self::Transport => "transport",
258            }
259        )
260    }
261}
262
263#[derive(
264    Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default,
265)]
266#[non_exhaustive]
267#[serde(rename_all = "lowercase")]
268pub enum LibreswanAddressFamily {
269    #[default]
270    Ipv4,
271    Ipv6,
272}
273
274impl std::fmt::Display for LibreswanAddressFamily {
275    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
276        write!(
277            f,
278            "{}",
279            match self {
280                Self::Ipv4 => "ipv4",
281                Self::Ipv6 => "ipv6",
282            }
283        )
284    }
285}