Skip to content

Commit 643f59a

Browse files
committed
sigma rules import and export
1 parent 2fca21b commit 643f59a

File tree

11 files changed

+603
-12
lines changed

11 files changed

+603
-12
lines changed
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
#!/bin/sh
22
MARKER_FILE="/usr/local/bin/.initial_setup_done"
3+
rm -rf /var/www/html/var/cache
4+
rm -rf /var/www/html/public/bundles
5+
composer install
36
if [ ! -f "$MARKER_FILE" ]; then
47
sleep 10
58
rm -rf /var/www/html/migrations/*.php
@@ -8,4 +11,4 @@ php /var/www/html/bin/console make:migration -n
811
php /var/www/html/bin/console doctrine:migrations:migrate -n
912
touch "$MARKER_FILE"
1013
fi
11-
exec "$@"
14+
symfony server:start --allow-http --port=8000 --listen-ip='0.0.0.0'

docker-compose.yml

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,17 +36,14 @@ services:
3636
- ./config/docker-config/backend-entrypoint.sh:/usr/local/bin/backend-entrypoint.sh:ro
3737
- ./sentinel-kit_server_backend:/var/www/html:delegated
3838
- ./sentinel-kit_server_backend/public:/var/www/html/public:delegated
39+
- ./config/sigma_ruleset:/detection-rules/sigma
3940
- sentinel-kit_certificates_jwt:/var/www/html/config/jwt
4041
- sentinel-kit_server_backend_vendor_cache:/var/www/html/vendor
4142
- sentinel-kit_server_backend_var_cache:/var/www/html/var
4243
stdin_open: true
4344
tty: true
4445
command: >
45-
sh -c "symfony server:ca:install &&
46-
rm -rf /var/www/html/var/cache &&
47-
rm -rf /var/www/html/public/bundles &&
48-
composer install &&
49-
symfony server:start --allow-http --port=8000 --listen-ip='0.0.0.0'"
46+
sh -c "/usr/local/bin/backend-entrypoint.sh"
5047
networks:
5148
- sentinel-kit-network
5249
depends_on:

sentinel-kit_server_backend/.env

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ DEFAULT_URI=https://localhost
3131
#
3232
# DATABASE_URL="sqlite:///%kernel.project_dir%/var/data_%kernel.environment%.db"
3333
# DATABASE_URL="mysql://app:[email protected]:3306/app?serverVersion=8.0.32&charset=utf8mb4"
34-
DATABASE_URL="mysql://sentinel-kit_user:sentinel-kit_passwd@sentinel-kit-db-mysql:3306/sentinel-kit_db?serverVersion=9.4.0&charset=utf8"
34+
DATABASE_URL="mysql://sentinel-kit_user:sentinel-kit_passwd@sentinel-kit-db-mysql:3306/sentinel-kit_db?serverVersion=9.4.0&charset=utf8mb4"
3535
# DATABASE_URL="postgresql://app:[email protected]:5432/app?serverVersion=16&charset=utf8"
3636
###< doctrine/doctrine-bundle ###
3737

sentinel-kit_server_backend/composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"symfony/runtime": "7.3.*",
3232
"symfony/security-bundle": "7.3.*",
3333
"symfony/serializer": "7.3.*",
34+
"symfony/string": "7.3.*",
3435
"symfony/twig-bundle": "7.3.*",
3536
"symfony/validator": "7.3.*",
3637
"symfony/yaml": "7.3.*"

sentinel-kit_server_backend/config/packages/doctrine.yaml

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
doctrine:
22
dbal:
33
url: '%env(resolve:DATABASE_URL)%'
4-
5-
# IMPORTANT: You MUST configure your server version,
6-
# either here or in the DATABASE_URL env var (see .env file)
7-
#server_version: '16'
8-
94
profiling_collect_backtrace: '%kernel.debug%'
105
use_savepoints: true
6+
charset: utf8mb4
7+
default_table_options:
8+
charset: utf8mb4
9+
collate: utf8mb4_unicode_ci
10+
options:
11+
1002: "SET NAMES utf8mb4"
12+
mapping_types:
13+
enum: string
1114
orm:
1215
auto_generate_proxy_classes: true
1316
enable_lazy_ghost_objects: true
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?php
2+
3+
namespace App\Command;
4+
5+
use Symfony\Component\Console\Attribute\AsCommand;
6+
use Symfony\Component\Console\Command\Command;
7+
use Symfony\Component\Console\Input\InputInterface;
8+
use Symfony\Component\Console\Output\OutputInterface;
9+
use Symfony\Component\Console\Style\SymfonyStyle;
10+
use Doctrine\ORM\EntityManagerInterface;
11+
use Symfony\Component\String\Slugger\SluggerInterface;
12+
use Symfony\Component\Yaml\Yaml;
13+
use Symfony\Component\Yaml\Exception\ParseException;
14+
use App\Entity\SigmaRule;
15+
use App\Entity\SigmaRuleVersion;
16+
17+
#[AsCommand(
18+
name: 'app:sigma:export-rules',
19+
description: 'Exports Sigma rules from Sentinel-Kit database in the backend application to *.yml files',
20+
)]
21+
class SigmaRulesExportCommand extends Command
22+
{
23+
private $entityManager;
24+
25+
public function __construct(EntityManagerInterface $entityManager)
26+
{
27+
parent::__construct();
28+
$this->entityManager = $entityManager;
29+
}
30+
31+
protected function configure(): void
32+
{
33+
}
34+
35+
protected function execute(InputInterface $input, OutputInterface $output): int
36+
{
37+
$io = new SymfonyStyle($input, $output);
38+
39+
$directory = '/detection-rules/sigma';
40+
41+
if (!is_dir($directory)) {
42+
$io->error("Directory does not exist: $directory");
43+
return Command::FAILURE;
44+
}
45+
46+
if(count(scandir($directory)) > 2) {
47+
$io->error("Sigma rules directory is not empty. Clear it before exporting rules.");
48+
return Command::FAILURE;
49+
}
50+
51+
$rules = $this->entityManager->getRepository(SigmaRule::class)->findAllWithLatestRuleVersion();
52+
foreach($rules as $rule) {
53+
$io->writeln("Exporting rule: " . $rule->getTitle());
54+
$latestVersion = $rule->getVersions()->first();
55+
56+
if (!$latestVersion) {
57+
$io->warning("Skipping rule '{$rule->getTitle()}' because it has no versions.");
58+
continue;
59+
}
60+
61+
try {
62+
file_put_contents($directory . '/' . $rule->getFilename() . '.yml', $latestVersion->getContent());
63+
}
64+
catch(\Exception $e) {
65+
$io->error("Error exporting rule '{$rule->getTitle()}': " . $e->getMessage());
66+
}
67+
}
68+
69+
$io->success("Successfully exported " . count($rules) . " Sigma rules.");
70+
71+
return Command::SUCCESS;
72+
}
73+
}
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
<?php
2+
3+
namespace App\Command;
4+
5+
use Symfony\Component\Console\Attribute\AsCommand;
6+
use Symfony\Component\Console\Command\Command;
7+
use Symfony\Component\Console\Input\InputInterface;
8+
use Symfony\Component\Console\Output\OutputInterface;
9+
use Symfony\Component\Console\Style\SymfonyStyle;
10+
use Doctrine\ORM\EntityManagerInterface;
11+
use Symfony\Component\String\Slugger\AsciiSlugger;
12+
use Symfony\Component\Yaml\Yaml;
13+
use Symfony\Component\Yaml\Exception\ParseException;
14+
use App\Entity\SigmaRule;
15+
use App\Entity\SigmaRuleVersion;
16+
17+
#[AsCommand(
18+
name: 'app:sigma:load-rules',
19+
description: 'Loads *.yml rules files into Sentinel-Kit database in the backend application',
20+
)]
21+
class SigmaRulesLoadCommand extends Command
22+
{
23+
private $entityManager;
24+
25+
public function __construct(EntityManagerInterface $entityManager)
26+
{
27+
parent::__construct();
28+
$this->entityManager = $entityManager;
29+
}
30+
31+
protected function configure(): void
32+
{
33+
}
34+
35+
protected function execute(InputInterface $input, OutputInterface $output): int
36+
{
37+
$io = new SymfonyStyle($input, $output);
38+
39+
$directory = '/detection-rules/sigma';
40+
41+
if (!is_dir($directory)) {
42+
$io->error("Directory does not exist: $directory");
43+
return Command::FAILURE;
44+
}
45+
46+
$yamlFiles = $this->findYamlFiles($directory);
47+
48+
$processedCount = 0;
49+
$errorCount = 0;
50+
51+
foreach ($yamlFiles as $filePath) {
52+
$relativePath = str_replace($directory . DIRECTORY_SEPARATOR, '', $filePath);
53+
$io->text("Processing: " . $relativePath);
54+
55+
try {
56+
$content = file_get_contents($filePath);
57+
if ($content === false) {
58+
$io->error("Could not read file: $filePath");
59+
$errorCount++;
60+
continue;
61+
}
62+
63+
$yamlData = Yaml::parse($content);
64+
65+
if ($yamlData === null) {
66+
$io->warning("File contains no valid YAML data: $filePath");
67+
continue;
68+
}
69+
70+
$this->storeSigmaRule($content, $yamlData, $relativePath, $io);
71+
$processedCount++;
72+
} catch (ParseException $e) {
73+
$io->error("YAML parse error in file " . $relativePath . ": " . $e->getMessage());
74+
$errorCount++;
75+
} catch (\Exception $e) {
76+
$io->error("Error processing file " . $relativePath . ": " . $e->getMessage());
77+
$errorCount++;
78+
}
79+
}
80+
81+
try{
82+
$this->entityManager->flush();
83+
} catch (\Exception $e) {
84+
$io->error("Error flushing to database: " . $e->getMessage());
85+
return Command::FAILURE;
86+
}
87+
88+
$io->section("Summary");
89+
$io->text("Total files processed: $processedCount");
90+
if ($errorCount > 0) {
91+
$io->text("Files with errors: $errorCount");
92+
return Command::FAILURE;
93+
} else {
94+
$io->success("Sigma rules loaded successfully.");
95+
}
96+
97+
98+
return Command::SUCCESS;
99+
}
100+
101+
/**
102+
* Recursively find all YAML files in a directory
103+
*/
104+
private function findYamlFiles(string $directory): array
105+
{
106+
$yamlFiles = [];
107+
$iterator = new \RecursiveIteratorIterator(
108+
new \RecursiveDirectoryIterator($directory, \RecursiveDirectoryIterator::SKIP_DOTS)
109+
);
110+
111+
foreach ($iterator as $file) {
112+
if ($file->isFile() && in_array($file->getExtension(), ['yml', 'yaml'])) {
113+
$yamlFiles[] = $file->getRealPath();
114+
}
115+
}
116+
117+
return $yamlFiles;
118+
}
119+
120+
/**
121+
* Store Sigma Rule and its version into the database
122+
*/
123+
private function storeSigmaRule(string $content, array $yamlData, string $filePath,SymfonyStyle $io): void
124+
{
125+
$slugger = new AsciiSlugger();
126+
$title = '';
127+
$description = null;
128+
$filename = $slugger->slug(pathinfo($filePath, PATHINFO_FILENAME));
129+
130+
if (!empty($yamlData['title'])) {
131+
$title = $yamlData['title'];
132+
}else{
133+
$title = substr($filename, 0, strlen($filename) - 4);
134+
}
135+
136+
if (!empty($yamlData['description'])) {
137+
$description = $yamlData['description'];
138+
}
139+
140+
$r = $this->entityManager->GetRepository(SigmaRule::class)->findOneBy(['title' => $title]);
141+
if ($r) {
142+
$io->warning(sprintf('Rule with title "%s" already exists already exists in database', $title));
143+
return;
144+
}
145+
146+
$rd = $this->entityManager->getRepository(SigmaRuleVersion::class)->findOneBy(['hash' => md5($content)]);
147+
if ($rd) {
148+
$io->warning(sprintf('Rule "%s" ignored - content already exists in %s', $filePath, $title, $description));
149+
return;
150+
}
151+
152+
$rule = new SigmaRule();
153+
$rule->setFilename($filename);
154+
$rule->setTitle($title);
155+
$rule->setDescription($description);
156+
$rule->setActive(false);
157+
158+
$ruleVersion = new SigmaRuleVersion();
159+
$ruleVersion->setContent($content);
160+
$rule->addVersion($ruleVersion);
161+
162+
try{
163+
$this->entityManager->persist($rule);
164+
}catch(\Exception $e){
165+
$io->error(sprintf("Error storing rule: %s - %s", $filePath, $e->getMessage()));
166+
return;
167+
}
168+
169+
return;
170+
}
171+
}

0 commit comments

Comments
 (0)