1818
1919logger = logging .getLogger (__name__ )
2020
21+
22+ # Some generator sourcetypes correspond to marketplace parser names that don't exist
23+ # as local parser directories in this repo. Provide aliases so we can still upload
24+ # a reasonable local parser when asked to sync those names.
25+ LOCAL_PARSER_ALIASES : Dict [str , str ] = {
26+ # Palo Alto Networks Firewall marketplace parser name -> local parser folder name
27+ "marketplace-paloaltonetworksfirewall-latest" : "paloalto_firewall-latest" ,
28+ }
29+
2130# Mapping from generator/source names to parser sourcetypes
2231# This maps scenario sources to their corresponding parser directory names
2332SCENARIO_SOURCE_TO_PARSER = {
4958
5059 # Network Security
5160 "darktrace" : "darktrace_darktrace_logs-latest" ,
52- "paloalto_firewall" : "paloalto_logs -latest" ,
61+ "paloalto_firewall" : "paloalto_firewall -latest" ,
5362 "f5_networks" : "f5_networks_logs-latest" ,
5463 "fortinet_fortigate" : "fortinet_fortigate_candidate_logs-latest" ,
5564 "zscaler" : "zscaler_logs-latest" ,
@@ -125,9 +134,36 @@ def get_parser_path_in_siem(self, sourcetype: str) -> str:
125134 sourcetype: The parser sourcetype (e.g., 'okta_authentication-latest')
126135
127136 Returns:
128- The parser path in SIEM (e.g., '/parsers /okta_authentication-latest')
137+ The parser path in SIEM (e.g., '/logParsers /okta_authentication-latest')
129138 """
130- return f"/parsers/{ sourcetype } "
139+ # In the Scalyr/SentinelOne config tree, log parsers are stored as JSON files
140+ # under /logParsers.
141+ leaf = sourcetype
142+ if not leaf .endswith (".json" ):
143+ leaf = f"{ leaf } .json"
144+ return f"/logParsers/{ leaf } "
145+
146+ def _local_parser_directories_for_sourcetype (self , sourcetype : str ) -> List [Path ]:
147+ local_name = LOCAL_PARSER_ALIASES .get (sourcetype , sourcetype )
148+
149+ # Handle prefixed sourcetypes produced by generator tooling (e.g., community-foo-latest)
150+ if local_name .startswith ("community-" ):
151+ leaf = local_name [len ("community-" ):]
152+ return [
153+ self .parsers_dir / "community" / leaf ,
154+ self .parsers_dir / "community_new" / leaf ,
155+ ]
156+ if local_name .startswith ("marketplace-" ):
157+ leaf = local_name [len ("marketplace-" ):]
158+ return [
159+ self .parsers_dir / "marketplace" / leaf ,
160+ ]
161+
162+ return [
163+ self .parsers_dir / "community" / local_name ,
164+ self .parsers_dir / "community_new" / local_name ,
165+ self .parsers_dir / "sentinelone" / local_name ,
166+ ]
131167
132168 def load_local_parser (self , sourcetype : str ) -> Optional [str ]:
133169 """
@@ -139,12 +175,7 @@ def load_local_parser(self, sourcetype: str) -> Optional[str]:
139175 Returns:
140176 The parser JSON content as string, or None if not found
141177 """
142- # Try community directory first
143- parser_dirs = [
144- self .parsers_dir / "community" / sourcetype ,
145- self .parsers_dir / "community_new" / sourcetype ,
146- self .parsers_dir / "sentinelone" / sourcetype ,
147- ]
178+ parser_dirs = self ._local_parser_directories_for_sourcetype (sourcetype )
148179
149180 for parser_dir in parser_dirs :
150181 if parser_dir .exists ():
@@ -168,6 +199,54 @@ def load_local_parser(self, sourcetype: str) -> Optional[str]:
168199
169200 logger .warning (f"Parser not found locally: { sourcetype } " )
170201 return None
202+
203+ def ensure_parser_for_sourcetype (
204+ self ,
205+ sourcetype : str ,
206+ config_write_token : str ,
207+ github_repo_urls : Optional [List [str ]] = None ,
208+ github_token : Optional [str ] = None ,
209+ selected_parser : Optional [Dict ] = None ,
210+ ) -> Dict [str , str ]:
211+ parser_path = self .get_parser_path_in_siem (sourcetype )
212+
213+ exists , _ = self .check_parser_exists (config_write_token , parser_path )
214+ if exists :
215+ return {
216+ "status" : "exists" ,
217+ "message" : f"Parser already exists: { parser_path } " ,
218+ }
219+
220+ parser_content = self .load_local_parser (sourcetype )
221+ from_github = False
222+
223+ if not parser_content and github_repo_urls :
224+ parser_content = self .load_parser_from_github (
225+ sourcetype = sourcetype ,
226+ repo_urls = github_repo_urls ,
227+ selected_parser = selected_parser ,
228+ github_token = github_token ,
229+ )
230+ from_github = parser_content is not None
231+
232+ if not parser_content :
233+ return {
234+ "status" : "no_parser" ,
235+ "message" : f"Parser not found locally or in GitHub repos: { sourcetype } " ,
236+ }
237+
238+ success = self .upload_parser (config_write_token , parser_path , parser_content )
239+ ok , detail = success
240+ if not ok :
241+ return {
242+ "status" : "failed" ,
243+ "message" : f"Failed to upload parser: { parser_path } ({ detail } )" ,
244+ }
245+
246+ return {
247+ "status" : "uploaded_from_github" if from_github else "uploaded" ,
248+ "message" : f"Parser uploaded successfully: { parser_path } " ,
249+ }
171250
172251 def load_parser_from_github (
173252 self ,
@@ -298,7 +377,7 @@ def upload_parser(
298377 parser_path : str ,
299378 content : str ,
300379 timeout : int = 30
301- ) -> bool :
380+ ) -> Tuple [ bool , str ] :
302381 """
303382 Upload a parser to the destination SIEM using putFile API
304383
@@ -309,7 +388,7 @@ def upload_parser(
309388 timeout: Request timeout in seconds
310389
311390 Returns:
312- True if upload succeeded, False otherwise
391+ Tuple of (success, message)
313392 """
314393 try :
315394 url = f"{ self .api_base_url } /putFile"
@@ -332,25 +411,22 @@ def upload_parser(
332411 result = response .json ()
333412 if result .get ("status" ) == "success" :
334413 logger .info (f"Parser uploaded successfully: { parser_path } " )
335- return True
414+ return True , "success"
336415 else :
337- logger .error (
338- f"Failed to upload parser { parser_path } : { result .get ('message' , 'Unknown error' )} "
339- )
340- return False
416+ msg = result .get ('message' , 'Unknown error' )
417+ logger .error (f"Failed to upload parser { parser_path } : { msg } " )
418+ return False , msg
341419 else :
342- logger .error (
343- f"Failed to upload parser { parser_path } : "
344- f"{ response .status_code } - { response .text } "
345- )
346- return False
420+ msg = f"{ response .status_code } - { response .text } "
421+ logger .error (f"Failed to upload parser { parser_path } : { msg } " )
422+ return False , msg
347423
348424 except requests .exceptions .Timeout :
349425 logger .error (f"Timeout uploading parser: { parser_path } " )
350- return False
426+ return False , "timeout"
351427 except Exception as e :
352428 logger .error (f"Error uploading parser { parser_path } : { e } " )
353- return False
429+ return False , str ( e )
354430
355431 def ensure_parsers_for_sources (
356432 self ,
@@ -457,9 +533,9 @@ def ensure_parsers_for_sources(
457533 continue
458534
459535 # Upload the parser
460- success = self .upload_parser (config_write_token , parser_path , parser_content )
536+ ok , detail = self .upload_parser (config_write_token , parser_path , parser_content )
461537
462- if success :
538+ if ok :
463539 status = "uploaded_from_github" if from_github else "uploaded"
464540 source_label = "GitHub" if from_github else "local"
465541 results [source ] = {
@@ -471,7 +547,7 @@ def ensure_parsers_for_sources(
471547 results [source ] = {
472548 "status" : "failed" ,
473549 "sourcetype" : actual_sourcetype ,
474- "message" : f"Failed to upload parser: { parser_path } "
550+ "message" : f"Failed to upload parser: { parser_path } ( { detail } ) "
475551 }
476552
477553 return results
0 commit comments