Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions apps/analysis/detection_rules/aws_rules.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
severity: "medium"
event_source: "sts.amazonaws.com"
event_name: "GetCallerIdentity"
auto_tags: ["suspicious", "reconnaissance"]
auto_tags: ["Suspicious", "Medium"]
enabled: true

- name: "Root Account Usage"
Expand All @@ -17,7 +17,7 @@
severity: "high"
event_source: "signin.amazonaws.com"
additional_criteria: {"user_identity": "root"}
auto_tags: ["high-risk", "compliance-violation"]
auto_tags: ["High", "Suspicious"]
enabled: true

- name: "Security Group Modification"
Expand All @@ -27,7 +27,7 @@
severity: "medium"
event_source: "ec2.amazonaws.com"
event_name: "AuthorizeSecurityGroupIngress"
auto_tags: ["security-group-change", "network-modification"]
auto_tags: ["Medium", "Informational"]
enabled: true

- name: "IAM Policy Changes"
Expand All @@ -37,5 +37,5 @@
severity: "high"
event_source: "iam.amazonaws.com"
event_name: "PutRolePolicy"
auto_tags: ["iam-change", "privilege-escalation"]
auto_tags: ["High", "Suspicious"]
enabled: true
52 changes: 52 additions & 0 deletions apps/analysis/detection_rules/azure_rules.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Azure Pre-built Detection Rules

- name: "Azure Sign-in from Unknown Location"
description: "Detects sign-in attempts from unusual or unauthorized locations"
cloud: "azure"
detection_type: "login"
severity: "medium"
event_source: "Microsoft.Azure.ActiveDirectory"
event_name: "Sign-in activity"
additional_criteria: {"raw_data_contains": "unfamiliarFeatures"}
auto_tags: ["Suspicious", "Medium"]
enabled: true

- name: "Azure Role Assignment Change"
description: "Detects changes to role assignments which could indicate privilege escalation"
cloud: "azure"
detection_type: "iam"
severity: "high"
event_source: "Microsoft.Authorization"
event_name: "Microsoft.Authorization/roleAssignments/write"
auto_tags: ["High", "Suspicious"]
enabled: true

- name: "Network Security Group Modification"
description: "Detects modifications to NSGs which could indicate network security changes"
cloud: "azure"
detection_type: "network"
severity: "medium"
event_source: "Microsoft.Network"
event_name: "Microsoft.Network/networkSecurityGroups/write"
auto_tags: ["Medium", "Informational"]
enabled: true

- name: "Key Vault Access Policy Change"
description: "Detects modifications to Key Vault access policies which could indicate unauthorized access attempts"
cloud: "azure"
detection_type: "keyvault"
severity: "high"
event_source: "Microsoft.KeyVault"
event_name: "Microsoft.KeyVault/vaults/accessPolicies/write"
auto_tags: ["High", "Suspicious"]
enabled: true

- name: "Storage Account Configuration Change"
description: "Detects changes to storage account configurations which could indicate data exfiltration risks"
cloud: "azure"
detection_type: "storage"
severity: "medium"
event_source: "Microsoft.Storage"
event_name: "Microsoft.Storage/storageAccounts/write"
auto_tags: ["Medium", "Informational"]
enabled: true
9 changes: 4 additions & 5 deletions apps/analysis/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,13 @@

urlpatterns = [
path('case/<int:case_id>/detections/', views.case_detections, name='case_detections'),
path('case/<int:case_id>/detections/run/', views.run_detections, name='run_detections'),
path('case/<int:case_id>/detections/results/', views.detection_results, name='detection_results'),
path('case/<int:case_id>/detections/rules/', views.detection_list, name='detection_list'),
path('case/<int:case_id>/detections/rules/create/', views.detection_create, name='detection_create'),
path('case/<int:case_id>/detections/rules/<int:pk>/edit/', views.detection_edit, name='detection_edit'),
path('case/<int:case_id>/detections/rules/<int:pk>/delete/', views.detection_delete, name='detection_delete'),
path('case/<int:case_id>/detections/run/', views.run_detections, name='run_detections'),
path('case/<int:case_id>/detections/rules/load-prebuilt/', views.load_prebuilt_rules, name='load_prebuilt_rules'),
path('case/<int:case_id>/detection-result/<int:result_id>/tag/',
views.tag_detection_result, name='tag_detection_result'),
path('api/detection-result/<int:result_id>/tags/',
views.get_detection_result_tags, name='get_detection_result_tags'),
path('case/<int:case_id>/detections/result/<int:result_id>/tag/', views.tag_detection_result, name='tag_detection_result'),
path('detection-result/<int:result_id>/tags/', views.get_detection_result_tags, name='get_detection_result_tags'),
]
55 changes: 44 additions & 11 deletions apps/analysis/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from io import StringIO
from apps.aws.models import AWSAccount
from .models import Tag
from apps.azure.models import AzureAccount

@login_required
def detection_list(request, case_id):
Expand Down Expand Up @@ -115,28 +116,60 @@ def detection_results(request, case_id):

@login_required
def case_detections(request, case_id):
"""Main detections page showing results and management options"""
case = get_object_or_404(Case, id=case_id)
detection_results = DetectionResult.objects.filter(
case_id=case_id
).select_related('detection', 'matched_log').order_by('-created_at')

# Get all available tags
available_tags = Tag.objects.all()
# Get account filter from query params
account_filter = request.GET.get('account')

# Get all detection results for the case
results = DetectionResult.objects.filter(case=case)

# Filter by account if specified
if account_filter:
account_type, account_id = account_filter.split(':')
if account_type == 'aws':
results = results.filter(matched_log__aws_account__account_id=account_id)
elif account_type == 'azure':
results = results.filter(matched_log__azure_account__subscription_id=account_id)

# Get unique accounts that have logs
accounts = []
aws_accounts = AWSAccount.objects.filter(
normalized_logs__detectionresult__case=case
).distinct()
azure_accounts = AzureAccount.objects.filter(
normalized_logs__detectionresult__case=case
).distinct()

for aws_acc in aws_accounts:
accounts.append({
'id': f'aws:{aws_acc.account_id}',
'name': f'AWS Account: {aws_acc.account_id}',
'type': 'aws'
})

for azure_acc in azure_accounts:
accounts.append({
'id': f'azure:{azure_acc.subscription_id}',
'name': f'Azure Account: {azure_acc.subscription_id}',
'type': 'azure'
})

# Group results by detection
results_by_detection = {}
for result in detection_results:
for result in results:
if result.detection not in results_by_detection:
results_by_detection[result.detection] = []
results_by_detection[result.detection].append(result)

context = {
'case': case,
'results_by_detection': results_by_detection,
'total_results': detection_results.count(),
'detection_count': Detection.objects.filter(enabled=True).count(),
'available_tags': available_tags
'detection_count': Detection.objects.count(),
'total_results': results.count(),
'available_tags': Tag.objects.all(),
'accounts': accounts,
'selected_account': account_filter
}

return render(request, 'analysis/case_detections.html', context)
Expand Down
6 changes: 6 additions & 0 deletions apps/azure/admin.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
from django.contrib import admin
from .models import AzureAccount, AzureResource, AzureLogSource, AzureIdentity

admin.site.register(AzureAccount)
admin.site.register(AzureResource)
admin.site.register(AzureLogSource)
admin.site.register(AzureIdentity)

# Register your models here.
52 changes: 52 additions & 0 deletions apps/azure/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from django import forms
from .models import AzureAccount, AzureResource

class AzureAccountForm(forms.ModelForm):
"""Form for connecting an Azure subscription using service principal credentials"""
class Meta:
model = AzureAccount
fields = ['subscription_id', 'tenant_id', 'client_id', 'client_secret']
widgets = {
'client_secret': forms.PasswordInput(),
'subscription_id': forms.TextInput(attrs={
'placeholder': 'e.g., 12345678-1234-5678-1234-567812345678',
'class': 'form-control'
}),
'tenant_id': forms.TextInput(attrs={
'placeholder': 'e.g., 87654321-4321-8765-4321-876543210987',
'class': 'form-control'
}),
'client_id': forms.TextInput(attrs={
'placeholder': 'e.g., 11111111-2222-3333-4444-555555555555',
'class': 'form-control'
})
}
labels = {
'subscription_id': 'Subscription ID',
'tenant_id': 'Directory (tenant) ID',
'client_id': 'Application (client) ID',
'client_secret': 'Client Secret'
}
help_texts = {
'subscription_id': 'Found in Azure Portal under Subscriptions',
'tenant_id': 'Found in Azure Active Directory → Overview',
'client_id': 'Found in App Registration → Overview',
'client_secret': 'Created in App Registration → Certificates & secrets'
}

class FetchActivityLogsForm(forms.Form):
"""Form for fetching Azure Activity Logs"""
start_date = forms.DateField(
label="Start Date",
widget=forms.DateInput(attrs={
"type": "date",
"class": "form-control"
})
)
end_date = forms.DateField(
label="End Date",
widget=forms.DateInput(attrs={
"type": "date",
"class": "form-control"
})
)
91 changes: 91 additions & 0 deletions apps/azure/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Generated by Django 5.1.3 on 2025-02-17 02:54

import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models


class Migration(migrations.Migration):

initial = True

dependencies = [
('case', '0001_initial'),
('data', '0006_alter_detectionresult_case_and_more'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.CreateModel(
name='AzureAccount',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('subscription_id', models.CharField(max_length=50, unique=True)),
('tenant_id', models.CharField(max_length=50)),
('client_id', models.CharField(max_length=100)),
('client_secret', models.CharField(max_length=100)),
('added_at', models.DateTimeField(auto_now_add=True)),
('validated', models.BooleanField(default=False)),
('added_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='added_azure_accounts', to=settings.AUTH_USER_MODEL)),
('case', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='azure_accounts', to='case.case')),
],
),
migrations.CreateModel(
name='AzureLogSource',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('service_name', models.CharField(max_length=100)),
('log_name', models.CharField(max_length=255)),
('log_details', models.JSONField(blank=True, null=True)),
('status', models.CharField(max_length=50)),
('location', models.CharField(blank=True, max_length=50, null=True)),
('slug', models.SlugField(blank=True, max_length=255, unique=True)),
('discovered_at', models.DateTimeField(auto_now_add=True)),
('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='log_sources', to='azure.azureaccount')),
('case', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='azure_log_sources', to='case.case')),
('tags', models.ManyToManyField(related_name='azure_log_source', to='data.tag')),
],
),
migrations.CreateModel(
name='AzureResource',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('resource_id', models.CharField(max_length=200)),
('resource_type', models.CharField(max_length=100)),
('resource_name', models.CharField(blank=True, max_length=200, null=True)),
('resource_group', models.CharField(max_length=200)),
('resource_details', models.JSONField(blank=True, null=True)),
('location', models.CharField(blank=True, max_length=50, null=True)),
('slug', models.SlugField(blank=True, max_length=255, unique=True)),
('discovered_at', models.DateTimeField(auto_now_add=True)),
('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='resources', to='azure.azureaccount')),
('case', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='azure_resources', to='case.case')),
('tags', models.ManyToManyField(related_name='azure_resource', to='data.tag')),
],
),
migrations.CreateModel(
name='AzureIdentity',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('object_id', models.CharField(max_length=100)),
('display_name', models.CharField(max_length=300)),
('user_principal_name', models.CharField(blank=True, max_length=300, null=True)),
('identity_type', models.CharField(max_length=50)),
('mfa_enabled', models.BooleanField(default=False)),
('created_datetime', models.DateTimeField(blank=True, null=True)),
('last_sign_in', models.DateTimeField(blank=True, null=True)),
('account_enabled', models.BooleanField(default=True)),
('assigned_roles', models.JSONField(blank=True, null=True)),
('identity_details', models.JSONField(blank=True, null=True)),
('slug', models.SlugField(blank=True, max_length=255, unique=True)),
('discovered_at', models.DateTimeField(auto_now_add=True)),
('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='identities', to='azure.azureaccount')),
('case', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='azure_identities', to='case.case')),
('tags', models.ManyToManyField(related_name='azure_identity', to='data.tag')),
],
options={
'verbose_name_plural': 'Azure identities',
'unique_together': {('account', 'object_id')},
},
),
]
Loading
Loading