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 && self
84 .dns_suffixes
85 .iter()
86 .any(|suffix| query.to_ascii_lowercase().ends_with(suffix))
87 {
88 return true;
89 }
90
91 if let Some(sni) = input.tls_sni
92 && self
93 .tls_sni_suffixes
94 .iter()
95 .any(|suffix| sni.to_ascii_lowercase().ends_with(suffix))
96 {
97 return true;
98 }
99
100 if let Some(host) = input.http_host
101 && self
102 .http_hosts
103 .iter()
104 .any(|expected| host.eq_ignore_ascii_case(expected))
105 {
106 return true;
107 }
108
109 false
110 }
111}
112
113impl ApplicationObj {
114 pub fn matches(&self, input: &ApplicationMatchInput<'_>) -> bool {
116 let dns_query = input.dns_query.map(|v| v.to_ascii_lowercase());
117 if let Some(ref query) = dns_query
118 && self
119 .dns_suffixes
120 .iter()
121 .any(|suffix| query.ends_with(suffix))
122 {
123 return true;
124 }
125
126 let tls_sni = input.tls_sni.map(|v| v.to_ascii_lowercase());
127 if let Some(ref sni) = tls_sni
128 && self
129 .tls_sni_suffixes
130 .iter()
131 .any(|suffix| sni.ends_with(suffix))
132 {
133 return true;
134 }
135
136 if let Some(host) = input.http_host
137 && self
138 .http_hosts
139 .iter()
140 .any(|expected| host.eq_ignore_ascii_case(expected))
141 {
142 return true;
143 }
144
145 false
146 }
147}
148
149impl<'a> From<ApplicationDefinition<'a>> for ApplicationObj {
150 fn from(def: ApplicationDefinition<'a>) -> Self {
151 Self {
152 name: def.name.to_string(),
153 category: def.category.to_string(),
154 transports: def.transports.to_vec(),
155 dns_suffixes: def
156 .indicators
157 .dns_suffixes
158 .iter()
159 .map(|s| s.to_string())
160 .collect(),
161 tls_sni_suffixes: def
162 .indicators
163 .tls_sni_suffixes
164 .iter()
165 .map(|s| s.to_string())
166 .collect(),
167 http_hosts: def
168 .indicators
169 .http_hosts
170 .iter()
171 .map(|s| s.to_string())
172 .collect(),
173 }
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180
181 #[test]
182 fn matches_dns_and_http() {
183 let app = ApplicationDefinition {
184 name: "demo",
185 category: "qa",
186 transports: &[TransportService::tcp(443)],
187 indicators: ApplicationIndicators {
188 dns_suffixes: &[".demo.local"],
189 tls_sni_suffixes: &[".demo.local"],
190 http_hosts: &["demo.local"],
191 },
192 };
193
194 let dns = ApplicationMatchInput {
195 dns_query: Some("api.demo.local"),
196 ..Default::default()
197 };
198 assert!(app.matches(&dns));
199
200 let http = ApplicationMatchInput {
201 http_host: Some("Demo.Local"),
202 ..Default::default()
203 };
204 assert!(app.matches(&http));
205 }
206
207 #[test]
208 fn owned_application_matches_metadata() {
209 let def = ApplicationDefinition {
210 name: "owned",
211 category: "sample",
212 transports: &[TransportService::tcp(8443)],
213 indicators: ApplicationIndicators {
214 dns_suffixes: &[".owned.local"],
215 tls_sni_suffixes: &[],
216 http_hosts: &["owned.local"],
217 },
218 };
219 let obj: ApplicationObj = def.into();
220 let input = ApplicationMatchInput {
221 http_host: Some("owned.local"),
222 ..Default::default()
223 };
224 assert!(obj.matches(&input));
225 }
226}