Skip to main content

unifly_api/command/requests/
vpn.rs

1use std::collections::BTreeMap;
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6fn default_enabled() -> bool {
7    true
8}
9
10fn default_purpose() -> String {
11    "site-vpn".into()
12}
13
14fn default_remote_access_purpose() -> String {
15    "remote-user-vpn".into()
16}
17
18fn default_vpn_client_purpose() -> String {
19    "vpn-client".into()
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct CreateSiteToSiteVpnRequest {
24    pub name: String,
25    pub vpn_type: String,
26    #[serde(default = "default_purpose")]
27    pub purpose: String,
28    #[serde(default = "default_enabled")]
29    pub enabled: bool,
30    #[serde(default, skip_serializing_if = "Option::is_none")]
31    pub remote_site_id: Option<String>,
32    #[serde(default, skip_serializing_if = "Option::is_none")]
33    pub x_ipsec_pre_shared_key: Option<String>,
34    #[serde(default, skip_serializing_if = "Option::is_none")]
35    pub ipsec_peer_ip: Option<String>,
36    #[serde(default, skip_serializing_if = "Option::is_none")]
37    pub ipsec_dynamic_routing: Option<bool>,
38    #[serde(default, skip_serializing_if = "Option::is_none")]
39    pub ipsec_separate_ikev2_networks: Option<bool>,
40    #[serde(default, skip_serializing_if = "Option::is_none")]
41    pub ipsec_tunnel_ip_enabled: Option<bool>,
42    #[serde(default, skip_serializing_if = "Option::is_none")]
43    pub ipsec_tunnel_ip: Option<String>,
44    #[serde(default, skip_serializing_if = "Option::is_none")]
45    pub ipsec_key_exchange: Option<String>,
46    #[serde(default, skip_serializing_if = "Option::is_none")]
47    pub ipsec_remote_identifier_enabled: Option<bool>,
48    #[serde(default, skip_serializing_if = "Option::is_none")]
49    pub ipsec_remote_identifier: Option<String>,
50    #[serde(default, skip_serializing_if = "Option::is_none")]
51    pub ipsec_local_identifier_enabled: Option<bool>,
52    #[serde(default, skip_serializing_if = "Option::is_none")]
53    pub ipsec_local_identifier: Option<String>,
54    #[serde(default, skip_serializing_if = "Option::is_none")]
55    pub ipsec_pfs: Option<bool>,
56    #[serde(default, skip_serializing_if = "Option::is_none")]
57    pub ipsec_ike_encryption: Option<String>,
58    #[serde(default, skip_serializing_if = "Option::is_none")]
59    pub ipsec_ike_hash: Option<String>,
60    #[serde(default, skip_serializing_if = "Option::is_none")]
61    pub ipsec_ike_dh_group: Option<u32>,
62    #[serde(default, skip_serializing_if = "Option::is_none")]
63    pub ipsec_dh_group: Option<u32>,
64    #[serde(default, skip_serializing_if = "Option::is_none")]
65    pub ipsec_ike_lifetime: Option<u32>,
66    #[serde(default, skip_serializing_if = "Option::is_none")]
67    pub ipsec_esp_encryption: Option<String>,
68    #[serde(default, skip_serializing_if = "Option::is_none")]
69    pub ipsec_esp_hash: Option<String>,
70    #[serde(default, skip_serializing_if = "Option::is_none")]
71    pub ipsec_esp_dh_group: Option<u32>,
72    #[serde(default, skip_serializing_if = "Option::is_none")]
73    pub ipsec_esp_lifetime: Option<u32>,
74    #[serde(default, skip_serializing_if = "Option::is_none")]
75    pub ipsec_interface: Option<String>,
76    #[serde(default, skip_serializing_if = "Option::is_none")]
77    pub ipsec_local_ip: Option<String>,
78    #[serde(default, skip_serializing_if = "Option::is_none")]
79    pub remote_vpn_dynamic_subnets_enabled: Option<bool>,
80    #[serde(default, skip_serializing_if = "Option::is_none")]
81    pub x_openvpn_shared_secret_key: Option<String>,
82    #[serde(default, skip_serializing_if = "Option::is_none")]
83    pub openvpn_local_address: Option<String>,
84    #[serde(default, skip_serializing_if = "Option::is_none")]
85    pub openvpn_local_port: Option<u16>,
86    #[serde(default, skip_serializing_if = "Option::is_none")]
87    pub openvpn_encryption_cipher: Option<String>,
88    #[serde(default, skip_serializing_if = "Option::is_none")]
89    pub openvpn_remote_host: Option<String>,
90    #[serde(default, skip_serializing_if = "Option::is_none")]
91    pub openvpn_remote_address: Option<String>,
92    #[serde(default, skip_serializing_if = "Option::is_none")]
93    pub openvpn_remote_port: Option<u16>,
94    #[serde(default, skip_serializing_if = "Option::is_none")]
95    pub openvpn_mode: Option<String>,
96    #[serde(default, skip_serializing_if = "Option::is_none")]
97    pub interface_mtu_enabled: Option<bool>,
98    #[serde(default, skip_serializing_if = "Option::is_none")]
99    pub interface_mtu: Option<u16>,
100    #[serde(default, skip_serializing_if = "Option::is_none")]
101    pub mss_clamp: Option<String>,
102    #[serde(default, skip_serializing_if = "Option::is_none")]
103    pub mss_clamp_mss: Option<u16>,
104    #[serde(default, skip_serializing_if = "Option::is_none")]
105    pub route_distance: Option<u32>,
106    #[serde(default, skip_serializing_if = "Vec::is_empty")]
107    pub remote_vpn_subnets: Vec<String>,
108    #[serde(default, flatten)]
109    pub extra: BTreeMap<String, Value>,
110}
111
112pub type UpdateSiteToSiteVpnRequest = CreateSiteToSiteVpnRequest;
113
114#[derive(Debug, Clone, Serialize, Deserialize)]
115pub struct CreateRemoteAccessVpnServerRequest {
116    pub name: String,
117    pub vpn_type: String,
118    #[serde(default = "default_remote_access_purpose")]
119    pub purpose: String,
120    #[serde(default = "default_enabled")]
121    pub enabled: bool,
122    #[serde(default, skip_serializing_if = "Option::is_none")]
123    pub setting_preference: Option<String>,
124    #[serde(default, skip_serializing_if = "Option::is_none")]
125    pub l2tp_allow_weak_ciphers: Option<bool>,
126    #[serde(default, skip_serializing_if = "Option::is_none")]
127    pub require_mschapv2: Option<bool>,
128    #[serde(default, skip_serializing_if = "Option::is_none")]
129    pub exposed_to_site_vpn: Option<bool>,
130    #[serde(default, skip_serializing_if = "Option::is_none")]
131    pub dhcpd_wins_enabled: Option<bool>,
132    #[serde(default, skip_serializing_if = "Option::is_none")]
133    pub dhcpd_wins_1: Option<String>,
134    #[serde(default, skip_serializing_if = "Option::is_none")]
135    pub dhcpd_wins_2: Option<String>,
136    #[serde(default, skip_serializing_if = "Option::is_none")]
137    pub dhcpd_dns_enabled: Option<bool>,
138    #[serde(default, skip_serializing_if = "Option::is_none")]
139    pub dhcpd_dns_1: Option<String>,
140    #[serde(default, skip_serializing_if = "Option::is_none")]
141    pub dhcpd_dns_2: Option<String>,
142    #[serde(default, skip_serializing_if = "Option::is_none")]
143    pub local_port: Option<u16>,
144    #[serde(default, skip_serializing_if = "Option::is_none")]
145    pub x_wireguard_private_key: Option<String>,
146    #[serde(default, skip_serializing_if = "Option::is_none")]
147    pub vpn_client_configuration_remote_ip_override_enabled: Option<bool>,
148    #[serde(default, skip_serializing_if = "Option::is_none")]
149    pub vpn_client_configuration_remote_ip_override: Option<String>,
150    #[serde(default, skip_serializing_if = "Option::is_none")]
151    pub x_ipsec_pre_shared_key: Option<String>,
152    #[serde(default, skip_serializing_if = "Option::is_none")]
153    pub radiusprofile_id: Option<String>,
154    #[serde(default, skip_serializing_if = "Option::is_none")]
155    pub ip_subnet: Option<String>,
156    #[serde(default, skip_serializing_if = "Option::is_none")]
157    pub ipv6_subnet: Option<String>,
158    #[serde(default, skip_serializing_if = "Option::is_none")]
159    pub dhcpd_start: Option<String>,
160    #[serde(default, skip_serializing_if = "Option::is_none")]
161    pub dhcpd_stop: Option<String>,
162    #[serde(default, skip_serializing_if = "Option::is_none")]
163    pub wireguard_interface: Option<String>,
164    #[serde(default, skip_serializing_if = "Option::is_none")]
165    pub wireguard_interface_binding_mode_ip_version: Option<String>,
166    #[serde(default, skip_serializing_if = "Option::is_none")]
167    pub l2tp_interface: Option<String>,
168    #[serde(default, skip_serializing_if = "Option::is_none")]
169    pub openvpn_interface: Option<String>,
170    #[serde(default, skip_serializing_if = "Option::is_none")]
171    pub wireguard_local_wan_ip: Option<String>,
172    #[serde(default, skip_serializing_if = "Option::is_none")]
173    pub l2tp_local_wan_ip: Option<String>,
174    #[serde(default, skip_serializing_if = "Option::is_none")]
175    pub openvpn_local_wan_ip: Option<String>,
176    #[serde(default, skip_serializing_if = "Option::is_none")]
177    pub vpn_binding_mode: Option<String>,
178    #[serde(default, skip_serializing_if = "Option::is_none")]
179    pub interface_mtu_enabled: Option<bool>,
180    #[serde(default, skip_serializing_if = "Option::is_none")]
181    pub interface_mtu: Option<u16>,
182    #[serde(default, skip_serializing_if = "Option::is_none")]
183    pub mss_clamp: Option<String>,
184    #[serde(default, skip_serializing_if = "Option::is_none")]
185    pub mss_clamp_mss: Option<u16>,
186    #[serde(default, skip_serializing_if = "Option::is_none")]
187    pub mss_clamp_ipv6: Option<String>,
188    #[serde(default, skip_serializing_if = "Option::is_none")]
189    pub mss_clamp_mss_ipv6: Option<u16>,
190    #[serde(default, flatten)]
191    pub extra: BTreeMap<String, Value>,
192}
193
194pub type UpdateRemoteAccessVpnServerRequest = CreateRemoteAccessVpnServerRequest;
195
196#[derive(Debug, Clone, Serialize, Deserialize)]
197pub struct CreateVpnClientProfileRequest {
198    pub name: String,
199    pub vpn_type: String,
200    #[serde(default = "default_vpn_client_purpose")]
201    pub purpose: String,
202    #[serde(default = "default_enabled")]
203    pub enabled: bool,
204    #[serde(default, flatten)]
205    pub extra: BTreeMap<String, Value>,
206}
207
208pub type UpdateVpnClientProfileRequest = CreateVpnClientProfileRequest;
209
210#[derive(Debug, Clone, Serialize, Deserialize)]
211pub struct CreateWireGuardPeerRequest {
212    pub name: String,
213    pub interface_ip: String,
214    #[serde(default, skip_serializing_if = "Option::is_none")]
215    pub interface_ipv6: Option<String>,
216    pub public_key: String,
217    #[serde(default, skip_serializing_if = "Vec::is_empty")]
218    pub allowed_ips: Vec<String>,
219    #[serde(default)]
220    pub preshared_key: String,
221    #[serde(default, skip_serializing_if = "Option::is_none")]
222    pub private_key: Option<String>,
223    #[serde(default, flatten)]
224    pub extra: BTreeMap<String, Value>,
225}
226
227pub type UpdateWireGuardPeerRequest = CreateWireGuardPeerRequest;
228
229#[cfg(test)]
230mod tests {
231    use super::{
232        CreateRemoteAccessVpnServerRequest, CreateSiteToSiteVpnRequest,
233        CreateVpnClientProfileRequest, CreateWireGuardPeerRequest,
234    };
235
236    #[test]
237    fn site_to_site_request_defaults_purpose_and_enabled() {
238        let request: CreateSiteToSiteVpnRequest = serde_json::from_value(serde_json::json!({
239            "name": "Branch Tunnel",
240            "vpn_type": "ipsec-vpn"
241        }))
242        .expect("request should deserialize");
243
244        assert_eq!(request.purpose, "site-vpn");
245        assert!(request.enabled);
246    }
247
248    #[test]
249    fn remote_access_request_defaults_purpose_and_enabled() {
250        let request: CreateRemoteAccessVpnServerRequest =
251            serde_json::from_value(serde_json::json!({
252                "name": "WireGuard Remote Access",
253                "vpn_type": "wireguard"
254            }))
255            .expect("request should deserialize");
256
257        assert_eq!(request.purpose, "remote-user-vpn");
258        assert!(request.enabled);
259    }
260
261    #[test]
262    fn vpn_client_profile_request_defaults_purpose_and_enabled() {
263        let request: CreateVpnClientProfileRequest = serde_json::from_value(serde_json::json!({
264            "name": "Branch Client",
265            "vpn_type": "openvpn-client"
266        }))
267        .expect("request should deserialize");
268
269        assert_eq!(request.purpose, "vpn-client");
270        assert!(request.enabled);
271    }
272
273    #[test]
274    fn wireguard_peer_request_preserves_empty_preshared_key() {
275        let request: CreateWireGuardPeerRequest = serde_json::from_value(serde_json::json!({
276            "name": "Laptop",
277            "interface_ip": "192.168.42.2",
278            "public_key": "pubkey",
279            "allowed_ips": []
280        }))
281        .expect("request should deserialize");
282
283        assert_eq!(request.preshared_key, "");
284        assert!(request.allowed_ips.is_empty());
285        assert!(request.private_key.is_none());
286    }
287}