firewall_objects/service/application/
mod.rs1pub mod catalog;
11
12use crate::service::TransportService;
13
14#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub struct ApplicationIndicators<'a> {
17 pub dns_suffixes: &'a [&'a str],
18 pub tls_sni_suffixes: &'a [&'a str],
19 pub http_hosts: &'a [&'a str],
20}
21
22#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub struct ApplicationDefinition<'a> {
25 pub name: &'a str,
26 pub category: &'a str,
27 pub transports: &'a [TransportService],
28 pub indicators: ApplicationIndicators<'a>,
29}
30
31#[derive(Default)]
32pub struct ApplicationMatchInput<'a> {
33 pub dns_query: Option<&'a str>,
34 pub tls_sni: Option<&'a str>,
35 pub http_host: Option<&'a str>,
36}
37
38#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
40#[derive(Debug, Clone, PartialEq)]
41pub struct ApplicationObj {
42 pub name: String,
43 pub category: String,
44 pub transports: Vec<TransportService>,
45 pub dns_suffixes: Vec<String>,
46 pub tls_sni_suffixes: Vec<String>,
47 pub http_hosts: Vec<String>,
48}
49
50impl<'a> ApplicationDefinition<'a> {
51 pub fn matches(&self, input: &ApplicationMatchInput<'_>) -> bool {
78 self.indicators.matches(input)
79 }
80}
81
82impl<'a> ApplicationIndicators<'a> {
83 pub fn matches(&self, input: &ApplicationMatchInput<'_>) -> bool {
84 if let Some(query) = input.dns_query {
85 if self
86 .dns_suffixes
87 .iter()
88 .any(|suffix| query.to_ascii_lowercase().ends_with(suffix))
89 {
90 return true;
91 }
92 }
93
94 if let Some(sni) = input.tls_sni {
95 if self
96 .tls_sni_suffixes
97 .iter()
98 .any(|suffix| sni.to_ascii_lowercase().ends_with(suffix))
99 {
100 return true;
101 }
102 }
103
104 if let Some(host) = input.http_host {
105 if self
106 .http_hosts
107 .iter()
108 .any(|expected| host.eq_ignore_ascii_case(expected))
109 {
110 return true;
111 }
112 }
113
114 false
115 }
116}
117
118impl ApplicationObj {
119 pub fn matches(&self, input: &ApplicationMatchInput<'_>) -> bool {
121 let dns_query = input.dns_query.map(|v| v.to_ascii_lowercase());
122 if let Some(ref query) = dns_query {
123 if self
124 .dns_suffixes
125 .iter()
126 .any(|suffix| query.ends_with(suffix))
127 {
128 return true;
129 }
130 }
131
132 let tls_sni = input.tls_sni.map(|v| v.to_ascii_lowercase());
133 if let Some(ref sni) = tls_sni {
134 if self
135 .tls_sni_suffixes
136 .iter()
137 .any(|suffix| sni.ends_with(suffix))
138 {
139 return true;
140 }
141 }
142
143 if let Some(host) = input.http_host {
144 if self
145 .http_hosts
146 .iter()
147 .any(|expected| host.eq_ignore_ascii_case(expected))
148 {
149 return true;
150 }
151 }
152
153 false
154 }
155}
156
157impl<'a> From<ApplicationDefinition<'a>> for ApplicationObj {
158 fn from(def: ApplicationDefinition<'a>) -> Self {
159 Self {
160 name: def.name.to_string(),
161 category: def.category.to_string(),
162 transports: def.transports.to_vec(),
163 dns_suffixes: def
164 .indicators
165 .dns_suffixes
166 .iter()
167 .map(|s| s.to_string())
168 .collect(),
169 tls_sni_suffixes: def
170 .indicators
171 .tls_sni_suffixes
172 .iter()
173 .map(|s| s.to_string())
174 .collect(),
175 http_hosts: def
176 .indicators
177 .http_hosts
178 .iter()
179 .map(|s| s.to_string())
180 .collect(),
181 }
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188
189 #[test]
190 fn matches_dns_and_http() {
191 let app = ApplicationDefinition {
192 name: "demo",
193 category: "qa",
194 transports: &[TransportService::tcp(443)],
195 indicators: ApplicationIndicators {
196 dns_suffixes: &[".demo.local"],
197 tls_sni_suffixes: &[".demo.local"],
198 http_hosts: &["demo.local"],
199 },
200 };
201
202 let dns = ApplicationMatchInput {
203 dns_query: Some("api.demo.local"),
204 ..Default::default()
205 };
206 assert!(app.matches(&dns));
207
208 let http = ApplicationMatchInput {
209 http_host: Some("Demo.Local"),
210 ..Default::default()
211 };
212 assert!(app.matches(&http));
213 }
214
215 #[test]
216 fn owned_application_matches_metadata() {
217 let def = ApplicationDefinition {
218 name: "owned",
219 category: "sample",
220 transports: &[TransportService::tcp(8443)],
221 indicators: ApplicationIndicators {
222 dns_suffixes: &[".owned.local"],
223 tls_sni_suffixes: &[],
224 http_hosts: &["owned.local"],
225 },
226 };
227 let obj: ApplicationObj = def.into();
228 let input = ApplicationMatchInput {
229 http_host: Some("owned.local"),
230 ..Default::default()
231 };
232 assert!(obj.matches(&input));
233 }
234}