@@ -148,24 +148,32 @@ def build_dataframes(src: MemoryStore, domain: str) -> Dict:
148148
149149
150150def build_ds_an_lg_relationships (dataframes : Dict ) -> Dict [str , pd .DataFrame ]:
151- """Build sheets for ds-an-lg.xlsx using existing relationship tables."""
152- # Use existing DetectionStrategy -> Analytics relationship table
153- ds_an = dataframes ["detectionstrategies" ].get ("detectionstrategies-analytic" , pd .DataFrame ())
151+ """Build detection-mappings.xlsx with a single DS → Analytic → LogSource sheet."""
154152
155- # Use existing Analytics -> LogSource relationship table
156- an_lg = dataframes ["analytics" ].get ("analytic-logsource" , pd .DataFrame ())
153+ ds_an = dataframes ["detectionstrategies" ].get (
154+ "detectionstrategies-analytic" , pd .DataFrame ()
155+ )
157156
158- # Use existing Analytics -> Detection Strategy relationship table
159- an_ds = dataframes ["analytics" ].get ("analytic-detectionstrategy" , pd .DataFrame ())
157+ an_ls = dataframes ["analytics" ].get (
158+ "analytic-logsource" , pd .DataFrame ()
159+ )
160+
161+ if ds_an .empty or an_ls .empty :
162+ combined = pd .DataFrame ()
163+ else :
164+ combined = ds_an .merge (
165+ an_ls ,
166+ on = ["analytic_id" , "analytic_name" , "platforms" ],
167+ how = "left" ,
168+ )
160169
161170 return {
162- "detectionstrategy_to_analytics" : ds_an ,
163- "analytics_to_logsources" : an_lg ,
164- "analytics_to_detectionstrategy" : an_ds ,
171+ "ds_an_ls" : combined
165172 }
166173
167174
168- def write_excel (dataframes : Dict , domain : str , version : Optional [str ] = None , output_dir : str = "." ) -> List :
175+
176+ def write_excel (dataframes : Dict , domain : str , src : MemoryStore , version : Optional [str ] = None , output_dir : str = "." ) -> List :
169177 """Given a set of dataframes from build_dataframes, write the ATT&CK dataset to output directory.
170178
171179 Parameters
@@ -174,6 +182,9 @@ def write_excel(dataframes: Dict, domain: str, version: Optional[str] = None, ou
174182 A dictionary of pandas dataframes as built by build_dataframes()
175183 domain : str
176184 Domain of ATT&CK the dataframes correspond to, e.g "enterprise-attack"
185+ src : stix2.MemoryStore
186+ A STIX bundle containing ATT&CK data for a domain already loaded into memory.
187+ Mutually exclusive with `remote` and `stix_file`.
177188 version : str, optional
178189 The version of ATT&CK the dataframes correspond to, e.g "v8.1".
179190 If omitted, the output files will not be labelled with the version number, by default None
@@ -199,6 +210,10 @@ def write_excel(dataframes: Dict, domain: str, version: Optional[str] = None, ou
199210 os .makedirs (output_directory )
200211 # master dataset file
201212 master_fp = os .path .join (output_directory , f"{ domain_version_string } .xlsx" )
213+
214+ ds_an_ls_df = stixToDf .detectionStrategiesAnalyticsLogSourcesDf (src )
215+ add_ds_an_ls_to = {"detectionstrategies" , "analytics" , "datacomponents" }
216+
202217 with pd .ExcelWriter (path = master_fp , engine = "xlsxwriter" ) as master_writer :
203218 # master list of citations
204219 citations = pd .DataFrame ()
@@ -217,6 +232,10 @@ def write_excel(dataframes: Dict, domain: str, version: Optional[str] = None, ou
217232 for sheet_name in object_data :
218233 logger .debug (f"Writing sheet to { fp } : { sheet_name } " )
219234 object_data [sheet_name ].to_excel (object_writer , sheet_name = sheet_name , index = False )
235+
236+ # Write Detection strategy - Analytics - Log sources file
237+ if object_type in add_ds_an_ls_to and isinstance (ds_an_ls_df , pd .DataFrame ) and not ds_an_ls_df .empty :
238+ ds_an_ls_df .to_excel (object_writer , sheet_name = "detection mappings" , index = False )
220239 written_files .append (fp )
221240
222241 # add citations to master citations list
@@ -303,6 +322,8 @@ def write_excel(dataframes: Dict, domain: str, version: Optional[str] = None, ou
303322
304323 written_files .append (fp )
305324
325+ if isinstance (ds_an_ls_df , pd .DataFrame ) and not ds_an_ls_df .empty :
326+ ds_an_ls_df .to_excel (master_writer , sheet_name = "detection mappings" , index = False )
306327 # remove duplicate citations and add sheet to master file
307328 logger .debug (f"Writing sheet to { master_fp } : citations" )
308329 citations .drop_duplicates (subset = "reference" , ignore_index = True ).sort_values ("reference" ).to_excel (
@@ -311,17 +332,6 @@ def write_excel(dataframes: Dict, domain: str, version: Optional[str] = None, ou
311332
312333 written_files .append (master_fp )
313334
314- # Write Detection strategy - Analytics - Log sources file
315- ds_an_lg_frames = build_ds_an_lg_relationships (dataframes )
316- ds_an_lg_fp = os .path .join (output_directory , f"{ domain_version_string } -detectionstrategy-analytic-logsources.xlsx" )
317-
318- with pd .ExcelWriter (ds_an_lg_fp ) as rel_writer :
319- for sheet_name , df in ds_an_lg_frames .items ():
320- if not df .empty :
321- df .to_excel (rel_writer , sheet_name = sheet_name , index = False )
322-
323- written_files .append (ds_an_lg_fp )
324-
325335 for thefile in written_files :
326336 logger .info (f"Excel file created: { thefile } " )
327337 return written_files
@@ -398,7 +408,7 @@ def export(
398408 return
399409
400410 dataframes = build_dataframes (src = mem_store , domain = domain )
401- write_excel (dataframes = dataframes , domain = domain , version = version , output_dir = output_dir )
411+ write_excel (dataframes = dataframes , domain = domain , src = mem_store , version = version , output_dir = output_dir )
402412
403413
404414def main ():
@@ -410,7 +420,7 @@ def main():
410420 "-domain" ,
411421 type = str ,
412422 choices = ["enterprise-attack" , "mobile-attack" , "ics-attack" ],
413- default = "enterprise -attack" ,
423+ default = "ics -attack" ,
414424 help = "which domain of ATT&CK to convert" ,
415425 )
416426 parser .add_argument (
0 commit comments