1use serde::{Deserialize, Serialize};
4
5use super::common::{DataSource, EntityOrigin};
6use super::entity_id::EntityId;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
9pub enum FirewallAction {
10 Allow,
11 Block,
12 Reject,
13}
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
16pub enum IpVersion {
17 Ipv4,
18 Ipv6,
19 Both,
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct FirewallZone {
25 pub id: EntityId,
26 pub name: String,
27 pub network_ids: Vec<EntityId>,
28 pub origin: Option<EntityOrigin>,
29
30 #[serde(skip)]
31 #[allow(dead_code)]
32 pub(crate) source: DataSource,
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct PolicyEndpoint {
40 pub zone_id: Option<EntityId>,
41 pub filter: Option<TrafficFilter>,
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
46#[serde(tag = "kind", rename_all = "snake_case")]
47pub enum TrafficFilter {
48 Network {
49 network_ids: Vec<EntityId>,
50 match_opposite: bool,
51 #[serde(default, skip_serializing_if = "Vec::is_empty")]
52 mac_addresses: Vec<String>,
53 #[serde(default, skip_serializing_if = "Option::is_none")]
54 ports: Option<PortSpec>,
55 },
56 IpAddress {
57 addresses: Vec<IpSpec>,
58 match_opposite: bool,
59 #[serde(default, skip_serializing_if = "Vec::is_empty")]
60 mac_addresses: Vec<String>,
61 #[serde(default, skip_serializing_if = "Option::is_none")]
62 ports: Option<PortSpec>,
63 },
64 MacAddress {
65 mac_addresses: Vec<String>,
66 #[serde(default, skip_serializing_if = "Option::is_none")]
67 ports: Option<PortSpec>,
68 },
69 Port {
70 ports: PortSpec,
71 },
72 Region {
73 regions: Vec<String>,
74 #[serde(default, skip_serializing_if = "Option::is_none")]
75 ports: Option<PortSpec>,
76 },
77 Application {
78 application_ids: Vec<i64>,
79 #[serde(default, skip_serializing_if = "Option::is_none")]
80 ports: Option<PortSpec>,
81 },
82 ApplicationCategory {
83 category_ids: Vec<i64>,
84 #[serde(default, skip_serializing_if = "Option::is_none")]
85 ports: Option<PortSpec>,
86 },
87 Domain {
88 domains: Vec<String>,
89 #[serde(default, skip_serializing_if = "Option::is_none")]
90 ports: Option<PortSpec>,
91 },
92 Other {
94 raw_type: String,
95 },
96}
97
98#[derive(Debug, Clone, Serialize, Deserialize)]
100#[serde(tag = "kind", rename_all = "snake_case")]
101pub enum PortSpec {
102 Values {
103 items: Vec<String>,
104 match_opposite: bool,
105 },
106 MatchingList {
107 list_id: EntityId,
108 match_opposite: bool,
109 },
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize)]
114#[serde(tag = "kind", rename_all = "snake_case")]
115pub enum IpSpec {
116 Address { value: String },
117 Range { start: String, stop: String },
118 Subnet { value: String },
119 MatchingList { list_id: EntityId },
120}
121
122impl TrafficFilter {
123 pub fn summary(&self) -> String {
125 match self {
126 Self::Network {
127 network_ids,
128 match_opposite,
129 ..
130 } => {
131 let prefix = if *match_opposite { "NOT " } else { "" };
132 format!("{prefix}net({} networks)", network_ids.len())
133 }
134 Self::IpAddress {
135 addresses,
136 match_opposite,
137 ..
138 } => {
139 let prefix = if *match_opposite { "NOT " } else { "" };
140 let items: Vec<String> = addresses
141 .iter()
142 .map(|a| match a {
143 IpSpec::Address { value } | IpSpec::Subnet { value } => value.clone(),
144 IpSpec::Range { start, stop } => format!("{start}-{stop}"),
145 IpSpec::MatchingList { list_id } => format!("list:{list_id}"),
146 })
147 .collect();
148 let display = if items.len() <= 2 {
149 items.join(", ")
150 } else {
151 format!("{}, {} +{} more", items[0], items[1], items.len() - 2)
152 };
153 format!("{prefix}ip({display})")
154 }
155 Self::MacAddress { mac_addresses, .. } => {
156 format!("mac({})", mac_addresses.len())
157 }
158 Self::Port { ports } => summarize_ports(ports),
159 Self::Region { regions, .. } => format!("region({})", regions.join(",")),
160 Self::Application {
161 application_ids, ..
162 } => {
163 format!("app({} apps)", application_ids.len())
164 }
165 Self::ApplicationCategory { category_ids, .. } => {
166 format!("cat({} categories)", category_ids.len())
167 }
168 Self::Domain { domains, .. } => {
169 if domains.len() <= 2 {
170 format!("domain({})", domains.join(", "))
171 } else {
172 format!("domain({} +{} more)", domains[0], domains.len() - 1)
173 }
174 }
175 Self::Other { raw_type } => format!("({raw_type})"),
176 }
177 }
178}
179
180fn summarize_ports(spec: &PortSpec) -> String {
181 match spec {
182 PortSpec::Values {
183 items,
184 match_opposite,
185 } => {
186 let prefix = if *match_opposite { "NOT " } else { "" };
187 format!("{prefix}port({})", items.join(","))
188 }
189 PortSpec::MatchingList {
190 list_id,
191 match_opposite,
192 } => {
193 let prefix = if *match_opposite { "NOT " } else { "" };
194 format!("{prefix}port(list:{list_id})")
195 }
196 }
197}
198
199#[derive(Debug, Clone, Serialize, Deserialize)]
201pub struct FirewallPolicy {
202 pub id: EntityId,
203 pub name: String,
204 pub description: Option<String>,
205 pub enabled: bool,
206 pub index: Option<i32>,
207
208 pub action: FirewallAction,
209 pub ip_version: IpVersion,
210
211 pub source: PolicyEndpoint,
213 pub destination: PolicyEndpoint,
214
215 pub source_summary: Option<String>,
217 pub destination_summary: Option<String>,
218
219 pub protocol_summary: Option<String>,
221 pub schedule: Option<String>,
222 pub ipsec_mode: Option<String>,
223
224 pub connection_states: Vec<String>,
225 pub logging_enabled: bool,
226
227 pub origin: Option<EntityOrigin>,
228
229 #[serde(skip)]
230 #[allow(dead_code)]
231 pub(crate) data_source: DataSource,
232}
233
234#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
236pub enum AclAction {
237 Allow,
238 Block,
239}
240
241#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
243pub enum AclRuleType {
244 Ipv4,
245 Mac,
246}
247
248#[derive(Debug, Clone, Serialize, Deserialize)]
250pub struct AclRule {
251 pub id: EntityId,
252 pub name: String,
253 pub enabled: bool,
254 pub rule_type: AclRuleType,
255 pub action: AclAction,
256 pub source_summary: Option<String>,
257 pub destination_summary: Option<String>,
258 pub origin: Option<EntityOrigin>,
259
260 #[serde(skip)]
261 #[allow(dead_code)]
262 pub(crate) source: DataSource,
263}