Skip to main content

unifly_api/convert/
firewall.rs

1use crate::integration_types;
2use crate::model::common::DataSource;
3use crate::model::entity_id::EntityId;
4use crate::model::firewall::{
5    AclAction, AclRule, AclRuleType, FirewallAction, FirewallPolicy, FirewallZone, IpSpec,
6    PolicyEndpoint, PortSpec, TrafficFilter,
7};
8
9use super::helpers::origin_from_metadata;
10
11// ── Firewall Policy ──────────────────────────────────────────────
12
13impl From<integration_types::FirewallPolicyResponse> for FirewallPolicy {
14    fn from(p: integration_types::FirewallPolicyResponse) -> Self {
15        let action = p.action.get("type").and_then(|v| v.as_str()).map_or(
16            FirewallAction::Block,
17            |a| match a {
18                "ALLOW" => FirewallAction::Allow,
19                "REJECT" => FirewallAction::Reject,
20                _ => FirewallAction::Block,
21            },
22        );
23
24        #[allow(clippy::as_conversions, clippy::cast_possible_truncation)]
25        let index = p
26            .extra
27            .get("index")
28            .and_then(serde_json::Value::as_i64)
29            .map(|i| i as i32);
30
31        let source_endpoint =
32            convert_policy_endpoint(p.source.as_ref(), p.extra.get("sourceFirewallZoneId"));
33        let destination_endpoint = convert_dest_policy_endpoint(
34            p.destination.as_ref(),
35            p.extra.get("destinationFirewallZoneId"),
36        );
37
38        let source_summary = source_endpoint.filter.as_ref().map(TrafficFilter::summary);
39        let destination_summary = destination_endpoint
40            .filter
41            .as_ref()
42            .map(TrafficFilter::summary);
43
44        let ip_version = p
45            .ip_protocol_scope
46            .as_ref()
47            .and_then(|v| v.get("ipVersion"))
48            .and_then(|v| v.as_str())
49            .map_or(crate::model::firewall::IpVersion::Both, |s| match s {
50                "IPV4_ONLY" | "IPV4" => crate::model::firewall::IpVersion::Ipv4,
51                "IPV6_ONLY" | "IPV6" => crate::model::firewall::IpVersion::Ipv6,
52                _ => crate::model::firewall::IpVersion::Both,
53            });
54
55        let ipsec_mode = p
56            .extra
57            .get("ipsecFilter")
58            .and_then(|v| v.as_str())
59            .map(String::from);
60
61        let connection_states = p
62            .extra
63            .get("connectionStateFilter")
64            .and_then(|v| v.as_array())
65            .map(|arr| {
66                arr.iter()
67                    .filter_map(|v| v.as_str().map(String::from))
68                    .collect()
69            })
70            .unwrap_or_default();
71
72        FirewallPolicy {
73            id: EntityId::Uuid(p.id),
74            name: p.name,
75            description: p.description,
76            enabled: p.enabled,
77            index,
78            action,
79            ip_version,
80            source: source_endpoint,
81            destination: destination_endpoint,
82            source_summary,
83            destination_summary,
84            protocol_summary: None,
85            schedule: None,
86            ipsec_mode,
87            connection_states,
88            logging_enabled: p.logging_enabled,
89            origin: p.metadata.as_ref().and_then(origin_from_metadata),
90            data_source: DataSource::IntegrationApi,
91        }
92    }
93}
94
95fn convert_policy_endpoint(
96    endpoint: Option<&integration_types::FirewallPolicySource>,
97    flat_zone_id: Option<&serde_json::Value>,
98) -> PolicyEndpoint {
99    if let Some(ep) = endpoint {
100        PolicyEndpoint {
101            zone_id: ep.zone_id.map(EntityId::Uuid),
102            filter: ep
103                .traffic_filter
104                .as_ref()
105                .map(convert_source_traffic_filter),
106        }
107    } else {
108        let zone_id = flat_zone_id
109            .and_then(|v| v.as_str())
110            .and_then(|s| uuid::Uuid::parse_str(s).ok())
111            .map(EntityId::Uuid);
112        PolicyEndpoint {
113            zone_id,
114            filter: None,
115        }
116    }
117}
118
119fn convert_dest_policy_endpoint(
120    endpoint: Option<&integration_types::FirewallPolicyDestination>,
121    flat_zone_id: Option<&serde_json::Value>,
122) -> PolicyEndpoint {
123    if let Some(ep) = endpoint {
124        PolicyEndpoint {
125            zone_id: ep.zone_id.map(EntityId::Uuid),
126            filter: ep.traffic_filter.as_ref().map(convert_dest_traffic_filter),
127        }
128    } else {
129        let zone_id = flat_zone_id
130            .and_then(|v| v.as_str())
131            .and_then(|s| uuid::Uuid::parse_str(s).ok())
132            .map(EntityId::Uuid);
133        PolicyEndpoint {
134            zone_id,
135            filter: None,
136        }
137    }
138}
139
140fn convert_source_traffic_filter(f: &integration_types::SourceTrafficFilter) -> TrafficFilter {
141    use integration_types::SourceTrafficFilter as S;
142    match f {
143        S::Network {
144            network_filter,
145            mac_address_filter,
146            port_filter,
147        } => TrafficFilter::Network {
148            network_ids: network_filter
149                .network_ids
150                .iter()
151                .copied()
152                .map(EntityId::Uuid)
153                .collect(),
154            match_opposite: network_filter.match_opposite,
155            mac_addresses: mac_address_filter
156                .as_ref()
157                .map(|m| m.mac_addresses.clone())
158                .unwrap_or_default(),
159            ports: port_filter.as_ref().map(convert_port_filter),
160        },
161        S::IpAddress {
162            ip_address_filter,
163            mac_address_filter,
164            port_filter,
165        } => TrafficFilter::IpAddress {
166            addresses: convert_ip_address_filter(ip_address_filter),
167            match_opposite: ip_filter_match_opposite(ip_address_filter),
168            mac_addresses: mac_address_filter
169                .as_ref()
170                .map(|m| m.mac_addresses.clone())
171                .unwrap_or_default(),
172            ports: port_filter.as_ref().map(convert_port_filter),
173        },
174        S::MacAddress {
175            mac_address_filter,
176            port_filter,
177        } => TrafficFilter::MacAddress {
178            mac_addresses: mac_address_filter.mac_addresses.clone(),
179            ports: port_filter.as_ref().map(convert_port_filter),
180        },
181        S::Port { port_filter } => TrafficFilter::Port {
182            ports: convert_port_filter(port_filter),
183        },
184        S::Region {
185            region_filter,
186            port_filter,
187        } => TrafficFilter::Region {
188            regions: region_filter.regions.clone(),
189            ports: port_filter.as_ref().map(convert_port_filter),
190        },
191        S::Unknown => TrafficFilter::Other {
192            raw_type: "UNKNOWN".into(),
193        },
194    }
195}
196
197fn convert_dest_traffic_filter(f: &integration_types::DestTrafficFilter) -> TrafficFilter {
198    use integration_types::DestTrafficFilter as D;
199    match f {
200        D::Network {
201            network_filter,
202            port_filter,
203        } => TrafficFilter::Network {
204            network_ids: network_filter
205                .network_ids
206                .iter()
207                .copied()
208                .map(EntityId::Uuid)
209                .collect(),
210            match_opposite: network_filter.match_opposite,
211            mac_addresses: Vec::new(),
212            ports: port_filter.as_ref().map(convert_port_filter),
213        },
214        D::IpAddress {
215            ip_address_filter,
216            port_filter,
217        } => TrafficFilter::IpAddress {
218            addresses: convert_ip_address_filter(ip_address_filter),
219            match_opposite: ip_filter_match_opposite(ip_address_filter),
220            mac_addresses: Vec::new(),
221            ports: port_filter.as_ref().map(convert_port_filter),
222        },
223        D::Port { port_filter } => TrafficFilter::Port {
224            ports: convert_port_filter(port_filter),
225        },
226        D::Region {
227            region_filter,
228            port_filter,
229        } => TrafficFilter::Region {
230            regions: region_filter.regions.clone(),
231            ports: port_filter.as_ref().map(convert_port_filter),
232        },
233        D::Application {
234            application_filter,
235            port_filter,
236        } => TrafficFilter::Application {
237            application_ids: application_filter.application_ids.clone(),
238            ports: port_filter.as_ref().map(convert_port_filter),
239        },
240        D::ApplicationCategory {
241            application_category_filter,
242            port_filter,
243        } => TrafficFilter::ApplicationCategory {
244            category_ids: application_category_filter.application_category_ids.clone(),
245            ports: port_filter.as_ref().map(convert_port_filter),
246        },
247        D::Domain {
248            domain_filter,
249            port_filter,
250        } => {
251            let domains = match domain_filter {
252                integration_types::DomainFilter::Specific { domains } => domains.clone(),
253                integration_types::DomainFilter::Unknown => Vec::new(),
254            };
255            TrafficFilter::Domain {
256                domains,
257                ports: port_filter.as_ref().map(convert_port_filter),
258            }
259        }
260        D::Unknown => TrafficFilter::Other {
261            raw_type: "UNKNOWN".into(),
262        },
263    }
264}
265
266fn convert_port_filter(pf: &integration_types::PortFilter) -> PortSpec {
267    match pf {
268        integration_types::PortFilter::Ports {
269            items,
270            match_opposite,
271        } => PortSpec::Values {
272            items: items
273                .iter()
274                .map(|item| match item {
275                    integration_types::PortItem::Number { value } => value.clone(),
276                    integration_types::PortItem::Range {
277                        start_port,
278                        end_port,
279                    } => format!("{start_port}-{end_port}"),
280                    integration_types::PortItem::Unknown => "?".into(),
281                })
282                .collect(),
283            match_opposite: *match_opposite,
284        },
285        integration_types::PortFilter::TrafficMatchingList {
286            traffic_matching_list_id,
287            match_opposite,
288        } => PortSpec::MatchingList {
289            list_id: EntityId::Uuid(*traffic_matching_list_id),
290            match_opposite: *match_opposite,
291        },
292        integration_types::PortFilter::Unknown => PortSpec::Values {
293            items: Vec::new(),
294            match_opposite: false,
295        },
296    }
297}
298
299fn convert_ip_address_filter(f: &integration_types::IpAddressFilter) -> Vec<IpSpec> {
300    match f {
301        integration_types::IpAddressFilter::Specific { items, .. } => items
302            .iter()
303            .map(|item| match item {
304                integration_types::IpAddressItem::Address { value } => IpSpec::Address {
305                    value: value.clone(),
306                },
307                integration_types::IpAddressItem::Range { start, stop } => IpSpec::Range {
308                    start: start.clone(),
309                    stop: stop.clone(),
310                },
311                integration_types::IpAddressItem::Subnet { value } => IpSpec::Subnet {
312                    value: value.clone(),
313                },
314            })
315            .collect(),
316        integration_types::IpAddressFilter::TrafficMatchingList {
317            traffic_matching_list_id,
318            ..
319        } => vec![IpSpec::MatchingList {
320            list_id: EntityId::Uuid(*traffic_matching_list_id),
321        }],
322        integration_types::IpAddressFilter::Unknown => Vec::new(),
323    }
324}
325
326fn ip_filter_match_opposite(f: &integration_types::IpAddressFilter) -> bool {
327    match f {
328        integration_types::IpAddressFilter::Specific { match_opposite, .. }
329        | integration_types::IpAddressFilter::TrafficMatchingList { match_opposite, .. } => {
330            *match_opposite
331        }
332        integration_types::IpAddressFilter::Unknown => false,
333    }
334}
335
336// ── Firewall Zone ────────────────────────────────────────────────
337
338impl From<integration_types::FirewallZoneResponse> for FirewallZone {
339    fn from(z: integration_types::FirewallZoneResponse) -> Self {
340        FirewallZone {
341            id: EntityId::Uuid(z.id),
342            name: z.name,
343            network_ids: z.network_ids.into_iter().map(EntityId::Uuid).collect(),
344            origin: origin_from_metadata(&z.metadata),
345            source: DataSource::IntegrationApi,
346        }
347    }
348}
349
350// ── ACL Rule ─────────────────────────────────────────────────────
351
352impl From<integration_types::AclRuleResponse> for AclRule {
353    fn from(r: integration_types::AclRuleResponse) -> Self {
354        let rule_type = match r.rule_type.as_str() {
355            "MAC" => AclRuleType::Mac,
356            _ => AclRuleType::Ipv4,
357        };
358
359        let action = match r.action.as_str() {
360            "ALLOW" => AclAction::Allow,
361            _ => AclAction::Block,
362        };
363
364        AclRule {
365            id: EntityId::Uuid(r.id),
366            name: r.name,
367            enabled: r.enabled,
368            rule_type,
369            action,
370            source_summary: None,
371            destination_summary: None,
372            origin: origin_from_metadata(&r.metadata),
373            source: DataSource::IntegrationApi,
374        }
375    }
376}