-
Notifications
You must be signed in to change notification settings - Fork 27
140 lines (122 loc) · 6.35 KB
/
Copy pathvalidate.yml
File metadata and controls
140 lines (122 loc) · 6.35 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
name: Validate Queries
on: [push, pull_request]
jobs:
validate-queries:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.x'
- name: Install dependencies
run: pip install pyyaml
- name: Validate YAML structure
run: |
python -c "
import yaml
import os
import sys
from pathlib import Path
required_fields = ['name', 'cql']
valid_log_sources = ['Endpoint', 'Network', 'Identity', 'Cloud', 'Mail', 'Other']
valid_tags = ['Hunting', 'Monitoring', 'Detection']
valid_modules = ['Insight', 'Identity', 'Spotlight', 'CSPM / ASPM / DSPM', 'Data Protection', 'IT Automation']
success_count = 0
failure_count = 0
def check_type(file_path, field_name, value, expected_type, allow_none=False):
if allow_none and value is None:
return True
if not isinstance(value, expected_type):
print(f'❌ {file_path}: Field \"{field_name}\" must be {expected_type.__name__}, got {type(value).__name__}')
return False
return True
def check_string_field(file_path, field_name, value, allow_empty=False):
if not check_type(file_path, field_name, value, str):
return False
if not allow_empty and not value.strip():
print(f'❌ {file_path}: Field \"{field_name}\" cannot be empty')
return False
return True
def check_list_field(file_path, field_name, value, valid_values=None, allow_empty=True):
if not check_type(file_path, field_name, value, list):
return False
if not allow_empty and len(value) == 0:
print(f'❌ {file_path}: Field \"{field_name}\" cannot be empty list')
return False
for item in value:
if not isinstance(item, str):
print(f'❌ {file_path}: All items in \"{field_name}\" must be strings, got {type(item).__name__}')
return False
if valid_values and item not in valid_values:
print(f'❌ {file_path}: Invalid value \"{item}\" in \"{field_name}\". Must be one of {valid_values}')
return False
return True
for yml_file in Path('queries').glob('*.yml'):
file_valid = True
try:
with open(yml_file) as f:
data = yaml.safe_load(f)
if not isinstance(data, dict):
print(f'❌ {yml_file}: Root must be a dictionary/object')
file_valid = False
# Check required fields
if file_valid:
for field in required_fields:
if field not in data:
print(f'❌ {yml_file}: Missing required field: {field}')
file_valid = False
elif field == 'name':
if not check_string_field(yml_file, field, data[field]):
file_valid = False
elif field == 'cql':
if not check_string_field(yml_file, field, data[field]):
file_valid = False
# Check optional string fields
if file_valid:
string_fields = ['description', 'author']
for field in string_fields:
if field in data and data[field] is not None:
if not check_string_field(yml_file, field, data[field], allow_empty=True):
file_valid = False
# Check optional list fields with validation
if file_valid and 'log_sources' in data and data['log_sources'] is not None:
if not check_list_field(yml_file, 'log_sources', data['log_sources'], valid_log_sources, allow_empty=False):
file_valid = False
if file_valid and 'tags' in data and data['tags'] is not None:
if not check_list_field(yml_file, 'tags', data['tags'], valid_tags, allow_empty=False):
file_valid = False
if file_valid and 'cs_required_modules' in data and data['cs_required_modules'] is not None:
if not check_list_field(yml_file, 'cs_required_modules', data['cs_required_modules'], valid_modules, allow_empty=False):
file_valid = False
# Check MITRE IDs with special validation
if file_valid and 'mitre_ids' in data and data['mitre_ids'] is not None:
if not check_list_field(yml_file, 'mitre_ids', data['mitre_ids'], allow_empty=False):
file_valid = False
else:
for mitre_id in data['mitre_ids']:
if not mitre_id.startswith('T'):
print(f'❌ {yml_file}: Invalid MITRE ID format: \"{mitre_id}\". Must start with \"T\"')
file_valid = False
if file_valid:
success_count += 1
else:
failure_count += 1
except yaml.YAMLError as e:
print(f'❌ {yml_file}: YAML parsing error: {e}')
failure_count += 1
except Exception as e:
print(f'❌ {yml_file}: Validation error: {e}')
failure_count += 1
# Print summary
total_files = success_count + failure_count
print(f'\\n📊 Validation Summary:')
print(f' Total files processed: {total_files}')
print(f' ✅ Successful validations: {success_count}')
print(f' ❌ Failed validations: {failure_count}')
if failure_count > 0:
print(f'\\n❌ Validation failed! {failure_count} file(s) have validation errors.')
sys.exit(1)
else:
print(f'\\n✅ All YAML files have valid structure and types!')
"