assemblyline_markings/
config.rs

1//! Objects for parsing configuration data from assemblyline.
2
3use std::collections::HashMap;
4
5use serde::{Deserialize, Serialize};
6
7use crate::errors::Errors;
8
9/// This the default classification engine provided with Assemblyline,
10/// it showcases all the different features of the classification engine
11/// while providing a useful configuration
12pub static DEFAULT_CLASSIFICATION_DATA: &str = r"
13# Turn on/off classification enforcement. When this flag is off, this
14#  completely disables the classification engine, any documents added while
15#  the classification engine is off gets the default unrestricted value
16enforce: false
17
18# Turn on/off dynamic group creation. This feature allow you to dynamically create classification groups based on
19#  features from the user.
20dynamic_groups: false
21
22# Set the type of dynamic groups to be used
23#  email: groups will be based of the user's email domain
24#  group: groups will be created out the the user's group values
25#  all: groups will be created out of both the email domain and the group values
26dynamic_groups_type: email
27
28# List of Classification level:
29#   Graded list were a smaller number is less restricted then an higher number.
30levels:
31  # List of alternate names for the current marking
32  - aliases:
33      - UNRESTRICTED
34      - UNCLASSIFIED
35      - U
36      - TLP:W
37      - TLP:WHITE
38    # Stylesheet applied in the UI for the different levels
39    css:
40      # Name of the color scheme used for display (default, primary, secondary, success, info, warning, error)
41      color: default
42    # Description of the classification level
43    description: Subject to standard copyright rules, TLP:CLEAR information may be distributed without restriction.
44    # Interger value of the Classification level (higher is more classified)
45    lvl: 100
46    # Long name of the classification item
47    name: TLP:CLEAR
48    # Short name of the classification item
49    short_name: TLP:C
50  - aliases: []
51    css:
52      color: success
53    description:
54      Recipients may share TLP:GREEN information with peers and partner organizations
55      within their sector or community, but not via publicly accessible channels. Information
56      in this category can be circulated widely within a particular community. TLP:GREEN
57      information may not be released outside of the community.
58    lvl: 110
59    name: TLP:GREEN
60    short_name: TLP:G
61  - aliases: []
62    css:
63      color: warning
64    description:
65      Recipients may only share TLP:AMBER information with members of their
66      own organization and with clients or customers who need to know the information
67      to protect themselves or prevent further harm.
68    lvl: 120
69    name: TLP:AMBER
70    short_name: TLP:A
71  - aliases:
72      - RESTRICTED
73    css:
74      color: warning
75    description:
76      Recipients may only share TLP:AMBER+STRICT information with members of their
77      own organization.
78    lvl: 125
79    name: TLP:AMBER+STRICT
80    short_name: TLP:A+S
81
82# List of required tokens:
83#   A user requesting access to an item must have all the
84#   required tokens the item has to gain access to it
85required:
86  - aliases: []
87    description: Produced using a commercial tool with limited distribution
88    name: COMMERCIAL
89    short_name: CMR
90    # The minimum classification level an item must have
91    #   for this token to be valid. (optional)
92    # require_lvl: 100
93    # This is a token that is required but will display in the groups part
94    #   of the classification string. (optional)
95    # is_required_group: true
96
97# List of groups:
98#   A user requesting access to an item must be part of a least
99#   of one the group the item is part of to gain access
100groups:
101  - aliases: []
102    # This is a special flag that when set to true, if any groups are selected
103    #   in a classification. This group will automatically be selected too. (optional)
104    auto_select: true
105    description: Employees of CSE
106    name: CSE
107    short_name: CSE
108    # Assuming that this groups is the only group selected, this is the display name
109    #   that will be used in the classification (that values has to be in the aliases
110    #   of this group and only this group) (optional)
111    # solitary_display_name: ANY
112
113# List of subgroups:
114#   A user requesting access to an item must be part of a least
115#   of one the subgroup the item is part of to gain access
116subgroups:
117  - aliases: []
118    description: Member of Incident Response team
119    name: IR TEAM
120    short_name: IR
121  - aliases: []
122    description: Member of the Canadian Centre for Cyber Security
123    # This is a special flag that auto-select the corresponding group
124    #   when this subgroup is selected (optional)
125    require_group: CSE
126    name: CCCS
127    short_name: CCCS
128    # This is a special flag that makes sure that none other then the
129    #   corresponding group is selected when this subgroup is selected (optional)
130    # limited_to_group: CSE
131
132# Default restricted classification
133restricted: TLP:A+S//CMR
134
135# Default unrestricted classification:
136#   When no classification are provided or that the classification engine is
137#   disabled, this is the classification value each items will get
138unrestricted: TLP:C";
139
140/// parse classification config applying asssemblyline's defaults
141pub fn ready_classification(config_data: Option<&str>) -> Result<ClassificationConfig, Errors> {
142    if let Some(config_data) = config_data {
143        // Load modifiers from the yaml config
144        let yml_data: serde_yaml::mapping::Mapping = serde_yaml::from_str(config_data)?;
145        let mut config: serde_yaml::Value = serde_yaml::from_str(DEFAULT_CLASSIFICATION_DATA)?;
146        config.as_mapping_mut().unwrap().extend(yml_data);
147        Ok(serde_yaml::from_value(config)?)
148    } else {
149        Ok(serde_yaml::from_str(DEFAULT_CLASSIFICATION_DATA)?)
150    }
151}
152
153
154/// Define sources for dynamic groups
155#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
156#[serde(rename_all="lowercase")]
157pub enum DynamicGroupType {
158    /// Draw classification groups from user email domains
159    #[default]
160    Email,
161    /// Draw classification groups from user ldap groups
162    Group,
163    /// Draw classification groups from user email domains and ldap groups
164    All,
165}
166
167
168/// A description of the configuration block used by assemblyline for classification schemes
169#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
170#[serde(deny_unknown_fields)]
171pub struct ClassificationConfig {
172    /// Turn on/off classification enforcement. When this flag is off, this
173    /// completely disables the classification engine, any documents added while
174    /// the classification engine is off gets the default unrestricted value
175    pub enforce: bool,
176
177    /// Turn on/off dynamic group creation. This feature allow you to dynamically create classification groups based on
178    ///  features from the user.
179    pub dynamic_groups: bool,
180
181    /// Set the type of dynamic groups to be used
182    #[serde(default)]
183    pub dynamic_groups_type: DynamicGroupType,
184
185    /// List of Classification level.
186    /// Graded list were a smaller number is less restricted then an higher number.
187    pub levels: Vec<ClassificationLevel>,
188
189    /// List of required tokens:
190    /// A user requesting access to an item must have all the
191    /// required tokens the item has to gain access to it
192    pub required: Vec<ClassificationMarking>,
193
194    /// List of groups:
195    /// A user requesting access to an item must be part of a least
196    /// of one the group the item is part of to gain access
197    pub groups: Vec<ClassificationGroup>,
198
199    /// List of subgroups:
200    /// A user requesting access to an item must be part of a least
201    /// of one the subgroup the item is part of to gain access
202    pub subgroups: Vec<ClassificationSubGroup>,
203
204    /// Default restricted classification
205    pub restricted: String,
206
207    /// Default unrestricted classification.
208    /// When no classification are provided or that the classification engine is
209    /// disabled, this is the classification value each items will get
210    pub unrestricted: String,
211}
212
213/// A category of data delineating access
214#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
215#[serde(deny_unknown_fields)]
216pub struct ClassificationLevel {
217    /// List of alternate names for the current marking
218    #[serde(default)]
219    pub aliases: Vec<NameString>,
220
221    /// Stylesheet applied in the UI for the different levels
222    #[serde(default="default_css")]
223    pub css: HashMap<String, String>,
224
225    /// Description of the classification level
226    #[serde(default="default_description")]
227    pub description: String,
228
229    /// Interger value of the Classification level (higher is more classified)
230    pub lvl: i32,
231
232    /// Long name of the classification item
233    pub name: NameString,
234
235    /// Short name of the classification item
236    pub short_name: NameString,
237    
238    /// Should the classification be skipped building UI options
239    #[serde(default)]
240    pub is_hidden: bool,
241
242    // #[serde(flatten)]
243    // currently planning to static define other fields as optional, making other_fields unneeded
244    // pub other_fields: HashMap<String, serde_value::Value>,
245}
246
247impl ClassificationLevel {
248    /// construct a classification level from its rank (higher is more restricted) and how it should be displayed
249    pub fn new(lvl: i32, short_name: &str, name: &str, aliases: Vec<&str>) -> Self {
250        ClassificationLevel {
251            aliases: aliases.into_iter().map(|x|x.parse().unwrap()).collect(),
252            css: default_css(),
253            description: default_description(),
254            lvl,
255            name: name.parse().unwrap(),
256            short_name: short_name.parse().unwrap(),
257            is_hidden: false,
258        }
259    }
260
261    /// Get all of the unique names used by this item
262    pub fn unique_names(&self) -> Vec<NameString> {
263        let mut names = vec![self.name.clone(), self.short_name.clone()];
264        names.extend(self.aliases.iter().cloned());
265        names.sort_unstable();
266        names.dedup();
267        return names
268    }
269}
270
271/// Get the CSS value to be used for classification levels when none is configured
272fn default_css() -> HashMap<String, String> { [("color".to_owned(), "default".to_owned()), ].into_iter().collect() }
273
274/// Get the description to be used on any description field that is not defined
275fn default_description() -> String {"N/A".to_owned()}
276
277/// A control or dissemination marking
278#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
279#[serde(deny_unknown_fields)]
280pub struct ClassificationMarking {
281    /// List of alternate names for the current marking
282    #[serde(default)]
283    pub aliases: Vec<NameString>,
284
285    /// Long form description of marking
286    #[serde(default="default_description")]
287    pub description: String,
288
289    /// Long form canonical name of marking
290    // #[serde(deserialize_with="deserialize_normalized_name")]
291    pub name: NameString,
292
293    /// Short form canonical name of marking
294    // #[serde(deserialize_with="deserialize_normalized_name")]
295    pub short_name: NameString,
296
297    /// The minimum classification level an item must have for this token to be valid. (optional)
298    #[serde(default)]
299    pub require_lvl: Option<i32>,
300
301    /// This is a token that is required but will display in the groups part
302    /// of the classification string. (optional)
303    #[serde(default)]
304    pub is_required_group: bool,
305
306    /// Should the marking be skipped building UI options
307    #[serde(default)]
308    pub is_hidden: bool,
309}
310
311impl ClassificationMarking {
312    /// Create a marking from canonical names and aliases
313    pub fn new(short_name: &str, name: &str, aliases: Vec<&str>) -> Self {
314        Self {
315            aliases: aliases.into_iter().map(|x|x.parse().unwrap()).collect(),
316            description: default_description(),
317            name: name.parse().unwrap(),
318            short_name: short_name.parse().unwrap(),
319            require_lvl: None,
320            is_required_group: false,
321            is_hidden: false,
322        }
323    }
324
325    /// Create a marking from canonical names that is a required group
326    pub fn new_required(short_name: &str, name: &str) -> Self {
327        let mut new = Self::new(short_name, name, vec![]);
328        new.is_required_group = true;
329        new
330    }
331
332    /// Get all of the unique names used by this item
333    pub fn unique_names(&self) -> Vec<NameString> {
334        let mut names = vec![self.name.clone(), self.short_name.clone()];
335        names.extend(self.aliases.iter().cloned());
336        names.sort_unstable();
337        names.dedup();
338        return names
339    }
340}
341
342/// A group granted access to an object
343#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
344pub struct ClassificationGroup {
345    /// List of alternate names for this group
346    #[serde(default)]
347    pub aliases: Vec<NameString>,
348
349    /// This is a special flag that when set to true, if any groups are selected
350    ///   in a classification. This group will automatically be selected too. (optional)
351    #[serde(default)]
352    pub auto_select: bool,
353
354    /// Long form description of marking
355    #[serde(default="default_description")]
356    pub description: String,
357
358    /// Long form canonical name of marking
359    pub name: NameString,
360
361    /// Short form canonical name of marking
362    pub short_name: NameString,
363
364    /// Assuming that this groups is the only group selected, this is the display name
365    /// that will be used in the classification (that values has to be in the aliases
366    /// of this group and only this group) (optional)
367    #[serde(default)]
368    pub solitary_display_name: Option<NameString>,
369
370    /// Should the marking be skipped building UI options
371    #[serde(default)]
372    pub is_hidden: bool,
373}
374
375impl ClassificationGroup {
376    /// Create access control group from canonical names
377    pub fn new(short_name: &str, name: &str) -> Self {
378        Self {
379            name: name.parse().unwrap(),
380            short_name: short_name.parse().unwrap(),
381            aliases: vec![],
382            auto_select: false,
383            description: default_description(),
384            solitary_display_name: None,
385            is_hidden: false,
386        }
387    }
388
389    /// Create access control group from canonical names that has a special display form when presented alone
390    pub fn new_solitary(short_name: &str, name: &str, solitary_display: &str) -> Self {
391        let mut new = Self::new(short_name, name);
392        new.solitary_display_name = Some(solitary_display.parse().unwrap());
393        return new
394    }
395}
396
397/// A subgroup granted access to an object
398#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
399#[serde(deny_unknown_fields)]
400pub struct ClassificationSubGroup {
401    /// List of alternate names for the current marking
402    #[serde(default)]
403    pub aliases: Vec<NameString>,
404
405    /// This is a special flag that when set to true, if any groups are selected
406    ///   in a classification. This group will automatically be selected too. (optional)
407    #[serde(default)]
408    pub auto_select: bool,
409
410    /// Long form description of marking
411    #[serde(default="default_description")]
412    pub description: String,
413
414    /// Long form canonical name of marking
415    pub name: NameString,
416
417    /// Short form canonical name of marking
418    pub short_name: NameString,
419
420    // /// Assuming that this groups is the only group selected, this is the display name
421    // /// that will be used in the classification (that values has to be in the aliases
422    // /// of this group and only this group) (optional)
423    // ///
424    // /// Loaded in the python version, but not actually used
425    // #[serde(default)]
426    // pub solitary_display_name: Option<String>,
427
428    /// This is a special flag that auto-select the corresponding group when
429    /// this subgroup is selected (optional)
430    #[serde(default)]
431    pub require_group: Option<NameString>,
432
433    /// This is a special flag that makes sure that none other then the
434    /// corresponding group is selected when this subgroup is selected (optional)
435    #[serde(default)]
436    pub limited_to_group: Option<NameString>,
437
438    /// Should the marking be skipped building UI options
439    #[serde(default)]
440    pub is_hidden: bool,
441}
442
443
444impl ClassificationSubGroup {
445    /// Create a new subgroup with aliases
446    pub fn new_aliased(short_name: &str, name: &str, aliases: Vec<&str>) -> Self {
447        Self {
448            short_name: short_name.parse().unwrap(),
449            name: name.parse().unwrap(),
450            aliases: aliases.iter().map(|item|item.parse().unwrap()).collect(),
451            auto_select: false,
452            description: default_description(),
453            require_group: None,
454            limited_to_group: None,
455            is_hidden: false,
456        }
457    }
458
459    /// create a new subgroup with required group
460    pub fn new_with_required(short_name: &str, name: &str, required: &str) -> Self {
461        let mut new = Self::new_aliased(short_name, name, vec![]);
462        new.require_group = Some(required.parse().unwrap());
463        return new
464    }
465
466    /// Create a new subgroup limited in access to a given group
467    pub fn new_with_limited(short_name: &str, name: &str, limited: &str) -> Self {
468        let mut new = Self::new_aliased(short_name, name, vec![]);
469        new.limited_to_group = Some(limited.parse().unwrap());
470        return new
471    }
472}
473
474
475/// A string restricted to the conditions required for the name of a classification element.
476/// Non-zero length and uppercase.
477#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
478pub struct NameString(String);
479
480impl core::ops::Deref for NameString {
481    type Target = str;
482
483    fn deref(&self) -> &Self::Target {
484        self.0.as_str()
485    }
486}
487
488impl std::str::FromStr for NameString {
489    type Err = Errors;
490
491    fn from_str(s: &str) -> Result<Self, Self::Err> {
492        let data = s.trim().to_uppercase();
493        if data.is_empty() {
494            return Err(Errors::ClassificationNameEmpty)
495        }
496        Ok(Self(data))
497    }
498}
499
500impl core::fmt::Display for NameString {
501    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
502        f.write_str(&self.0)
503    }
504}
505
506impl NameString {
507    /// Access the raw string data behind this object
508    pub fn as_str(&self) -> &str {
509        &self.0
510    }
511}
512
513impl<'de> Deserialize<'de> for NameString {
514    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
515    where D: serde::Deserializer<'de> {
516        let data = String::deserialize(deserializer)?;
517        data.parse().map_err(serde::de::Error::custom)
518    }
519}
520
521impl Serialize for NameString {
522    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
523    where
524        S: serde::Serializer {
525        self.0.serialize(serializer)
526    }
527}
528
529#[test]
530fn check_default_configurations() {
531    let default_config = ready_classification(None).unwrap();
532    assert!(!default_config.enforce);
533
534    println!("{:?}", serde_yaml::to_value(DEFAULT_CLASSIFICATION_DATA).unwrap());
535       
536    let config = ready_classification(Some("enforce: true")).unwrap();
537    assert!(config.enforce);
538
539    use crate::classification::ClassificationParser;
540    let ce = ClassificationParser::new(default_config).unwrap();
541
542    assert_eq!(ce.normalize_classification("ABC123").unwrap(), "");
543}