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