Skip to main content

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            && c.psk.is_some()
57        {
58            c.psk = Some(NetworkState::PASSWORD_HID_BY_NMSTATE.to_string());
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            && 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 {} as \
73                     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#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
84#[serde(deny_unknown_fields)]
85#[non_exhaustive]
86pub struct LibreswanConfig {
87    /// Whether use NetworkManager default value. Default is true.
88    /// Setting false will instruct nmstate to follow libreswan default value
89    /// sets.
90    #[serde(rename = "nm-auto-defaults", default = "default_true")]
91    pub nm_auto_defaults: bool,
92    pub right: String,
93    #[serde(skip_serializing_if = "Option::is_none")]
94    pub rightid: Option<String>,
95    #[serde(skip_serializing_if = "Option::is_none")]
96    pub rightrsasigkey: Option<String>,
97    #[serde(skip_serializing_if = "Option::is_none")]
98    pub rightcert: Option<String>,
99    #[serde(skip_serializing_if = "Option::is_none")]
100    pub left: Option<String>,
101    #[serde(skip_serializing_if = "Option::is_none")]
102    pub leftid: Option<String>,
103    #[serde(skip_serializing_if = "Option::is_none")]
104    pub leftrsasigkey: Option<String>,
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub leftcert: Option<String>,
107    #[serde(skip_serializing_if = "Option::is_none")]
108    pub ikev2: Option<String>,
109    /// PSK authentication, if not defined, will use X.509 PKI authentication
110    #[serde(skip_serializing_if = "Option::is_none")]
111    pub psk: Option<String>,
112    #[serde(skip_serializing_if = "Option::is_none")]
113    pub ikelifetime: Option<String>,
114    #[serde(skip_serializing_if = "Option::is_none")]
115    pub salifetime: Option<String>,
116    #[serde(skip_serializing_if = "Option::is_none")]
117    pub ike: Option<String>,
118    #[serde(skip_serializing_if = "Option::is_none")]
119    pub esp: Option<String>,
120    #[serde(
121        skip_serializing_if = "Option::is_none",
122        default,
123        deserialize_with = "crate::deserializer::option_u64_or_string"
124    )]
125    pub dpddelay: Option<u64>,
126    #[serde(
127        skip_serializing_if = "Option::is_none",
128        default,
129        deserialize_with = "crate::deserializer::option_u64_or_string"
130    )]
131    pub dpdtimeout: Option<u64>,
132    #[serde(skip_serializing_if = "Option::is_none")]
133    pub dpdaction: Option<String>,
134    #[serde(
135        skip_serializing_if = "Option::is_none",
136        rename = "ipsec-interface",
137        default,
138        deserialize_with = "parse_ipsec_iface"
139    )]
140    pub ipsec_interface: Option<String>,
141    #[serde(skip_serializing_if = "Option::is_none")]
142    pub authby: Option<String>,
143    #[serde(skip_serializing_if = "Option::is_none")]
144    pub rightsubnet: Option<String>,
145    #[serde(skip_serializing_if = "Option::is_none")]
146    pub rightsubnets: Option<String>,
147    #[serde(skip_serializing_if = "Option::is_none")]
148    pub leftsubnet: Option<String>,
149    #[serde(skip_serializing_if = "Option::is_none")]
150    pub leftsubnets: Option<String>,
151    #[serde(skip_serializing_if = "Option::is_none")]
152    pub leftmodecfgclient: Option<bool>,
153    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
154    pub kind: Option<LibreswanConnectionType>,
155    #[serde(skip_serializing_if = "Option::is_none")]
156    pub hostaddrfamily: Option<LibreswanAddressFamily>,
157    #[serde(skip_serializing_if = "Option::is_none")]
158    pub clientaddrfamily: Option<LibreswanAddressFamily>,
159    #[serde(
160        skip_serializing_if = "Option::is_none",
161        rename = "require-id-on-certificate"
162    )]
163    pub require_id_on_certificate: Option<bool>,
164    #[serde(skip_serializing_if = "Option::is_none")]
165    pub leftsendcert: Option<String>,
166    #[serde(skip_serializing_if = "Option::is_none")]
167    pub rightca: Option<String>,
168    #[serde(skip_serializing_if = "Option::is_none")]
169    pub leftprotoport: Option<String>,
170    #[serde(skip_serializing_if = "Option::is_none")]
171    pub rightprotoport: Option<String>,
172}
173
174fn default_true() -> bool {
175    true
176}
177
178impl LibreswanConfig {
179    pub fn new() -> Self {
180        Self::default()
181    }
182}
183
184impl std::fmt::Debug for LibreswanConfig {
185    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
186        f.debug_struct("LibreswanConfig")
187            .field("nm-auto-defaults", &self.nm_auto_defaults)
188            .field("right", &self.right)
189            .field("rightid", &self.rightid)
190            .field("rightrsasigkey", &self.rightrsasigkey)
191            .field("rightcert", &self.rightcert)
192            .field("left", &self.left)
193            .field("leftid", &self.leftid)
194            .field("leftrsasigkey", &self.leftrsasigkey)
195            .field("leftcert", &self.leftcert)
196            .field("ikev2", &self.ikev2)
197            .field(
198                "psk",
199                &Some(NetworkState::PASSWORD_HID_BY_NMSTATE.to_string()),
200            )
201            .field("ikelifetime", &self.ikelifetime)
202            .field("salifetime", &self.salifetime)
203            .field("ike", &self.ike)
204            .field("esp", &self.esp)
205            .field("dpddelay", &self.dpddelay)
206            .field("dpdtimeout", &self.dpdtimeout)
207            .field("dpdaction", &self.dpdaction)
208            .field("ipsec_interface", &self.ipsec_interface)
209            .field("authby", &self.authby)
210            .field("rightsubnet", &self.rightsubnet)
211            .field("rightsubnets", &self.rightsubnets)
212            .field("leftsubnet", &self.leftsubnet)
213            .field("leftsubnets", &self.leftsubnets)
214            .field("leftmodecfgclient", &self.leftmodecfgclient)
215            .field("kind", &self.kind)
216            .field("hostaddrfamily", &self.hostaddrfamily)
217            .field("clientaddrfamily", &self.clientaddrfamily)
218            .field("require_id_on_certificate", &self.require_id_on_certificate)
219            .field("leftsendcert", &self.leftsendcert)
220            .field("rightca", &self.rightca)
221            .field("leftprotoport", &self.leftprotoport)
222            .field("rightprotoport", &self.rightprotoport)
223            .finish()
224    }
225}
226
227fn parse_ipsec_iface<'de, D>(
228    deserializer: D,
229) -> Result<Option<String>, D::Error>
230where
231    D: Deserializer<'de>,
232{
233    let v = serde_json::Value::deserialize(deserializer)?;
234
235    match v {
236        serde_json::Value::Number(d) => {
237            if let Some(d) = d.as_u64() {
238                Ok(Some(d.to_string()))
239            } else {
240                Err(serde::de::Error::custom(
241                    "Invalid ipsec-interface value, should be unsigned \
242                     integer, string 'yes' or 'no'",
243                ))
244            }
245        }
246        serde_json::Value::String(s) => match s.as_str() {
247            "yes" | "no" => Ok(Some(s)),
248            _ => {
249                if s.parse::<u32>().is_ok() {
250                    Ok(Some(s))
251                } else {
252                    Err(serde::de::Error::custom(
253                        "Invalid ipsec-interface value, should be unsigned \
254                         integer, string 'yes' or 'no'",
255                    ))
256                }
257            }
258        },
259        _ => Err(serde::de::Error::custom(
260            "Invalid ipsec-interface value, should be unsigned integer, \
261             string 'yes' or 'no'",
262        )),
263    }
264}
265
266#[derive(
267    Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default,
268)]
269#[non_exhaustive]
270#[serde(rename_all = "lowercase")]
271pub enum LibreswanConnectionType {
272    #[default]
273    Tunnel,
274    Transport,
275}
276
277impl std::fmt::Display for LibreswanConnectionType {
278    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
279        write!(
280            f,
281            "{}",
282            match self {
283                Self::Tunnel => "tunnel",
284                Self::Transport => "transport",
285            }
286        )
287    }
288}
289
290#[derive(
291    Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default,
292)]
293#[non_exhaustive]
294#[serde(rename_all = "lowercase")]
295pub enum LibreswanAddressFamily {
296    #[default]
297    Ipv4,
298    Ipv6,
299}
300
301impl std::fmt::Display for LibreswanAddressFamily {
302    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
303        write!(
304            f,
305            "{}",
306            match self {
307                Self::Ipv4 => "ipv4",
308                Self::Ipv6 => "ipv6",
309            }
310        )
311    }
312}