@@ -41,23 +41,148 @@ async function getPrebuiltDetectionRules(
4141 tagSummaries : Map < string , TagSummary >
4242) {
4343 let count = 0 ;
44+ type Technique = {
45+ id : string ;
46+ name : string ;
47+ reference : string ;
48+ subtechnique ?: { id : string ; reference : string } [ ] ;
49+ } ;
50+
51+ type Tactic = {
52+ id : string ;
53+ name : string ;
54+ reference : string ;
55+ } ;
56+
57+ type Threat = {
58+ framework : string ;
59+ technique ?: Technique [ ] ;
60+ tactic ?: Tactic ;
61+ } ;
62+
63+ const convertHuntMitre = function ( mitreData : string [ ] ) : Threat [ ] {
64+ const threat : Threat [ ] = [ ] ;
65+
66+ mitreData . forEach ( ( item ) => {
67+ if ( item . startsWith ( 'TA' ) ) {
68+ threat . push ( {
69+ framework : "MITRE ATT&CK" ,
70+ tactic : {
71+ id : item ,
72+ name : "" ,
73+ reference : `https://attack.mitre.org/tactics/${ item } /` ,
74+ } ,
75+ technique : [ ] , // Ensure technique is an empty array if not present
76+ } ) ;
77+ } else if ( item . startsWith ( 'T' ) ) {
78+ const parts = item . split ( '.' ) ;
79+ const techniqueId = parts [ 0 ] ;
80+ const subtechniqueId = parts [ 1 ] ;
81+
82+ const technique : Technique = {
83+ id : techniqueId ,
84+ name : "" ,
85+ reference : `https://attack.mitre.org/techniques/${ techniqueId } /` ,
86+ } ;
87+
88+ if ( subtechniqueId ) {
89+ technique . subtechnique = [
90+ {
91+ id : `${ techniqueId } .${ subtechniqueId } ` ,
92+ reference : `https://attack.mitre.org/techniques/${ techniqueId } /${ subtechniqueId } /` ,
93+ } ,
94+ ] ;
95+ }
96+
97+ // Find the last added threat with a tactic to add the technique to it
98+ const lastThreat = threat [ threat . length - 1 ] ;
99+ if ( lastThreat && lastThreat . tactic && lastThreat . technique ) {
100+ lastThreat . technique . push ( technique ) ;
101+ } else {
102+ threat . push ( {
103+ framework : "MITRE ATT&CK" ,
104+ tactic : {
105+ id : "" ,
106+ name : "" ,
107+ reference : "" ,
108+ } ,
109+ technique : [ technique ] ,
110+ } ) ;
111+ }
112+ }
113+ } ) ;
114+
115+ return threat ;
116+ } ;
117+
44118 const addRule = function ( buffer ) {
45119 const ruleContent = toml . parse ( buffer ) ;
120+
121+ // Check if ruleContent.rule and ruleContent.hunt exist
122+ const ruleId = ruleContent . rule ?. rule_id || ruleContent . hunt ?. uuid ;
123+ if ( ! ruleId ) {
124+ throw new Error ( 'Neither rule_id nor hunt.uuid is available' ) ;
125+ }
126+
127+ // Initialize ruleContent.rule and ruleContent.metadata if they are undefined
128+ ruleContent . rule = ruleContent . rule || { } ;
129+ ruleContent . metadata = ruleContent . metadata || { } ;
130+
131+ // Helper function to set default values if they do not exist
132+ const setDefault = ( obj , key , defaultValue ) => {
133+ if ( ! obj [ key ] ) {
134+ obj [ key ] = defaultValue ;
135+ }
136+ } ;
137+
138+ // Use default tags if ruleContent.rule.tags does not exist
139+ const tags = ruleContent . rule . tags || [ "Hunt Type: Hunt" ] ;
140+ setDefault ( ruleContent . rule , 'tags' , [ "Hunt Type: Hunt" ] ) ;
141+
142+ // Add a tag based on the language
143+ const language = ruleContent . rule ?. language ;
144+ if ( language ) {
145+ tags . push ( `Language: ${ language } ` ) ;
146+ }
147+
148+ // Add creation_date and updated_date if they do not exist
149+ const defaultDate = new Date ( 0 ) . toISOString ( ) ;
150+ setDefault ( ruleContent . metadata , 'creation_date' , defaultDate ) ;
151+ setDefault ( ruleContent . metadata , 'updated_date' , defaultDate ) ;
152+
153+ // Use current date as default updated_date if it does not exist
154+ const updatedDate = new Date ( ruleContent . metadata . updated_date . replace ( / \/ / g, '-' ) ) ;
155+
156+ // Use hunt.name if rule.name does not exist
157+ const ruleName = ruleContent . rule . name || ruleContent . hunt . name || 'Unknown Rule' ;
158+
159+ // Set other default values if they do not exist
160+ setDefault ( ruleContent . metadata , 'integration' , ruleContent . hunt ?. integration ) ;
161+ setDefault ( ruleContent . rule , 'query' , ruleContent . hunt ?. query ) ;
162+ setDefault ( ruleContent . rule , 'license' , "Elastic License v2" ) ;
163+ setDefault ( ruleContent . rule , 'description' , ruleContent . hunt ?. description ) ;
164+
165+ // Convert hunt.mitre to rule.threat if hunt.mitre exists
166+ if ( ruleContent . hunt ?. mitre ) {
167+ ruleContent . rule . threat = convertHuntMitre ( ruleContent . hunt . mitre ) ;
168+ }
169+
46170 ruleSummaries . push ( {
47- id : ruleContent . rule . rule_id ,
48- name : ruleContent . rule . name ,
49- tags : ruleContent . rule . tags ,
50- updated_date : new Date (
51- ruleContent . metadata . updated_date . replace ( / \/ / g, '-' )
52- ) ,
171+ id : ruleId ,
172+ name : ruleName ,
173+ tags : tags ,
174+ updated_date : updatedDate ,
53175 } ) ;
54- for ( const t of ruleContent . rule . tags ) {
176+
177+ for ( const t of tags ) {
55178 addTagSummary ( t , tagSummaries ) ;
56179 }
180+
57181 fs . writeFileSync (
58- `${ RULES_OUTPUT_PATH } ${ ruleContent . rule . rule_id } .json` ,
182+ `${ RULES_OUTPUT_PATH } ${ ruleId } .json` ,
59183 JSON . stringify ( ruleContent )
60184 ) ;
185+
61186 count ++ ;
62187 } ;
63188
@@ -70,6 +195,7 @@ async function getPrebuiltDetectionRules(
70195 parser . on ( 'entry' , entry => {
71196 if (
72197 ( entry . path . match ( / ^ e l a s t i c - d e t e c t i o n - r u l e s - .* \/ r u l e s \/ .* \. t o m l $ / ) ||
198+ entry . path . match ( / ^ e l a s t i c - d e t e c t i o n - r u l e s - .* \/ h u n t i n g \/ .* \. t o m l $ / ) ||
73199 entry . path . match (
74200 / ^ e l a s t i c - d e t e c t i o n - r u l e s - .* \/ r u l e s _ b u i l d i n g _ b l o c k \/ .* \. t o m l $ /
75201 ) ) &&
0 commit comments