Skip to content

Commit 1189275

Browse files
committed
Release v2.1.3
1 parent 2a31815 commit 1189275

62 files changed

Lines changed: 11632 additions & 231 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.test.example

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# SOAR Testing Environment Configuration
2+
# Copy this file to .env.test and modify as needed for your testing environment.
3+
# .env.test is in .gitignore so your local changes won't be committed.
4+
5+
# AWS Configuration for Testing
6+
AWS_DEFAULT_REGION=us-east-1
7+
AWS_ACCESS_KEY_ID=test
8+
AWS_SECRET_ACCESS_KEY=test
9+
AWS_SESSION_TOKEN=test
10+
11+
# LocalStack Configuration (for integration tests)
12+
LOCALSTACK_ENDPOINT=http://localhost:4566
13+
SERVICES=s3,lambda,dynamodb,iam,sts,rds,ec2,securityhub,stepfunctions
14+
15+
# Python Path Configuration
16+
PYTHONPATH=${PYTHONPATH}:$(pwd)
17+
18+
# Test Account Configuration
19+
TEST_ACCOUNT_ID=123456789012
20+
TEST_ORGANIZATION_ID=o-example123456
21+
22+
# Test Region Configuration
23+
TEST_REGIONS=us-east-1,us-west-2
24+
25+
# Security Hub Test Configuration
26+
TEST_SECURITY_HUB_ACCOUNT=123456789012
27+
TEST_FINDING_PROVIDER_FIELDS=AWS/Inspector,AWS/GuardDuty,AWS/Config
28+
29+
# Database Test Configuration
30+
TEST_DYNAMODB_TABLE_PREFIX=soar-test-
31+
32+
# Email Test Configuration
33+
TEST_EMAIL_FROM=test@example.com
34+
TEST_EMAIL_TO=security-team@example.com
35+
36+
# AI/Bedrock Test Configuration (for real AWS tests)
37+
# Uncomment and configure for real AWS testing
38+
# AWS_BEDROCK_REGION=us-east-1
39+
# AWS_BEDROCK_MODEL_ID=anthropic.claude-3-sonnet-20240229-v1:0
40+
41+
# Ticketing System Test Configuration
42+
# JIRA_URL=https://your-company.atlassian.net
43+
# JIRA_USERNAME=test-user
44+
# JIRA_API_TOKEN=your-test-token
45+
# SERVICENOW_INSTANCE=your-test-instance
46+
# SERVICENOW_USERNAME=test-user
47+
# SERVICENOW_PASSWORD=test-password
48+
49+
# Test Execution Flags
50+
RUN_INTEGRATION_TESTS=false
51+
RUN_REAL_AWS_TESTS=false
52+
SKIP_SLOW_TESTS=true
53+
54+
# Logging Configuration
55+
LOG_LEVEL=INFO
56+
TEST_LOG_LEVEL=DEBUG

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ celerybeat-schedule.*
198198

199199
# Environments
200200
.env
201+
.env.test
201202
.venv
202203
env/
203204
venv/

CHANGELOG.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,20 @@
11
# Change Log
22

3+
## v2.1.3
4+
* Comprehensive testing infrastructure implementation with 53% auto-remediation coverage (16/30 functions, 236 tests)
5+
* Complete EC2 auto-remediation testing (8/8 functions, 134 tests) with ASFF standardization patterns
6+
* Complete RDS auto-remediation testing (7/7 controls, 88 tests) with comprehensive edge case coverage
7+
* Established documentation-first testing methodology for efficient test development
8+
* Added centralized test data management via fixtures/asff_data.py for consistent ASFF structures
9+
* Implemented critical pytest module import isolation to prevent cross-contamination between test suites
10+
* Enhanced testing documentation with LocalStack Docker integration and contributor guidelines
11+
* Added bug handling protocol for test development to ensure proper production code review processes
12+
* Comprehensive documentation added to core SOAR functions and auto-remediation components
13+
* Testing strategy now serves as template for expanding coverage across all OpenSecOps repositories
14+
315
## v2.1.2
416
* Set incident AI Query to 600 (was 120) as Claude Sonnet 4 keeps timing out - it's new.
517

6-
718
## v2.1.1
819
* Added existence check to the auto-remediation for ELB.5.
920

functions/accounts/clear_account_data/app.py

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,20 @@
1+
"""
2+
AWS Account Data Cache Management: Clear Account Data
3+
4+
This Lambda function clears all cached account data from the DynamoDB table used
5+
for account information caching. This is typically used for maintenance operations
6+
or when a full refresh of account data is needed across the SOAR system.
7+
8+
Operations:
9+
1. Scan the entire cached account data table to get all account IDs
10+
2. Create batch delete requests for all found accounts
11+
3. Process deletions in batches of 25 (DynamoDB batch limit)
12+
4. Handle unprocessed items with retry logic
13+
14+
Target Resources: DynamoDB cached account data table
15+
Purpose: Complete cache invalidation for account data refresh
16+
"""
17+
118
import os
219
import boto3
320

@@ -9,40 +26,70 @@
926

1027

1128
def lambda_handler(_event, _context):
12-
# Scan the table to get all items with only the 'id' attribute projected
29+
"""
30+
Main Lambda handler for clearing all cached account data.
31+
32+
Args:
33+
_event: Lambda event data (unused)
34+
_context: Lambda context (unused)
35+
36+
Returns:
37+
None (implicit)
38+
39+
Process:
40+
1. Scan table for all account IDs using pagination
41+
2. Create delete requests in batches of 25 items
42+
3. Process each batch with retry handling for unprocessed items
43+
4. Continue until all cached account data is cleared
44+
"""
45+
# STEP 1: Scan entire table to collect all account IDs
46+
# Use ProjectionExpression to minimize data transfer by only retrieving IDs
1347
response = table.scan(ProjectionExpression="id")
1448
result = response['Items']
1549

16-
# If there are more items to scan, continue scanning and adding to the result list
50+
# Handle pagination - continue scanning if more items exist
1751
while 'LastEvaluatedKey' in response:
1852
response = table.scan(ExclusiveStartKey=response['LastEvaluatedKey'])
1953
result.extend(response['Items'])
2054

21-
# Create a list of delete requests for each item in the result list
55+
# STEP 2: Create delete requests for all discovered accounts
2256
remaining_delete_requests = [delete_request(x['id']) for x in result]
2357

24-
# Process delete requests in batches of 25
58+
# STEP 3: Process deletions in batches of 25 (DynamoDB limit)
2559
while len(remaining_delete_requests) > 0:
2660
delete_requests = remaining_delete_requests[:25]
2761
remaining_delete_requests = remaining_delete_requests[25:]
2862

29-
# Process each batch of delete requests
63+
# STEP 4: Execute batch delete with retry handling for unprocessed items
3064
while len(delete_requests) > 0:
3165
print(delete_requests)
32-
# Use the batch_write_item method to delete the items in the batch
66+
# Execute batch delete operation
3367
response = dynamodb.batch_write_item(
3468
RequestItems={
3569
CACHED_ACCOUNT_DATA_TABLE_NAME: delete_requests
3670
}
3771
)
3872
print(response)
39-
# Get any unprocessed items and add them back to the delete_requests list
73+
74+
# Handle unprocessed items - retry them in the next iteration
4075
delete_requests = response.get('UnprocessedItems', {}).get(
4176
CACHED_ACCOUNT_DATA_TABLE_NAME, [])
4277

4378

4479
def delete_request(account_id):
45-
# Create a delete request for a given account_id
80+
"""
81+
Create a DynamoDB delete request structure for a given account ID.
82+
83+
Args:
84+
account_id: AWS account ID to delete from cache
85+
86+
Returns:
87+
dict: DynamoDB delete request structure for batch operations
88+
89+
Format:
90+
Returns the standard DynamoDB batch delete request format with
91+
the account ID as the primary key.
92+
"""
4693
return {
4794
'DeleteRequest': {
4895
'Key': {

functions/accounts/get_account_data/app.py

Lines changed: 105 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,121 @@
1+
"""
2+
AWS Account Data Management: Get Account Data with Caching
3+
4+
This Lambda function retrieves comprehensive account information including metadata,
5+
tags, organizational structure, and team assignments. Uses DynamoDB caching to
6+
improve performance and reduce API calls to AWS Organizations.
7+
8+
Account Data Includes:
9+
- Basic account information (ID, name, email, join date)
10+
- Organizational unit placement and hierarchy
11+
- Tag-based metadata (team, environment, client, project)
12+
- Contact information and team email assignments
13+
- Ticketing system integration details (Jira/ServiceNow)
14+
- Account age and classification (new vs established)
15+
16+
Caching Strategy:
17+
- Check DynamoDB cache first for existing account data
18+
- Fetch fresh data from AWS Organizations if cache miss
19+
- Store fresh data in cache for future requests
20+
- Cache improves performance and reduces Organizations API throttling
21+
22+
Target Resources: AWS Organizations accounts and DynamoDB cache
23+
Purpose: Centralized account metadata retrieval with performance optimization
24+
"""
25+
126
import os
227
import datetime as dt
328
import json
429
import boto3
530
from botocore.config import Config
631
from dateutil import parser
732

8-
# Get environment variables
33+
# SOAR Configuration
934
PRODUCT_NAME = os.environ['PRODUCT_NAME']
35+
36+
# Team Contact Configuration
1037
ACCOUNT_TEAM_EMAIL_TAG = os.environ['ACCOUNT_TEAM_EMAIL_TAG'] # soar:team:email
1138
ACCOUNT_TEAM_EMAIL_TAG_APP = os.environ['ACCOUNT_TEAM_EMAIL_TAG_APP'] # soar:team:email:app
1239
DEFAULT_TEAM_EMAIL = os.environ['DEFAULT_TEAM_EMAIL']
40+
41+
# Account Classification Tags
1342
ENVIRONMENT_TAG = os.environ['ENVIRONMENT_TAG'] # soar:environment
1443
CLIENT_TAG = os.environ['CLIENT_TAG'] # soar:client
1544
PROJECT_TAG = os.environ['PROJECT_TAG'] # soar:project
1645
TEAM_TAG = os.environ['TEAM_TAG'] # soar:team
1746

18-
TICKETING_SYSTEM = os.environ['TICKETING_SYSTEM'] #
47+
# Ticketing System Integration
48+
TICKETING_SYSTEM = os.environ['TICKETING_SYSTEM'] # JIRA or ServiceNow
1949

50+
# Jira Integration Configuration
2051
JIRA_PROJECT_KEY_TAG = os.environ['JIRA_PROJECT_KEY_TAG'] # soar:jira:project-key
2152
JIRA_PROJECT_KEY_TAG_APP = os.environ['JIRA_PROJECT_KEY_TAG_APP'] # soar:jira:project-key:app
22-
JIRA_DEFAULT_PROJECT_KEY = os.environ['JIRA_DEFAULT_PROJECT_KEY'] # XXX
53+
JIRA_DEFAULT_PROJECT_KEY = os.environ['JIRA_DEFAULT_PROJECT_KEY'] # Default project key
2354

55+
# ServiceNow Integration Configuration
2456
SERVICE_NOW_PROJECT_QUEUE_TAG = os.environ['SERVICE_NOW_PROJECT_QUEUE_TAG'] # soar:service-now:project-queue
2557
SERVICE_NOW_PROJECT_QUEUE_TAG_APP = os.environ['SERVICE_NOW_PROJECT_QUEUE_TAG_APP'] # soar:service-now:project-queue:app
26-
SERVICE_NOW_DEFAULT_PROJECT_QUEUE = os.environ['SERVICE_NOW_DEFAULT_PROJECT_QUEUE'] # XXX
58+
SERVICE_NOW_DEFAULT_PROJECT_QUEUE = os.environ['SERVICE_NOW_DEFAULT_PROJECT_QUEUE'] # Default queue
2759

60+
# Cache Configuration
2861
CACHED_ACCOUNT_DATA_TABLE_NAME = os.environ['CACHED_ACCOUNT_DATA_TABLE_NAME']
29-
3062
MIN_AGE_HOURS = int(os.environ['MIN_AGE_HOURS'])
3163

32-
33-
# Configure Boto3
64+
# Configure Boto3 with minimal retries - let Step Functions handle retry logic
3465
config = Config(
3566
retries={
3667
'total_max_attempts': 1 # Let Step Functions handle the retries
3768
}
3869
)
3970

40-
# Create Boto3 clients
71+
# AWS Service Clients
4172
client = boto3.client('organizations', config=config)
4273
dynamodb = boto3.client('dynamodb')
4374

44-
# Lambda handler function
75+
4576
def lambda_handler(account_id, _context):
46-
# Check if account data is cached
77+
"""
78+
Main Lambda handler for retrieving account data with caching.
79+
80+
Args:
81+
account_id: AWS account ID to retrieve data for
82+
_context: Lambda context (unused)
83+
84+
Returns:
85+
dict: Comprehensive account data including metadata, contacts, and classification
86+
87+
Process:
88+
1. Check DynamoDB cache for existing account data
89+
2. If cache hit, return cached data immediately
90+
3. If cache miss, fetch fresh data from AWS Organizations
91+
4. Store fresh data in cache and return to caller
92+
"""
93+
# STEP 1: Check cache first for performance optimization
4794
cached_account_data = get_cached_account_data(account_id)
4895
if cached_account_data:
4996
print(f"Account {account_id}: Using cache")
5097
return cached_account_data
5198

52-
# Fetch fresh account data
99+
# STEP 2: Cache miss - fetch fresh data and update cache
53100
account_data = get_fresh_account_data(account_id)
54101
put_cached_account_data(account_id, account_data)
55102
return account_data
56103

57-
# Get cached account data from DynamoDB
104+
58105
def get_cached_account_data(account_id):
106+
"""
107+
Retrieve account data from DynamoDB cache.
108+
109+
Args:
110+
account_id: AWS account ID to look up
111+
112+
Returns:
113+
dict: Cached account data if found and valid, False otherwise
114+
115+
Error Handling:
116+
Returns False for any cache misses, corruption, or JSON parsing errors.
117+
This ensures graceful fallback to fresh data fetching.
118+
"""
59119
response = dynamodb.get_item(
60120
TableName=CACHED_ACCOUNT_DATA_TABLE_NAME,
61121
Key={
@@ -76,8 +136,18 @@ def get_cached_account_data(account_id):
76136

77137
return data
78138

79-
# Put account data into DynamoDB cache
80139
def put_cached_account_data(account_id, account_data):
140+
"""
141+
Store account data in DynamoDB cache for future requests.
142+
143+
Args:
144+
account_id: AWS account ID as cache key
145+
account_data: Complete account data dictionary to cache
146+
147+
Cache Format:
148+
Stores JSON-serialized account data with account ID as primary key.
149+
This enables fast lookup and reduces Organizations API calls.
150+
"""
81151
response = dynamodb.put_item(
82152
TableName=CACHED_ACCOUNT_DATA_TABLE_NAME,
83153
Item={
@@ -87,8 +157,29 @@ def put_cached_account_data(account_id, account_data):
87157
)
88158
print(response)
89159

90-
# Fetch fresh account data
160+
91161
def get_fresh_account_data(account_id):
162+
"""
163+
Fetch comprehensive account data from AWS Organizations and compile metadata.
164+
165+
Args:
166+
account_id: AWS account ID to retrieve information for
167+
168+
Returns:
169+
dict: Complete account data including:
170+
- Basic account info (ID, name, email, join date)
171+
- Organizational structure (OU placement)
172+
- Tag-based metadata (team, environment, client, project)
173+
- Contact information (team emails)
174+
- Ticketing integration (Jira/ServiceNow project mappings)
175+
- Account classification (new vs established)
176+
- Tallies for reporting and aggregation
177+
178+
Data Sources:
179+
- AWS Organizations: Account details, tags, OU structure
180+
- Environment variables: Default values and tag mappings
181+
- Calculated fields: Account age, team assignments, project mappings
182+
"""
92183
print(f"Account {account_id}: Fetching fresh data")
93184

94185
# Get account details

0 commit comments

Comments
 (0)