assemblyline_models/config.rs
1use std::{collections::HashMap, path::PathBuf};
2
3use serde::{Deserialize, Serialize};
4use serde_with::{SerializeDisplay, DeserializeFromStr};
5
6use crate::types::ServiceName;
7
8
9/// Named Value
10#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Hash, Clone)]
11pub struct NamedValue {
12 /// Name
13 pub name: String,
14 /// Value
15 pub value: String
16}
17
18/// Webhook Configuration
19#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Hash, Clone)]
20pub struct Webhook {
21 /// Password used to authenticate with source
22 #[serde(default)]
23 pub password: Option<String>,
24 /// CA cert for source
25 #[serde(default)]
26 pub ca_cert: Option<String>,
27 /// Ignore SSL errors when reaching out to source?
28 #[serde(default)]
29 pub ssl_ignore_errors: bool,
30 #[serde(default)]
31 pub ssl_ignore_hostname: bool,
32 /// Proxy server for source
33 #[serde(default)]
34 pub proxy: Option<String>,
35 /// HTTP method used to access webhook
36 #[serde(default="default_webhook_method")]
37 pub method: String,
38 /// URI to source
39 pub uri: String,
40 /// Username used to authenticate with source
41 #[serde(default)]
42 pub username: Option<String>,
43 /// Headers
44 #[serde(default)]
45 pub headers: Vec<NamedValue>,
46 /// Number of attempts to connect to webhook endpoint
47 #[serde(default="default_webhook_retries")]
48 pub retries: Option<u32>,
49}
50
51fn default_webhook_method() -> String { "POST".to_string() }
52fn default_webhook_retries() -> Option<u32> { Some(3) }
53
54/// Resubmission Options
55#[derive(Debug, Default, Serialize, Deserialize)]
56#[serde(default)]
57pub struct ResubmitOptions {
58 pub additional_services: Vec<ServiceName>,
59 pub random_below: Option<i32>,
60}
61
62/// Postprocessing Action
63#[derive(Debug, Serialize, Deserialize)]
64pub struct PostprocessAction {
65 /// Is this action active
66 #[serde(default)]
67 pub enabled: bool,
68 /// Should this action run on cache hits
69 #[serde(default)]
70 pub run_on_cache: bool,
71 /// Should this action run on newly completed submissions
72 #[serde(default)]
73 pub run_on_completed: bool,
74 /// Query string to select submissions
75 pub filter: String,
76 /// Webhook action configuration
77 #[serde(default)]
78 pub webhook: Option<Webhook>,
79 /// Raise an alert when this action is triggered
80 #[serde(default)]
81 pub raise_alert: bool,
82 /// Resubmission configuration
83 #[serde(default)]
84 pub resubmit: Option<ResubmitOptions>,
85 /// Archive the submission when this action is triggered
86 #[serde(default)]
87 pub archive_submission: bool,
88}
89
90impl PostprocessAction {
91 pub fn new(filter: String) -> Self {
92 Self {
93 enabled: Default::default(),
94 run_on_cache: Default::default(),
95 run_on_completed: Default::default(),
96 filter,
97 webhook: Default::default(),
98 raise_alert: Default::default(),
99 resubmit: Default::default(),
100 archive_submission: Default::default(),
101 }
102 }
103
104 pub fn enable(mut self) -> Self {
105 self.enabled = true; self
106 }
107
108 pub fn alert(mut self) -> Self {
109 self.raise_alert = true; self
110 }
111
112 pub fn on_completed(mut self) -> Self {
113 self.run_on_completed = true; self
114 }
115}
116
117pub fn default_postprocess_actions() -> HashMap<String, PostprocessAction> {
118 // Raise alerts for all submissions over 500, both on cache hits and submission complete
119 [("default_alerts".to_string(), PostprocessAction{
120 enabled: true,
121 run_on_cache: true,
122 run_on_completed: true,
123 filter: "max_score: >=500".to_string(),
124 webhook: None,
125 raise_alert: true,
126 resubmit: None,
127 archive_submission: false
128 }),
129 // Resubmit submissions on completion. All submissions with score >= 0 are elegable, but sampling
130 // is applied to scores below 500
131 ("default_resubmit".to_string(), PostprocessAction{
132 enabled: true,
133 run_on_cache: true,
134 run_on_completed: true,
135 filter: "max_score: >=0".to_string(),
136 webhook: None,
137 raise_alert: false,
138 resubmit: Some(ResubmitOptions{
139 additional_services: vec![],
140 random_below: Some(500)
141 }),
142 archive_submission: false
143 })].into_iter().collect()
144}
145
146// from typing import Dict, List
147
148// from assemblyline import odm
149// from assemblyline.odm.models.service import EnvironmentVariable
150// from assemblyline.odm.models.service_delta import DockerConfigDelta
151
152
153// AUTO_PROPERTY_TYPE = ['access', 'classification', 'type', 'role', 'remove_role', 'group']
154// DEFAULT_EMAIL_FIELDS = ['email', 'emails', 'extension_selectedEmailAddress', 'otherMails', 'preferred_username', 'upn']
155
156
157// @odm.model(index=False, store=False, description="Password Requirement")
158// class PasswordRequirement(odm.Model):
159// lower: bool = odm.Boolean(description="Password must contain lowercase letters")
160// number: bool = odm.Boolean(description="Password must contain numbers")
161// special: bool = odm.Boolean(description="Password must contain special characters")
162// upper: bool = odm.Boolean(description="Password must contain uppercase letters")
163// min_length: int = odm.Integer(description="Minimum password length")
164
165
166// DEFAULT_PASSWORD_REQUIREMENTS = {
167// "lower": False,
168// "number": False,
169// "special": False,
170// "upper": False,
171// "min_length": 12
172// }
173
174
175// @odm.model(index=False, store=False,
176// description="Configuration block for [GC Notify](https://notification.canada.ca/) signup and password reset")
177// class Notify(odm.Model):
178// base_url: str = odm.Optional(odm.Keyword(), description="Base URL")
179// api_key: str = odm.Optional(odm.Keyword(), description="API key")
180// registration_template: str = odm.Optional(odm.Keyword(), description="Registration template")
181// password_reset_template: str = odm.Optional(odm.Keyword(), description="Password reset template")
182// authorization_template: str = odm.Optional(odm.Keyword(), description="Authorization template")
183// activated_template: str = odm.Optional(odm.Keyword(), description="Activated Template")
184
185
186// DEFAULT_NOTIFY = {
187// "base_url": None,
188// "api_key": None,
189// "registration_template": None,
190// "password_reset_template": None,
191// "authorization_template": None,
192// "activated_template": None,
193// }
194
195
196// @odm.model(index=False, store=False, description="Configuration block for SMTP signup and password reset")
197// class SMTP(odm.Model):
198// from_adr: str = odm.Optional(odm.Keyword(), description="Email address used for sender")
199// host: str = odm.Optional(odm.Keyword(), description="SMTP host")
200// password: str = odm.Optional(odm.Keyword(), description="Password for SMTP server")
201// port: int = odm.Integer(description="Port of SMTP server")
202// tls: bool = odm.Boolean(description="Should we communicate with SMTP server via TLS?")
203// user: str = odm.Optional(odm.Keyword(), description="User to authenticate to the SMTP server")
204
205
206// DEFAULT_SMTP = {
207// "from_adr": None,
208// "host": None,
209// "password": None,
210// "port": 587,
211// "tls": True,
212// "user": None
213// }
214
215
216// @odm.model(index=False, store=False, description="Signup Configuration")
217// class Signup(odm.Model):
218// enabled: bool = odm.Boolean(description="Can a user automatically signup for the system")
219// smtp: SMTP = odm.Compound(SMTP, default=DEFAULT_SMTP, description="Signup via SMTP")
220// notify: Notify = odm.Compound(Notify, default=DEFAULT_NOTIFY, description="Signup via GC Notify")
221// valid_email_patterns: List[str] = odm.List(
222// odm.Keyword(),
223// description="Email patterns that will be allowed to automatically signup for an account")
224
225
226// DEFAULT_SIGNUP = {
227// "enabled": False,
228// "notify": DEFAULT_NOTIFY,
229// "smtp": DEFAULT_SMTP,
230// "valid_email_patterns": [".*", ".*@localhost"]
231// }
232
233
234// @odm.model(index=False, store=False)
235// class AutoProperty(odm.Model):
236// field: str = odm.Keyword(description="Field to apply `pattern` to")
237// pattern: str = odm.Keyword(description="Regex pattern for auto-prop assignment")
238// type: str = odm.Enum(AUTO_PROPERTY_TYPE, description="Type of property assignment on pattern match")
239// value: List[str] = odm.List(odm.Keyword(), auto=True, default=[], description="Assigned property value")
240
241
242// @odm.model(index=False, store=False, description="LDAP Configuration")
243// class LDAP(odm.Model):
244// enabled: bool = odm.Boolean(description="Should LDAP be enabled or not?")
245// admin_dn: str = odm.Optional(odm.Keyword(), description="DN of the group or the user who will get admin privileges")
246// bind_user: str = odm.Optional(odm.Keyword(), description="User use to query the LDAP server")
247// bind_pass: str = odm.Optional(odm.Keyword(), description="Password used to query the LDAP server")
248// auto_create: bool = odm.Boolean(description="Auto-create users if they are missing")
249// auto_sync: bool = odm.Boolean(description="Should we automatically sync with LDAP server on each login?")
250// auto_properties: List[AutoProperty] = odm.List(odm.Compound(AutoProperty), default=[],
251// description="Automatic role and classification assignments")
252// base: str = odm.Keyword(description="Base DN for the users")
253// classification_mappings: Dict[str, str] = odm.Any(description="Classification mapping")
254// email_field: str = odm.Keyword(description="Name of the field containing the email address")
255// group_lookup_query: str = odm.Keyword(description="How the group lookup is queried")
256// image_field: str = odm.Keyword(description="Name of the field containing the user's avatar")
257// image_format: str = odm.Keyword(description="Type of image used to store the avatar")
258// name_field: str = odm.Keyword(description="Name of the field containing the user's name")
259// signature_importer_dn: str = odm.Optional(
260// odm.Keyword(),
261// description="DN of the group or the user who will get signature_importer role")
262// signature_manager_dn: str = odm.Optional(
263// odm.Keyword(),
264// description="DN of the group or the user who will get signature_manager role")
265// uid_field: str = odm.Keyword(description="Field name for the UID")
266// uri: str = odm.Keyword(description="URI to the LDAP server")
267
268
269// DEFAULT_LDAP = {
270// "enabled": False,
271// "bind_user": None,
272// "bind_pass": None,
273// "auto_create": True,
274// "auto_sync": True,
275// "auto_properties": [],
276// "base": "ou=people,dc=assemblyline,dc=local",
277// "email_field": "mail",
278// "group_lookup_query": "(&(objectClass=Group)(member=%s))",
279// "image_field": "jpegPhoto",
280// "image_format": "jpeg",
281// "name_field": "cn",
282// "uid_field": "uid",
283// "uri": "ldap://localhost:389",
284
285// # Deprecated
286// "admin_dn": None,
287// "classification_mappings": {},
288// "signature_importer_dn": None,
289// "signature_manager_dn": None,
290// }
291
292
293// @odm.model(index=False, store=False, description="Internal Authentication Configuration")
294// class Internal(odm.Model):
295// enabled: bool = odm.Boolean(description="Internal authentication allowed?")
296// failure_ttl: int = odm.Integer(description="How long to wait after `max_failures` before re-attempting login?")
297// max_failures: int = odm.Integer(description="Maximum number of fails allowed before timeout")
298// password_requirements: PasswordRequirement = odm.Compound(PasswordRequirement,
299// default=DEFAULT_PASSWORD_REQUIREMENTS,
300// description="Password requirements")
301// signup: Signup = odm.Compound(Signup, default=DEFAULT_SIGNUP, description="Signup method")
302
303
304// DEFAULT_INTERNAL = {
305// "enabled": True,
306// "failure_ttl": 60,
307// "max_failures": 5,
308// "password_requirements": DEFAULT_PASSWORD_REQUIREMENTS,
309// "signup": DEFAULT_SIGNUP
310// }
311
312
313// @odm.model(index=False, store=False, description="App provider")
314// class AppProvider(odm.Model):
315// access_token_url: str = odm.Keyword(description="URL used to get the access token")
316// user_get: str = odm.Optional(odm.Keyword(), description="Path from the base_url to fetch the user info")
317// group_get: str = odm.Optional(odm.Keyword(), description="Path from the base_url to fetch the group info")
318// scope: str = odm.Keyword()
319// client_id: str = odm.Optional(odm.Keyword(), description="ID of your application to authenticate to the OAuth")
320// client_secret: str = odm.Optional(odm.Keyword(),
321// description="Password to your application to authenticate to the OAuth provider")
322
323
324// @odm.model(index=False, store=False, description="OAuth Provider Configuration")
325// class OAuthProvider(odm.Model):
326// auto_create: bool = odm.Boolean(default=True, description="Auto-create users if they are missing")
327// auto_sync: bool = odm.Boolean(default=False, description="Should we automatically sync with OAuth provider?")
328// auto_properties: List[AutoProperty] = odm.List(odm.Compound(AutoProperty), default=[],
329// description="Automatic role and classification assignments")
330// app_provider: AppProvider = odm.Optional(odm.Compound(AppProvider))
331// uid_randomize: bool = odm.Boolean(default=False,
332// description="Should we generate a random username for the authenticated user?")
333// uid_randomize_digits: int = odm.Integer(default=0,
334// description="How many digits should we add at the end of the username?")
335// uid_randomize_delimiter: str = odm.Keyword(default="-",
336// description="What is the delimiter used by the random name generator?")
337// uid_regex: str = odm.Optional(
338// odm.Keyword(),
339// description="Regex used to parse an email address and capture parts to create a user ID out of it")
340// uid_format: str = odm.Optional(odm.Keyword(),
341// description="Format of the user ID based on the captured parts from the regex")
342// client_id: str = odm.Optional(odm.Keyword(),
343// description="ID of your application to authenticate to the OAuth provider")
344// client_secret: str = odm.Optional(odm.Keyword(),
345// description="Password to your application to authenticate to the OAuth provider")
346// request_token_url: str = odm.Optional(odm.Keyword(), description="URL to request token")
347// request_token_params: str = odm.Optional(odm.Keyword(), description="Parameters to request token")
348// access_token_url: str = odm.Optional(odm.Keyword(), description="URL to get access token")
349// access_token_params: str = odm.Optional(odm.Keyword(), description="Parameters to get access token")
350// authorize_url: str = odm.Optional(odm.Keyword(), description="URL used to authorize access to a resource")
351// authorize_params: str = odm.Optional(odm.Keyword(), description="Parameters used to authorize access to a resource")
352// api_base_url: str = odm.Optional(odm.Keyword(), description="Base URL for downloading the user's and groups info")
353// client_kwargs: Dict[str, str] = odm.Optional(odm.Mapping(odm.Keyword()),
354// description="Keyword arguments passed to the different URLs")
355// jwks_uri: str = odm.Optional(odm.Keyword(), description="URL used to verify if a returned JWKS token is valid")
356// uid_field: str = odm.Optional(odm.Keyword(), description="Name of the field that will contain the user ID")
357// user_get: str = odm.Optional(odm.Keyword(), description="Path from the base_url to fetch the user info")
358// user_groups: str = odm.Optional(odm.Keyword(), description="Path from the base_url to fetch the group info")
359// user_groups_data_field: str = odm.Optional(
360// odm.Keyword(),
361// description="Field return by the group info API call that contains the list of groups")
362// user_groups_name_field: str = odm.Optional(
363// odm.Keyword(),
364// description="Name of the field in the list of groups that contains the name of the group")
365// use_new_callback_format: bool = odm.Boolean(default=False, description="Should we use the new callback method?")
366// allow_external_tokens: bool = odm.Boolean(
367// default=False, description="Should token provided to the login API directly be use for authentication?")
368// external_token_alternate_audiences: List[str] = odm.List(
369// odm.Keyword(), default=[], description="List of valid alternate audiences for the external token.")
370// email_fields: List[str] = odm.List(odm.Keyword(), default=DEFAULT_EMAIL_FIELDS,
371// description="List of fields in the claim to get the email from")
372// username_field: str = odm.Keyword(default='uname', description="Name of the field that will contain the username")
373
374
375// DEFAULT_OAUTH_PROVIDER_AZURE = {
376// "access_token_url": 'https://login.microsoftonline.com/common/oauth2/token',
377// "api_base_url": 'https://login.microsoft.com/common/',
378// "authorize_url": 'https://login.microsoftonline.com/common/oauth2/authorize',
379// "client_id": None,
380// "client_secret": None,
381// "client_kwargs": {"scope": "openid email profile"},
382// "jwks_uri": "https://login.microsoftonline.com/common/discovery/v2.0/keys",
383// "user_get": "openid/userinfo"
384// }
385
386// DEFAULT_OAUTH_PROVIDER_GOOGLE = {
387// "access_token_url": 'https://oauth2.googleapis.com/token',
388// "api_base_url": 'https://openidconnect.googleapis.com/',
389// "authorize_url": 'https://accounts.google.com/o/oauth2/v2/auth',
390// "client_id": None,
391// "client_secret": None,
392// "client_kwargs": {"scope": "openid email profile"},
393// "jwks_uri": "https://www.googleapis.com/oauth2/v3/certs",
394// "user_get": "v1/userinfo"
395// }
396
397// DEFAULT_OAUTH_PROVIDER_AUTH_ZERO = {
398// "access_token_url": 'https://{TENANT}.auth0.com/oauth/token',
399// "api_base_url": 'https://{TENANT}.auth0.com/',
400// "authorize_url": 'https://{TENANT}.auth0.com/authorize',
401// "client_id": None,
402// "client_secret": None,
403// "client_kwargs": {"scope": "openid email profile"},
404// "jwks_uri": "https://{TENANT}.auth0.com/.well-known/jwks.json",
405// "user_get": "userinfo"
406// }
407
408// DEFAULT_OAUTH_PROVIDERS = {
409// 'auth0': DEFAULT_OAUTH_PROVIDER_AUTH_ZERO,
410// 'azure_ad': DEFAULT_OAUTH_PROVIDER_AZURE,
411// 'google': DEFAULT_OAUTH_PROVIDER_GOOGLE,
412// }
413
414
415// @odm.model(index=False, store=False, description="OAuth Configuration")
416// class OAuth(odm.Model):
417// enabled: bool = odm.Boolean(description="Enable use of OAuth?")
418// gravatar_enabled: bool = odm.Boolean(description="Enable gravatar?")
419// providers: Dict[str, OAuthProvider] = odm.Mapping(odm.Compound(OAuthProvider), default=DEFAULT_OAUTH_PROVIDERS,
420// description="OAuth provider configuration")
421
422
423// DEFAULT_OAUTH = {
424// "enabled": False,
425// "gravatar_enabled": True,
426// "providers": DEFAULT_OAUTH_PROVIDERS
427// }
428
429
430/// Authentication Methods
431#[derive(Serialize, Deserialize, Default)]
432#[serde(default)]
433pub struct Auth {
434 // allow_2fa: bool = odm.Boolean(description="Allow 2FA?")
435 // allow_apikeys: bool = odm.Boolean(description="Allow API keys?")
436 /// Number of days apikey can live for.
437 pub apikey_max_dtl: Option<u64>,
438 // allow_extended_apikeys: bool = odm.Boolean(description="Allow extended API keys?")
439 // allow_security_tokens: bool = odm.Boolean(description="Allow security tokens?")
440 // internal: Internal = odm.Compound(Internal, default=DEFAULT_INTERNAL, description="Internal authentication settings")
441 // ldap: LDAP = odm.Compound(LDAP, default=DEFAULT_LDAP, description="LDAP settings")
442 // oauth: OAuth = odm.Compound(OAuth, default=DEFAULT_OAUTH, description="OAuth settings")
443}
444
445// DEFAULT_AUTH = {
446// "allow_2fa": True,
447// "allow_apikeys": True,
448// "allow_extended_apikeys": True,
449// "allow_security_tokens": True,
450// "internal": DEFAULT_INTERNAL,
451// "ldap": DEFAULT_LDAP,
452// "oauth": DEFAULT_OAUTH
453// }
454
455
456// @odm.model(index=False, store=False, description="Alerter Configuration")
457// class Alerter(odm.Model):
458// alert_ttl: int = odm.Integer(description="Time to live (days) for an alert in the system")
459// constant_alert_fields: List[str] = odm.List(
460// odm.Keyword(), description="List of fields that should not change during an alert update")
461// default_group_field: str = odm.Keyword(description="Default field used for alert grouping view")
462// delay: int = odm.Integer(
463// description="Time in seconds that we give extended scans and workflow to complete their work "
464// "before we start showing alerts in the alert viewer.")
465// filtering_group_fields: List[str] = odm.List(
466// odm.Keyword(),
467// description="List of group fields that when selected will ignore certain alerts where this field is missing.")
468// non_filtering_group_fields: List[str] = odm.List(
469// odm.Keyword(), description="List of group fields that are sure to be present in all alerts.")
470// process_alert_message: str = odm.Keyword(
471// description="Python path to the function that will process an alert message.")
472// threshold: int = odm.Integer(description="Minimum score to reach for a submission to be considered an alert.")
473
474
475// DEFAULT_ALERTER = {
476// "alert_ttl": 90,
477// "constant_alert_fields": ["alert_id", "file", "ts"],
478// "default_group_field": "file.sha256",
479// "delay": 300,
480// "filtering_group_fields": [
481// "file.name",
482// "status",
483// "priority"
484// ],
485// "non_filtering_group_fields": [
486// "file.md5",
487// "file.sha1",
488// "file.sha256"
489// ],
490// "process_alert_message": "assemblyline_core.alerter.processing.process_alert_message",
491// "threshold": 500
492// }
493
494#[derive(Serialize, Deserialize)]
495#[serde(default)]
496pub struct Classification {
497 pub path: Option<PathBuf>,
498 pub config: Option<String>,
499}
500
501impl Default for Classification {
502 fn default() -> Self {
503 Self {
504 path: Some("/etc/assemblyline/classification.yml".into()),
505 config: None,
506 }
507 }
508}
509
510
511/// Dispatcher Configuration
512#[derive(Serialize, Deserialize)]
513#[serde(default)]
514pub struct Dispatcher {
515 /// Time between re-dispatching attempts, as long as some action (submission or any task completion) happens before this timeout ends, the timeout resets.
516 pub timeout: f64,
517 /// Maximum submissions allowed to be in-flight
518 pub max_inflight: u64,
519}
520
521impl Default for Dispatcher {
522 fn default() -> Self {
523 Self {
524 timeout: 15.0 * 60.0,
525 max_inflight: 1000
526 }
527 }
528}
529
530
531// Configuration options regarding data expiry
532#[derive(Serialize, Deserialize)]
533#[serde(default)]
534pub struct Expiry {
535 /// Perform expiry in batches?<br>Delete queries are rounded by day therefore all delete operation happen at the same time at midnight
536 pub batch_delete: bool,
537 /// Delay, in hours, that will be applied to the expiry query so we can keep data longer then previously set or we can offset deletion during non busy hours
538 pub delay: u32,
539 /// Should we also cleanup the file storage?
540 pub delete_storage: bool,
541 /// Time, in seconds, to sleep in between each expiry run
542 pub sleep_time: u32,
543 /// Number of concurrent workers
544 pub workers: u32,
545 /// Worker processes for file storage deletes.
546 pub delete_workers: u32,
547 /// How many query chunks get run per iteration.
548 pub iteration_max_tasks: u32,
549 /// How large a batch get deleted per iteration.
550 pub delete_batch_size: u32,
551 /// The default period, in days, before tags expire from Badlist
552 pub badlisted_tag_dtl: u32,
553}
554
555impl Default for Expiry {
556 fn default() -> Self {
557 Self {
558 batch_delete: false,
559 delay: 0,
560 delete_storage: true,
561 sleep_time: 15,
562 workers: 20,
563 delete_workers: 2,
564 iteration_max_tasks: 20,
565 delete_batch_size: 200,
566 badlisted_tag_dtl: 0
567 }
568 }
569}
570
571
572#[derive(strum::EnumIter, strum::Display, strum::EnumString, SerializeDisplay, DeserializeFromStr, PartialEq, Eq, Hash)]
573#[strum(ascii_case_insensitive, serialize_all = "kebab-case")]
574pub enum Priority {
575 Low,
576 Medium,
577 High,
578 Critical,
579 UserLow,
580 UserMedium,
581 UserHigh,
582}
583
584impl Priority {
585 pub fn range(&self) -> (u16, u16) {
586 match self {
587 Priority::Low => (0, 100),
588 Priority::Medium => (101, 200),
589 Priority::High => (201, 300),
590 Priority::Critical => (301, 400),
591 Priority::UserLow => (401, 500),
592 Priority::UserMedium => (501, 1000),
593 Priority::UserHigh => (1001, 1500),
594 }
595 }
596}
597
598
599/// Ingester Configuration
600#[derive(Serialize, Deserialize)]
601#[serde(default)]
602pub struct Ingester {
603 // /// Default user for bulk ingestion and unattended submissions
604 // pub default_user: str = odm.Keyword()
605 // /// Default service selection
606 // pub default_services: List[str] = odm.List(odm.Keyword(), )
607 // /// Default service selection for resubmits
608 // pub default_resubmit_services: List[str] = odm.List(odm.Keyword(), )
609 // /// A prefix for descriptions. When a description is automatically generated, it will be the hash prefixed by this string
610 // pub description_prefix: str = odm.Keyword()
611 // /// Path to a callback function filtering ingestion tasks that should have their priority forcefully reset to low
612 // pub is_low_priority: str = odm.Keyword()
613 // get_whitelist_verdict: str = odm.Keyword()
614 // whitelist: str = odm.Keyword()
615 // /// How many extracted files may be added to a Submission. Overrideable via submission parameters.
616 // pub default_max_extracted: int = odm.Integer()
617 // /// How many supplementary files may be added to a Submission. Overrideable via submission parameters
618 // pub default_max_supplementary: int = odm.Integer()
619 /// Period, in seconds, in which a task should be expired
620 pub expire_after: f32,
621 /// Drop a task altogether after this many seconds
622 pub stale_after_seconds: f32,
623 /// How long should scores be kept before expiry
624 pub incomplete_expire_after_seconds: f32,
625 /// How long should scores be cached in the ingester
626 pub incomplete_stale_after_seconds: f32,
627 /// Thresholds at certain buckets before sampling
628 pub sampling_at: HashMap<Priority, i64>,
629 /// How many files to send to dispatcher concurrently
630 pub max_inflight: u64,
631 /// How long are files results cached
632 pub cache_dtl: u32,
633 /// Always create submissions even on cache hit?
634 pub always_create_submission: bool,
635}
636
637impl Default for Ingester {
638 fn default() -> Self {
639 Self {
640 cache_dtl: 2,
641// 'default_user': 'internal',
642// 'default_services': [],
643// 'default_resubmit_services': [],
644// 'description_prefix': 'Bulk',
645// 'is_low_priority': 'assemblyline.common.null.always_false',
646// 'get_whitelist_verdict': 'assemblyline.common.signaturing.drop',
647// 'whitelist': 'assemblyline.common.null.whitelist',
648// 'default_max_extracted': 100,
649// 'default_max_supplementary': 100,
650 expire_after: 15.0 * 24.0 * 60.0 * 60.0,
651 stale_after_seconds: 1.0 * 24.0 * 60.0 * 60.0,
652 incomplete_expire_after_seconds: 3600.0,
653 incomplete_stale_after_seconds: 1800.0,
654 sampling_at: [
655 (Priority::Low, 10000000),
656 (Priority::Medium, 2000000),
657 (Priority::High, 1000000),
658 (Priority::Critical, 500000),
659 ].into_iter().collect(),
660 max_inflight: 5000,
661 always_create_submission: false,
662 }
663 }
664}
665
666
667/// Redis Service configuration
668#[derive(Serialize, Deserialize)]
669pub struct RedisServer {
670 /// Hostname of Redis instance
671 pub host: String,
672 /// Port of Redis instance
673 pub port: u16,
674 /// Which db to connect to
675 #[serde(default)]
676 pub db: i64,
677}
678
679fn default_redis_nonpersistant() -> RedisServer {
680 RedisServer {
681 host: "127.0.0.1".to_owned(),
682 port: 6379,
683 db: 0,
684 }
685}
686
687fn default_redis_persistant() -> RedisServer {
688 RedisServer {
689 host: "127.0.0.1".to_owned(),
690 port: 6380,
691 db: 0,
692 }
693}
694
695
696// @odm.model(index=False, store=False)
697// class ESMetrics(odm.Model):
698// hosts: List[str] = odm.Optional(odm.List(odm.Keyword()), description="Elasticsearch hosts")
699// host_certificates: str = odm.Optional(odm.Keyword(), description="Host certificates")
700// warm = odm.Integer(description="How long, per unit of time, should a document remain in the 'warm' tier?")
701// cold = odm.Integer(description="How long, per unit of time, should a document remain in the 'cold' tier?")
702// delete = odm.Integer(description="How long, per unit of time, should a document remain before being deleted?")
703// unit = odm.Enum(['d', 'h', 'm'], description="Unit of time used by `warm`, `cold`, `delete` phases")
704
705
706// DEFAULT_ES_METRICS = {
707// 'hosts': None,
708// 'host_certificates': None,
709// 'warm': 2,
710// 'cold': 30,
711// 'delete': 90,
712// 'unit': 'd'
713// }
714
715
716#[derive(Serialize, Deserialize, Default)]
717#[serde(default)]
718pub struct APMServer {
719 /// URL to API server
720 pub server_url: Option<String>,
721 /// Authentication token for server
722 pub token: Option<String>,
723}
724
725
726/// Metrics Configuration
727#[derive(Serialize, Deserialize)]
728#[serde(default)]
729pub struct Metrics {
730 /// APM server configuration
731 pub apm_server: APMServer,
732// elasticsearch: ESMetrics = odm.Compound(ESMetrics, default=DEFAULT_ES_METRICS, description="Where to export metrics?")
733 /// How often should we be exporting metrics in seconds?
734 pub export_interval: u32,
735 /// Redis for Dashboard metrics
736 pub redis: RedisServer,
737}
738
739impl Default for Metrics {
740 fn default() -> Self {
741 Self {
742 apm_server: Default::default(),
743 export_interval: 5,
744 redis: default_redis_nonpersistant()
745 }
746 }
747}
748
749
750#[derive(Serialize, Deserialize, Default)]
751/// Malware Archive Configuration
752#[serde(default)]
753pub struct Archiver {
754 /// List of minimum required service before archiving takes place
755 pub minimum_required_services: Vec<ServiceName>,
756}
757
758/// Redis Configuration
759#[derive(Serialize, Deserialize)]
760#[serde(default)]
761pub struct Redis {
762 /// A volatile Redis instance
763 pub nonpersistent: RedisServer,
764 /// A persistent Redis instance
765 pub persistent: RedisServer,
766}
767
768impl Default for Redis {
769 fn default() -> Self {
770 Self {
771 nonpersistent: default_redis_nonpersistant(),
772 persistent: default_redis_persistant()
773 }
774 }
775}
776
777
778// @odm.model(index=False, store=False, description="A configuration for mounting existing volumes to a container")
779// class Mount(odm.Model):
780// name: str = odm.Keyword(description="Name of volume mount")
781// path: str = odm.Text(description="Target mount path")
782// read_only: bool = odm.Boolean(default=True, description="Should this be mounted as read-only?")
783// privileged_only: bool = odm.Boolean(default=False,
784// description="Should this mount only be available for privileged services?")
785
786// # Kubernetes-specific
787// resource_type: str = odm.Enum(default='volume', values=['secret', 'configmap', 'volume'],
788// description="Type of mountable Kubernetes resource")
789// resource_name: str = odm.Optional(odm.Keyword(), description="Name of resource (Kubernetes only)")
790// resource_key: str = odm.Optional(odm.Keyword(), description="Key of ConfigMap/Secret (Kubernetes only)")
791
792// # TODO: Deprecate in next major change in favour of general configuration above for mounting Kubernetes resources
793// config_map: str = odm.Optional(odm.Keyword(), description="Name of ConfigMap (Kubernetes only, deprecated)")
794// key: str = odm.Optional(odm.Keyword(), description="Key of ConfigMap (Kubernetes only, deprecated)")
795
796
797// @odm.model(index=False, store=False,
798// description="A set of default values to be used running a service when no other value is set")
799// class ScalerServiceDefaults(odm.Model):
800// growth: int = odm.Integer(description="Period, in seconds, to wait before scaling up a service deployment")
801// shrink: int = odm.Integer(description="Period, in seconds, to wait before scaling down a service deployment")
802// backlog: int = odm.Integer(description="Backlog threshold that dictates scaling adjustments")
803// min_instances: int = odm.Integer(description="The minimum number of service instances to be running")
804// environment: List[EnvironmentVariable] = odm.List(odm.Compound(EnvironmentVariable), default=[],
805// description="Environment variables to pass onto services")
806// mounts: List[Mount] = odm.List(odm.Compound(Mount), default=[],
807// description="A list of volume mounts for every service")
808
809
810// # The operations we support for label and field selectors are based on the common subset of
811// # what kubernetes supports on the list_node API endpoint and the nodeAffinity field
812// # on pod specifications. The selector needs to work in both cases because we use these
813// # selectors both for probing what nodes are available (list_node) and making sure
814// # the pods only run on the pods that are returned there (using nodeAffinity)
815
816// @odm.model(index=False, store=False, description="Limit a set of kubernetes objects based on a field query.")
817// class FieldSelector(odm.Model):
818// key = odm.keyword(description="Name of a field to select on.")
819// equal = odm.boolean(default=True, description="When true key must equal value, when false it must not")
820// value = odm.keyword(description="Value to compare field to.")
821
822
823// # Excluded from this list is Gt and Lt for above reason
824// KUBERNETES_LABEL_OPS = ['In', 'NotIn', 'Exists', 'DoesNotExist']
825
826
827// @odm.model(index=False, store=False, description="Limit a set of kubernetes objects based on a label query.")
828// class LabelSelector(odm.Model):
829// key = odm.keyword(description="Name of label to select on.")
830// operator = odm.Enum(KUBERNETES_LABEL_OPS, description="Operation to select label with.")
831// values = odm.sequence(odm.keyword(), description="Value list to compare label to.")
832
833
834// @odm.model(index=False, store=False)
835// class Selector(odm.Model):
836// field = odm.sequence(odm.compound(FieldSelector), default=[],
837// description="Field selector for resource under kubernetes")
838// label = odm.sequence(odm.compound(LabelSelector), default=[],
839// description="Label selector for resource under kubernetes")
840
841
842// @odm.model(index=False, store=False)
843// class Scaler(odm.Model):
844// service_defaults: ScalerServiceDefaults = odm.Compound(ScalerServiceDefaults,
845// description="Defaults Scaler will assign to a service.")
846// cpu_overallocation: float = odm.Float(description="Percentage of CPU overallocation")
847// memory_overallocation: float = odm.Float(description="Percentage of RAM overallocation")
848// overallocation_node_limit = odm.Optional(odm.Integer(description="If the system has this many nodes or "
849// "more overallocation is ignored"))
850// additional_labels: List[str] = odm.Optional(
851// odm.List(odm.Text()), description="Additional labels to be applied to services('=' delimited)")
852// linux_node_selector = odm.compound(Selector, description="Selector for linux nodes under kubernetes")
853// # windows_node_selector = odm.compound(Selector, description="Selector for windows nodes under kubernetes")
854
855
856// DEFAULT_SCALER = {
857// 'additional_labels': None,
858// 'cpu_overallocation': 1,
859// 'memory_overallocation': 1,
860// 'overallocation_node_limit': None,
861// 'service_defaults': {
862// 'growth': 60,
863// 'shrink': 30,
864// 'backlog': 100,
865// 'min_instances': 0,
866// 'environment': [
867// {'name': 'SERVICE_API_HOST', 'value': 'http://service-server:5003'},
868// {'name': 'AL_SERVICE_TASK_LIMIT', 'value': 'inf'},
869// ],
870// },
871// 'linux_node_selector': {
872// 'field': [],
873// 'label': [],
874// },
875// # 'windows_node_selector': {
876// # 'field': [],
877// # 'label': [],
878// # },
879// }
880
881
882// @odm.model(index=False, store=False)
883// class RegistryConfiguration(odm.Model):
884// name: str = odm.Text(description="Name of container registry")
885// proxies: Dict = odm.Optional(odm.Mapping(odm.Text()),
886// description="Proxy configuration that is passed to Python Requests")
887
888
889// @odm.model(index=False, store=False)
890// class Updater(odm.Model):
891// job_dockerconfig: DockerConfigDelta = odm.Compound(
892// DockerConfigDelta, description="Container configuration used for service registration/updates")
893// registry_configs: List = odm.List(odm.Compound(RegistryConfiguration),
894// description="Configurations to be used with container registries")
895
896
897// DEFAULT_UPDATER = {
898// 'job_dockerconfig': {
899// 'cpu_cores': 1,
900// 'ram_mb': 1024,
901// 'ram_mb_min': 256,
902// },
903// 'registry_configs': [{
904// 'name': 'registry.hub.docker.com',
905// 'proxies': {}
906// }]
907// }
908
909
910// @odm.model(index=False, store=False)
911// class VacuumSafelistItem(odm.Model):
912// name = odm.Keyword()
913// conditions = odm.Mapping(odm.Keyword())
914
915
916// @odm.model(index=False, store=False)
917// class Vacuum(odm.Model):
918// list_cache_directory: str = odm.Keyword()
919// worker_cache_directory: str = odm.Keyword()
920// data_directories: List[str] = odm.List(odm.Keyword())
921// file_directories: List[str] = odm.List(odm.Keyword())
922// assemblyline_user: str = odm.Keyword()
923// department_map_url = odm.Optional(odm.Keyword())
924// department_map_init = odm.Optional(odm.Keyword())
925// stream_map_url = odm.Optional(odm.Keyword())
926// stream_map_init = odm.Optional(odm.Keyword())
927// safelist = odm.List(odm.Compound(VacuumSafelistItem))
928// worker_threads: int = odm.Integer()
929// worker_rollover: int = odm.Integer()
930// minimum_classification: str = odm.Keyword()
931// ingest_type = odm.keyword()
932
933
934// DEFAULT_VACUUM = dict(
935// list_cache_directory="/cache/",
936// worker_cache_directory="/memory/",
937// data_directories=[],
938// file_directories=[],
939// assemblyline_user="vacuum-service-account",
940// department_map_url=None,
941// department_map_init=None,
942// stream_map_url=None,
943// stream_map_init=None,
944// safelist=[],
945// worker_threads=50,
946// worker_rollover=1000,
947// minimum_classification='U',
948// ingest_type='VACUUM',
949// )
950
951
952/// Core Component Configuration
953#[derive(Serialize, Deserialize, Default)]
954#[serde(default)]
955// @odm.model(index=False, store=False, description="")
956pub struct Core {
957 // /// Configuration for Alerter
958 // #[serde(default)]
959 // pub alerter: Alerter,
960 /// Configuration for the permanent submission archive
961 pub archiver: Archiver,
962 /// Configuration for Dispatcher
963 pub dispatcher: Dispatcher,
964 /// Configuration for Expiry
965 pub expiry: Expiry,
966 /// Configuration for Ingester
967 pub ingester: Ingester,
968 /// Configuration for Metrics Collection
969 pub metrics: Metrics,
970 /// Configuration for system cleanup
971 pub plumber: Plumber,
972 /// Configuration for Redis instances
973 pub redis: Redis,
974 // /// Configuration for Scaler
975 // #[serde(default)]
976 // pub scaler: Scaler,
977 // /// Configuration for Updater
978 // #[serde(default)]
979 // pub updater: Updater,
980 // /// Configuration for Vacuum
981 // #[serde(default)]
982 // pub vacuum: Vacuum,
983}
984
985// DEFAULT_CORE = {
986// "alerter": DEFAULT_ALERTER,
987// "archiver": DEFAULT_ARCHIVER,
988// "dispatcher": DEFAULT_DISPATCHER,
989// "expiry": DEFAULT_EXPIRY,
990// "ingester": DEFAULT_INGESTER,
991// "metrics": DEFAULT_METRICS,
992// "redis": DEFAULT_REDIS,
993// "scaler": DEFAULT_SCALER,
994// "updater": DEFAULT_UPDATER,
995// }
996
997/// Plumber Configuration
998#[derive(Serialize, Deserialize)]
999#[serde(default)]
1000pub struct Plumber {
1001 /// Interval in seconds at which the notification queue cleanup should run
1002 pub notification_queue_interval: u64,
1003 /// Max age in seconds notification queue messages can be
1004 pub notification_queue_max_age: u64,
1005}
1006
1007impl Default for Plumber {
1008 fn default() -> Self {
1009 Self {
1010 notification_queue_interval: 30 * 60,
1011 notification_queue_max_age: 24 * 60 * 60
1012 }
1013 }
1014}
1015
1016
1017
1018
1019/// Datastore Archive feature configuration
1020#[derive(Serialize, Deserialize)]
1021#[serde(default)]
1022pub struct Archive {
1023 /// Are we enabling Achiving features across indices?
1024 pub enabled: bool,
1025 /// List of indices the ILM Applies to
1026 pub indices: Vec<String>,
1027}
1028
1029impl Default for Archive {
1030 fn default() -> Self {
1031 Self {
1032 enabled: false,
1033 indices: vec!["file".to_owned(), "submission".to_owned(), "result".to_owned()],
1034 }
1035 }
1036}
1037
1038
1039#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Copy)]
1040#[serde(rename_all="lowercase")]
1041pub enum DatastoreType {
1042 Elasticsearch
1043}
1044
1045#[test]
1046fn test_datastore_type_serialization() {
1047 assert_eq!(serde_json::to_string(&DatastoreType::Elasticsearch).unwrap(), "\"elasticsearch\"");
1048 assert_eq!(serde_json::from_str::<DatastoreType>("\"elasticsearch\"").unwrap(), DatastoreType::Elasticsearch);
1049 assert_eq!(serde_json::to_value(DatastoreType::Elasticsearch).unwrap(), serde_json::json!("elasticsearch"));
1050 // assert_eq!(serde_json::from_str::<DatastoreType>("\"Elasticsearch\"").unwrap(), DatastoreType::Elasticsearch);
1051
1052 #[derive(Debug, Serialize, Deserialize)]
1053 struct Test {
1054 ds: DatastoreType
1055 }
1056 let sample = Test {ds: DatastoreType::Elasticsearch};
1057 assert_eq!(serde_json::to_string(&sample).unwrap(), "{\"ds\":\"elasticsearch\"}");
1058}
1059
1060
1061/// Datastore Configuration
1062#[derive(Serialize, Deserialize)]
1063#[serde(default)]
1064pub struct Datastore {
1065 /// List of hosts used for the datastore
1066 pub hosts: Vec<String>,
1067 /// Datastore Archive feature configuration
1068 pub archive: Archive,
1069 /// Default cache lenght for computed indices (submission_tree, submission_summary...
1070 pub cache_dtl: u32,
1071 /// Type of application used for the datastore
1072 #[serde(rename="type")]
1073 pub dtype: DatastoreType,
1074}
1075
1076impl Default for Datastore {
1077 fn default() -> Self {
1078 Self {
1079 hosts: vec!["http://elastic:devpass@localhost:9200".to_owned()],
1080 archive: Default::default(),
1081 cache_dtl: 5,
1082 dtype: DatastoreType::Elasticsearch,
1083 }
1084 }
1085}
1086
1087
1088// @odm.model(index=False, store=False, description="Datasource Configuration")
1089// class Datasource(odm.Model):
1090// classpath: str = odm.Keyword()
1091// config: Dict[str, str] = odm.Mapping(odm.Keyword())
1092
1093
1094// DEFAULT_DATASOURCES = {
1095// "al": {
1096// "classpath": 'assemblyline.datasource.al.AL',
1097// "config": {}
1098// },
1099// "alert": {
1100// "classpath": 'assemblyline.datasource.alert.Alert',
1101// "config": {}
1102// }
1103// }
1104
1105
1106/// Filestore Configuration
1107#[derive(Serialize, Deserialize)]
1108#[serde(default)]
1109pub struct Filestore {
1110 /// List of filestores used for malware archive
1111 pub archive: Vec<String>,
1112 /// List of filestores used for caching
1113 pub cache: Vec<String>,
1114 /// List of filestores used for storage
1115 pub storage: Vec<String>,
1116}
1117
1118impl Default for Filestore {
1119 fn default() -> Self {
1120 Self {
1121 archive: vec!["s3://al_storage_key:Ch@ngeTh!sPa33w0rd@localhost:9000?s3_bucket=al-archive&use_ssl=False".to_string()],
1122 cache: vec!["s3://al_storage_key:Ch@ngeTh!sPa33w0rd@localhost:9000?s3_bucket=al-cache&use_ssl=False".to_string()],
1123 storage: vec!["s3://al_storage_key:Ch@ngeTh!sPa33w0rd@localhost:9000?s3_bucket=al-storage&use_ssl=False".to_string()]
1124 }
1125 }
1126}
1127
1128#[derive(Debug, strum::Display, SerializeDisplay, strum::EnumString, DeserializeFromStr)]
1129#[strum(serialize_all="UPPERCASE", ascii_case_insensitive)]
1130pub enum LogLevel {
1131 Debug,
1132 Info,
1133 Warning,
1134 Error,
1135 Critical,
1136 Disabled,
1137}
1138
1139#[derive(Debug, Serialize, Deserialize)]
1140pub enum SyslogTransport {
1141 Udp,
1142 Tcp
1143}
1144
1145/// Model Definition for the Logging Configuration
1146#[derive(Debug, Serialize, Deserialize)]
1147#[serde(default)]
1148pub struct Logging {
1149 /// What level of logging should we have?
1150 pub log_level: LogLevel,
1151 /// Should we log to console?
1152 pub log_to_console: bool,
1153 /// Should we log to files on the server?
1154 pub log_to_file: bool,
1155 /// If `log_to_file: true`, what is the directory to store logs?
1156 pub log_directory: PathBuf,
1157 /// Should logs be sent to a syslog server?
1158 pub log_to_syslog: bool,
1159 /// If `log_to_syslog: true`, provide hostname/IP of the syslog server?
1160 pub syslog_host: String,
1161 /// If `log_to_syslog: true`, provide port of the syslog server?
1162 pub syslog_port: u16,
1163 /// If `log_to_syslog: true`, provide transport for syslog server?
1164 pub syslog_transport: SyslogTransport,
1165 // /// How often, in seconds, should counters log their values?
1166 // pub export_interval: int = odm.Integer(")
1167 /// Log in JSON format?
1168 pub log_as_json: bool,
1169 // /// Add a health check to core components.<br>If `true`, core components will touch this path regularly to tell the container environment it is healthy
1170 // pub heartbeat_file: str = odm.Optional(odm.Keyword(),")
1171}
1172
1173impl Default for Logging {
1174 fn default() -> Self {
1175 Self {
1176 log_directory: "/var/log/assemblyline/".into(),
1177 log_as_json: true,
1178 log_level: LogLevel::Info,
1179 log_to_console: true,
1180 log_to_file: false,
1181 log_to_syslog: false,
1182 syslog_host: "localhost".to_owned(),
1183 syslog_port: 514,
1184 syslog_transport: SyslogTransport::Tcp,
1185 // export_interval: 5,
1186 // heartbeat_file: "/tmp/heartbeat"
1187 }
1188 }
1189}
1190
1191// SERVICE_CATEGORIES = [
1192// 'Antivirus',
1193// 'Dynamic Analysis',
1194// 'External',
1195// 'Extraction',
1196// 'Filtering',
1197// 'Internet Connected',
1198// 'Networking',
1199// 'Static Analysis',
1200// ]
1201
1202fn default_service_stages() -> Vec<String> {
1203 vec![
1204 "FILTER".to_string(),
1205 "EXTRACT".to_string(),
1206 "CORE".to_string(),
1207 "SECONDARY".to_string(),
1208 "POST".to_string(),
1209 "REVIEW".to_string(),
1210 ]
1211}
1212
1213#[derive(SerializeDisplay, DeserializeFromStr, strum::Display, strum::EnumString, Debug, Clone, Copy, PartialEq, Eq)]
1214// #[metadata_type(ElasticMeta)]
1215#[strum(serialize_all = "lowercase")]
1216pub enum SafelistHashTypes {
1217 Sha1, Sha256, Md5
1218}
1219
1220#[derive(SerializeDisplay, DeserializeFromStr, strum::Display, strum::EnumString, Debug, Clone, Copy)]
1221#[strum(serialize_all = "lowercase")]
1222pub enum RegistryTypes {
1223 Docker,
1224 Harbor
1225}
1226
1227/// Service's Safelisting Configuration
1228// @odm.model(index=False, store=False, description="")
1229#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1230#[serde(default)]
1231pub struct ServiceSafelist {
1232 /// Should services be allowed to check extracted files against safelist?
1233 pub enabled: bool,
1234 /// Types of file hashes used for safelist checks
1235 pub hash_types: Vec<SafelistHashTypes>,
1236 /// Should the Safelist service always run on extracted files?
1237 pub enforce_safelist_service: bool,
1238}
1239
1240impl Default for ServiceSafelist {
1241 fn default() -> Self {
1242 Self {
1243 enabled: true,
1244 hash_types: vec![SafelistHashTypes::Sha1, SafelistHashTypes::Sha256],
1245 enforce_safelist_service: false,
1246 }
1247 }
1248}
1249
1250// @odm.model(index=False, store=False, description="Pre-Configured Registry Details for Services")
1251// class ServiceRegistry(odm.Model):
1252// name: str = odm.Keyword(description="Name of container registry")
1253// type: str = odm.Enum(values=REGISTRY_TYPES, default='docker', description="Type of container registry")
1254// username: str = odm.Keyword(description="Username for container registry")
1255// password: str = odm.Keyword(description="Password for container registry")
1256
1257
1258/// Services Configuration
1259#[derive(Debug, Clone, Serialize, Deserialize)]
1260#[serde(default)]
1261pub struct Services {
1262// categories: List[str] = odm.List(odm.Keyword(), description="List of categories a service can be assigned to")
1263// default_timeout: int = odm.Integer(description="Default service timeout time in seconds")
1264 /// List of execution stages a service can be assigned to
1265 pub stages: Vec<String>,
1266 /// Substitution variables for image paths (for custom registry support)
1267 // pub image_variables: Dict[str, str] = odm.Mapping(odm.Keyword(default=''), ),
1268 /// Similar to `image_variables` but only applied to the updater. Intended for use with local registries.
1269 // pub update_image_variables: Dict[str, str] = odm.Mapping(odm.Keyword(default=''), ),
1270 /// Default update channel to be used for new services
1271 pub preferred_update_channel: String,
1272 /// Allow fetching container images from insecure registries
1273 pub allow_insecure_registry: bool,
1274 /// Global registry type to be used for fetching updates for a service (overridable by a service)
1275 pub preferred_registry_type: RegistryTypes,
1276 /// Global preference that controls if services should be privileged to communicate with core infrastucture
1277 pub prefer_service_privileged: bool,
1278 /// How much CPU do we want to reserve relative to the service's request?<br> At `1`, a service's full CPU request will be reserved for them.<br> At `0` (only for very small appliances/dev boxes), the service's CPU will be limited ""but no CPU will be reserved allowing for more flexible scheduling of containers.
1279 pub cpu_reservation: f64,
1280 pub safelist: ServiceSafelist,
1281// registries = odm.Optional(odm.List(odm.Compound(ServiceRegistry)), description="Global set of registries for services")
1282// service_account = odm.optional(odm.keyword(description="Service account to use for pods in kubernetes where the service does not have one configured."))
1283}
1284
1285impl Default for Services {
1286 fn default() -> Self {
1287 Self {
1288 // "categories": SERVICE_CATEGORIES,
1289 // "default_timeout": 60,
1290 stages: default_service_stages(),
1291 // "image_variables": {},
1292 // "update_image_variables": {},
1293 preferred_update_channel: "stable".to_string(),
1294 preferred_registry_type: RegistryTypes::Docker,
1295 prefer_service_privileged: false,
1296 allow_insecure_registry: false,
1297 cpu_reservation: 0.25,
1298 safelist: Default::default(),
1299 // "registries": []
1300 }
1301 }
1302}
1303
1304// @odm.model(index=False, store=False, description="System Configuration")
1305// class System(odm.Model):
1306// constants: str = odm.Keyword(description="Module path to the assemblyline constants")
1307// organisation: str = odm.Text(description="Organisation acronym used for signatures")
1308// type: str = odm.Enum(values=['production', 'staging', 'development'], description="Type of system")
1309
1310
1311// DEFAULT_SYSTEM = {
1312// "constants": "assemblyline.common.constants",
1313// "organisation": "ACME",
1314// "type": 'production',
1315// }
1316
1317
1318// @odm.model(index=False, store=False, description="Statistics")
1319// class Statistics(odm.Model):
1320// alert: List[str] = odm.List(odm.Keyword(),
1321// description="Fields used to generate statistics in the Alerts page")
1322// submission: List[str] = odm.List(odm.Keyword(),
1323// description="Fields used to generate statistics in the Submissions page")
1324
1325
1326// DEFAULT_STATISTICS = {
1327// "alert": [
1328// 'al.attrib',
1329// 'al.av',
1330// 'al.behavior',
1331// 'al.domain',
1332// 'al.ip',
1333// 'al.yara',
1334// 'file.name',
1335// 'file.md5',
1336// 'owner'
1337// ],
1338// "submission": [
1339// 'params.submitter'
1340// ]
1341// }
1342
1343
1344// @odm.model(index=False, store=False, description="Alerting Metadata")
1345// class AlertingMeta(odm.Model):
1346// important: List[str] = odm.List(odm.Keyword(), description="Metadata keys that are considered important")
1347// subject: List[str] = odm.List(odm.Keyword(), description="Metadata keys that refer to an email's subject")
1348// url: List[str] = odm.List(odm.Keyword(), description="Metadata keys that refer to a URL")
1349
1350
1351// DEFAULT_ALERTING_META = {
1352// 'important': [
1353// 'original_source',
1354// 'protocol',
1355// 'subject',
1356// 'submitted_url',
1357// 'source_url',
1358// 'url',
1359// 'web_url',
1360// 'from',
1361// 'to',
1362// 'cc',
1363// 'bcc',
1364// 'ip_src',
1365// 'ip_dst',
1366// 'source'
1367// ],
1368// 'subject': [
1369// 'subject'
1370// ],
1371// 'url': [
1372// 'submitted_url',
1373// 'source_url',
1374// 'url',
1375// 'web_url'
1376// ]
1377
1378// }
1379
1380
1381// @odm.model(index=False, store=False, description="Target definition of an external link")
1382// class ExternalLinksTargets(odm.Model):
1383// type: str = odm.Enum(values=['metadata', 'tag', 'hash'], description="Type of external link target")
1384// key: str = odm.Keyword(description="Key that it can be used against")
1385
1386
1387// @odm.model(index=False, store=False, description="External links that specific metadata and tags can pivot to")
1388// class ExternalLinks(odm.Model):
1389// allow_bypass: bool = odm.boolean(
1390// default=False,
1391// description="If the classification of the item is higher than the max_classificaiton, can we let the user "
1392// "bypass the check and still query the external link?")
1393// name: str = odm.Keyword(description="Name of the link")
1394// double_encode: bool = odm.boolean(default=False, description="Should the replaced value be double encoded?")
1395// classification = odm.Optional(
1396// odm.ClassificationString(description="Minimum classification the user must have to see this link"))
1397// max_classification = odm.Optional(
1398// odm.ClassificationString(description="Maximum classification of data that may be handled by the link"))
1399// replace_pattern: str = odm.Keyword(
1400// description="Pattern that will be replaced in the URL with the metadata or tag value")
1401// targets: List[ExternalLinksTargets] = odm.List(
1402// odm.Compound(ExternalLinksTargets),
1403// default=[],
1404// description="List of external sources to query")
1405// url: str = odm.Keyword(description="URL to redirect to")
1406
1407
1408// EXAMPLE_EXTERNAL_LINK_VT = {
1409// # This is an example on how this would work with VirusTotal
1410// "name": "VirusTotal",
1411// "replace_pattern": "{REPLACE}",
1412// "targets": [
1413// {"type": "tag", "key": "network.static.uri"},
1414// {"type": "tag", "key": "network.dynamic.uri"},
1415// {"type": "metadata", "key": "submitted_url"},
1416// {"type": "hash", "key": "md5"},
1417// {"type": "hash", "key": "sha1"},
1418// {"type": "hash", "key": "sha256"},
1419// ],
1420// "url": "https://www.virustotal.com/gui/search/{REPLACE}",
1421// "double_encode": True,
1422// # "classification": "TLP:CLEAR",
1423// # "max_classification": "TLP:CLEAR",
1424// }
1425
1426// EXAMPLE_EXTERNAL_LINK_MB_SHA256 = {
1427// # This is an example on how this would work with Malware Bazaar
1428// "name": "MalwareBazaar",
1429// "replace_pattern": "{REPLACE}",
1430// "targets": [
1431// {"type": "hash", "key": "sha256"},
1432// ],
1433// "url": "https://bazaar.abuse.ch/sample/{REPLACE}/",
1434// # "classification": "TLP:CLEAR",
1435// # "max_classification": "TLP:CLEAR",
1436// }
1437
1438
1439// @odm.model(index=False, store=False, description="Connection details for external systems/data sources.")
1440// class ExternalSource(odm.Model):
1441// name: str = odm.Keyword(description="Name of the source.")
1442// classification = odm.Optional(
1443// odm.ClassificationString(
1444// description="Minimum classification applied to information from the source"
1445// " and required to know the existance of the source."))
1446// max_classification = odm.Optional(
1447// odm.ClassificationString(description="Maximum classification of data that may be handled by the source"))
1448// url: str = odm.Keyword(description="URL of the upstream source's lookup service.")
1449
1450
1451// EXAMPLE_EXTERNAL_SOURCE_VT = {
1452// # This is an example on how this would work with VirusTotal
1453// "name": "VirusTotal",
1454// "url": "vt-lookup.namespace.svc.cluster.local",
1455// "classification": "TLP:CLEAR",
1456// "max_classification": "TLP:CLEAR",
1457// }
1458
1459// EXAMPLE_EXTERNAL_SOURCE_MB = {
1460// # This is an example on how this would work with Malware Bazaar
1461// "name": "Malware Bazaar",
1462// "url": "mb-lookup.namespace.scv.cluster.local",
1463// "classification": "TLP:CLEAR",
1464// "max_classification": "TLP:CLEAR",
1465// }
1466
1467
1468/// UI Configuration
1469#[derive(Serialize, Deserialize)]
1470#[serde(default)]
1471pub struct UI {
1472// alerting_meta: AlertingMeta = odm.Compound(AlertingMeta, default=DEFAULT_ALERTING_META,description="Alerting metadata fields")
1473 /// Allow user to tell in advance the system that a file is malicious?
1474 pub allow_malicious_hinting: bool,
1475// allow_raw_downloads: bool = odm.Boolean(description="Allow user to download raw files?")
1476// allow_zip_downloads: bool = odm.Boolean(description="Allow user to download files as password protected ZIPs?")
1477// allow_replay: bool = odm.Boolean(description="Allow users to request replay on another server?")
1478// allow_url_submissions: bool = odm.Boolean(description="Allow file submissions via url?")
1479// audit: bool = odm.Boolean(description="Should API calls be audited and saved to a separate log file?")
1480// banner: Dict[str, str] = odm.Optional(odm.Mapping(odm.Keyword()), description="Banner message display on the main page (format: {<language_code>: message})")
1481// banner_level: str = odm.Enum(values=["info", "warning", "success", "error"],description="Banner message level")
1482// debug: bool = odm.Boolean(description="Enable debugging?")
1483// discover_url: str = odm.Optional(odm.Keyword(), description="Discover URL")
1484// download_encoding = odm.Enum(values=["raw", "cart"], description="Which encoding will be used for downloads?")
1485// email: str = odm.Optional(odm.Email(), description="Assemblyline admins email address")
1486 /// Enforce the user's quotas?
1487 pub enforce_quota: bool,
1488// external_links: List[ExternalLinks] = odm.List(odm.Compound(ExternalLinks),description="List of external pivot links")
1489// external_sources: List[ExternalSource] = odm.List(odm.Compound(ExternalSource), description="List of external sources to query")
1490// fqdn: str = odm.Text(description="Fully qualified domain name to use for the 2-factor authentication validation")
1491// ingest_max_priority: int = odm.Integer(description="Maximum priority for ingest API")
1492// read_only: bool = odm.Boolean(description="Turn on read only mode in the UI")
1493// read_only_offset: str = odm.Keyword(default="", description="Offset of the read only mode for all paging and searches")
1494// rss_feeds: List[str] = odm.List(odm.Keyword(), default=[], description="List of RSS feeds to display on the UI")
1495// services_feed: str = odm.Keyword(description="Feed of all the services available on AL")
1496// secret_key: str = odm.Keyword(description="Flask secret key to store cookies, etc.")
1497// session_duration: int = odm.Integer(description="Duration of the user session before the user has to login again")
1498// statistics: Statistics = odm.Compound(Statistics, default=DEFAULT_STATISTICS, description="Statistics configuration")
1499// tos: str = odm.Optional(odm.Text(), description="Terms of service")
1500// tos_lockout: bool = odm.Boolean(description="Lock out user after accepting the terms of service?")
1501// tos_lockout_notify: List[str] = odm.Optional(odm.List(odm.Keyword()), description="List of admins to notify when a user gets locked out")
1502// url_submission_headers: Dict[str, str] = odm.Optional(odm.Mapping(odm.Keyword()), description="Headers used by the url_download method")
1503// url_submission_proxies: Dict[str, str] = odm.Optional(odm.Mapping(odm.Keyword()), description="Proxy used by the url_download method")
1504// url_submission_timeout: int = odm.Integer(default=15, description="Request timeout for fetching URLs")
1505// validate_session_ip: bool = odm.Boolean(description="Validate if the session IP matches the IP the session was created from")
1506// validate_session_useragent: bool = odm.Boolean(description="Validate if the session useragent matches the useragent the session was created with")
1507}
1508
1509impl Default for UI {
1510 fn default() -> Self {
1511 Self {
1512// DEFAULT_UI = {
1513// "alerting_meta": DEFAULT_ALERTING_META,
1514 allow_malicious_hinting: false,
1515// "allow_raw_downloads": True,
1516// "allow_zip_downloads": True,
1517// "allow_replay": False,
1518// "allow_url_submissions": True,
1519// "audit": True,
1520// "banner": None,
1521// "banner_level": 'info',
1522// "debug": False,
1523// "discover_url": None,
1524// "download_encoding": "cart",
1525// "email": None,
1526 enforce_quota: true,
1527// "external_links": [],
1528// "external_sources": [],
1529// "fqdn": "localhost",
1530// "ingest_max_priority": 250,
1531// "read_only": False,
1532// "read_only_offset": "",
1533// "rss_feeds": [
1534// "https://alpytest.blob.core.windows.net/pytest/stable.json",
1535// "https://alpytest.blob.core.windows.net/pytest/services.json",
1536// "https://alpytest.blob.core.windows.net/pytest/blog.json"
1537// ],
1538// "services_feed": "https://alpytest.blob.core.windows.net/pytest/services.json",
1539// "secret_key": "This is the default flask secret key... you should change this!",
1540// "session_duration": 3600,
1541// "statistics": DEFAULT_STATISTICS,
1542// "tos": None,
1543// "tos_lockout": False,
1544// "tos_lockout_notify": None,
1545// "url_submission_headers": {
1546// "User-Agent": "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko)"
1547// " Chrome/110.0.0.0 Safari/537.36"
1548// },
1549// "url_submission_proxies": {},
1550// "validate_session_ip": True,
1551// "validate_session_useragent": True,
1552// }
1553 }
1554 }
1555}
1556
1557// # Options regarding all submissions, regardless of their input method
1558// @odm.model(index=False, store=False)
1559// class TagTypes(odm.Model):
1560// attribution: List[str] = odm.List(odm.Keyword(), description="Attibution tags")
1561// behavior: List[str] = odm.List(odm.Keyword(), description="Behaviour tags")
1562// ioc: List[str] = odm.List(odm.Keyword(), description="IOC tags")
1563
1564
1565// DEFAULT_TAG_TYPES = {
1566// 'attribution': [
1567// 'attribution.actor',
1568// 'attribution.campaign',
1569// 'attribution.exploit',
1570// 'attribution.implant',
1571// 'attribution.family',
1572// 'attribution.network',
1573// 'av.virus_name',
1574// 'file.config',
1575// 'technique.obfuscation',
1576// ],
1577// 'behavior': [
1578// 'file.behavior'
1579// ],
1580// 'ioc': [
1581// 'network.email.address',
1582// 'network.static.ip',
1583// 'network.static.domain',
1584// 'network.static.uri',
1585// 'network.dynamic.ip',
1586// 'network.dynamic.domain',
1587// 'network.dynamic.uri',
1588
1589// ]
1590// }
1591
1592
1593// @odm.model(index=False, store=False, description="A source entry for the sha256 downloader")
1594// class Sha256Source(odm.Model):
1595// name: str = odm.Keyword(description="Name of the sha256 source")
1596// classification = odm.Optional(
1597// odm.ClassificationString(
1598// description="Minimum classification applied to the downloaded "
1599// "files and required to know the existance of the source."))
1600// data: str = odm.Optional(odm.Keyword(description="Data block sent during the URL call (Uses replace pattern)"))
1601// failure_pattern: str = odm.Optional(odm.Keyword(
1602// description="Pattern to find as a failure case when API return 200 OK on failures..."))
1603// method: str = odm.Enum(values=['GET', 'POST'], default="GET", description="Method used to call the URL")
1604// url: str = odm.Keyword(description="Url to fetch the file via SHA256 from (Uses replace pattern)")
1605// replace_pattern: str = odm.Keyword(description="Pattern to replace in the URL with the SHA256")
1606// headers: Dict[str, str] = odm.Mapping(odm.Keyword(), default={},
1607// description="Headers used to connect to the URL")
1608// proxies: Dict[str, str] = odm.Mapping(odm.Keyword(), default={},
1609// description="Proxy used to connect to the URL")
1610// verify: bool = odm.Boolean(default=True, description="Should the download function Verify SSL connections?")
1611
1612
1613// EXAMPLE_SHA256_SOURCE_VT = {
1614// # This is an example on how this would work with VirusTotal
1615// "name": "VirusTotal",
1616// "url": r"https://www.virustotal.com/api/v3/files/{SHA256}/download",
1617// "replace_pattern": r"{SHA256}",
1618// "headers": {"x-apikey": "YOUR_KEY"},
1619// }
1620
1621// EXAMPLE_SHA256_SOURCE_MB = {
1622// # This is an example on how this would work with Malware Bazaar
1623// "name": "Malware Bazaar",
1624// "url": r"https://mb-api.abuse.ch/api/v1/",
1625// "headers": {"Content-Type": "application/x-www-form-urlencoded"},
1626// "data": r"query=get_file&sha256_hash={SHA256}",
1627// "method": "POST",
1628// "replace_pattern": r"{SHA256}",
1629// "failure_pattern": '"query_status": "file_not_found"'
1630// }
1631
1632
1633// @odm.model(index=False, store=False,
1634// description="Minimum score value to get the specified verdict, otherwise the file is considered safe.")
1635// class Verdicts(odm.Model):
1636// info: int = odm.Integer(description="Minimum score for the verdict to be Informational.")
1637// suspicious: int = odm.Integer(description="Minimum score for the verdict to be Suspicious.")
1638// highly_suspicious: int = odm.Integer(description="Minimum score for the verdict to be Highly Suspicious.")
1639// malicious: int = odm.Integer(description="Minimum score for the verdict to be Malicious.")
1640
1641
1642// DEFAULT_VERDICTS = {
1643// 'info': 0,
1644// 'suspicious': 300,
1645// 'highly_suspicious': 700,
1646// 'malicious': 1000
1647// }
1648
1649#[derive(SerializeDisplay, DeserializeFromStr, strum::Display, strum::EnumString, Debug, Clone, Copy, PartialEq, Eq)]
1650// #[metadata_type(ElasticMeta)]
1651#[strum(serialize_all = "lowercase")]
1652pub enum TemporaryKeyType {
1653 Union,
1654 Overwrite,
1655}
1656
1657impl Default for TemporaryKeyType {
1658 fn default() -> Self {
1659 Self::Overwrite
1660 }
1661}
1662
1663
1664/// Default values for parameters for submissions that may be overridden on a per submission basis
1665#[derive(Serialize, Deserialize)]
1666#[serde(default)]
1667pub struct Submission {
1668 // /// How many extracted files may be added to a submission?
1669 // pub default_max_extracted: u32,
1670 // /// How many supplementary files may be added to a submission?
1671 // pub default_max_supplementary: u32,
1672 // /// Number of days submissions will remain in the system by default
1673 // pub dtl: u32,
1674 /// Number of days emptyresult will remain in the system
1675 pub emptyresult_dtl: u32,
1676 /// Maximum number of days submissions will remain in the system
1677 pub max_dtl: u32,
1678 /// Maximum files extraction depth
1679 pub max_extraction_depth: u32,
1680 /// Maximum size for files submitted in the system
1681 pub max_file_size: u64,
1682 /// Maximum length for each metadata values
1683 pub max_metadata_length: u32,
1684 /// Maximum length for each temporary data values
1685 pub max_temp_data_length: u32,
1686 // /// List of external source to fetch file via their SHA256 hashes
1687 // pub sha256_sources: Vec<Sha256Source>,
1688 // /// Tag types that show up in the submission summary
1689 // pub tag_types: TagTypes,
1690 // /// Minimum score value to get the specified verdict.
1691 // pub verdicts: Verdicts,
1692
1693 /// Set the operation that will be used to update values using this key in the temporary submission data.
1694 pub default_temporary_keys: HashMap<String, TemporaryKeyType>,
1695 pub temporary_keys: HashMap<String, TemporaryKeyType>,
1696}
1697
1698impl Default for Submission {
1699 fn default() -> Self {
1700 Self {
1701 // default_max_extracted: 500,
1702 // default_max_supplementary: 500,
1703 // dtl: 30,
1704 emptyresult_dtl: 5,
1705 max_dtl: 0,
1706 max_extraction_depth: 6,
1707 max_file_size: 104857600,
1708 max_metadata_length: 4096,
1709 max_temp_data_length: 4096,
1710 // sha256_sources: Default::default(),
1711 // tag_types: Default::default(),
1712 // verdicts: Default::default()
1713 default_temporary_keys: [
1714 ("passwords".to_owned(), TemporaryKeyType::Union),
1715 ("email_body".to_owned(), TemporaryKeyType::Union),
1716 ].into_iter().collect(),
1717 temporary_keys: Default::default()
1718 }
1719 }
1720}
1721
1722
1723// @odm.model(index=False, store=False, description="Configuration for connecting to a retrohunt service.")
1724// class Retrohunt(odm.Model):
1725// enabled = odm.Boolean(default=False, description="Is the Retrohunt functionnality enabled on the frontend")
1726// dtl: int = odm.Integer(default=30, description="Number of days retrohunt jobs will remain in the system by default")
1727// max_dtl: int = odm.Integer(
1728// default=0, description="Maximum number of days retrohunt jobs will remain in the system")
1729// url = odm.Keyword(description="Base URL for service API")
1730// api_key = odm.Keyword(description="Service API Key")
1731// tls_verify = odm.Boolean(description="Should tls certificates be verified", default=True)
1732
1733
1734/// Assemblyline Deployment Configuration
1735#[derive(Serialize, Deserialize, Default)]
1736#[serde(default)]
1737pub struct Config {
1738 /// Classification information
1739 pub classification: Classification,
1740 /// Authentication module configuration
1741 pub auth: Auth,
1742 /// Core component configuration
1743 pub core: Core,
1744 /// Datastore configuration
1745 pub datastore: Datastore,
1746 // /// Datasources configuration
1747 // #[serde(default = "default_datasources")]
1748 // pub datasources: HashMap<String, Datasource>,
1749 /// Filestore configuration
1750 pub filestore: Filestore,
1751 /// Logging configuration
1752 pub logging: Logging,
1753 /// Service configuration
1754 pub services: Services,
1755 // /// System configuration
1756 // pub system: System,
1757 /// UI configuration parameters
1758 pub ui: UI,
1759 /// Options for how submissions will be processed
1760 pub submission: Submission,
1761 // /// Retrohunt configuration for the frontend and server
1762 // pub retrohunt: Option<Retrohunt>,
1763}