Skip to content

[Phase 1] XCCDF Data-Stream Generator from MongoDB#99

Merged
remyluslosius merged 3 commits into
mainfrom
feature/xccdf-generator
Oct 15, 2025
Merged

[Phase 1] XCCDF Data-Stream Generator from MongoDB#99
remyluslosius merged 3 commits into
mainfrom
feature/xccdf-generator

Conversation

@remyluslosius
Copy link
Copy Markdown
Contributor

Summary

Implements Phase 1, Issue #3 of the 7-phase hybrid scanning architecture plan. Generates XCCDF 1.2 compliant benchmarks and tailoring files from MongoDB compliance rules for scan-time variable customization.

Related Issues

Changes

1. XCCDF Generator Service (`xccdf_generator_service.py` - 550 lines)

Core Methods:

  • `generate_benchmark()`: Creates complete XCCDF 1.2 Benchmark XML
  • `generate_tailoring()`: Creates XCCDF Tailoring files for variable overrides

XCCDF Elements Generated:

  • `xccdf:Value`: Variables with type constraints (number ranges, string patterns, boolean)
  • `xccdf:Rule`: Compliance checks with severity, references, identifiers
  • `xccdf:Group`: Rule categorization (authentication, network, etc.)
  • `xccdf:Profile`: Framework-specific rule selections (NIST, CIS, STIG)
  • `xccdf:Benchmark`: Root element with metadata, status, version

XCCDF 1.2 Compliance:
All IDs follow required pattern `xccdf_`:

  • Benchmark: `xccdf_com.hanalyx.openwatch_benchmark_{name}`
  • Rule: `xccdf_com.hanalyx.openwatch_rule_{name}`
  • Value: `xccdf_com.hanalyx.openwatch_value_{var_id}`
  • Group: `xccdf_com.hanalyx.openwatch_group_{category}`
  • Profile: `xccdf_com.hanalyx.openwatch_profile_{framework}_{version}`

2. XCCDF API Endpoints (`xccdf_api.py` - 220 lines)

Endpoints:

  • `POST /api/v1/xccdf/generate-benchmark`: Generate XCCDF Benchmark
  • `POST /api/v1/xccdf/generate-tailoring`: Generate tailoring file
  • `GET /api/v1/xccdf/frameworks`: List available frameworks/versions
  • `GET /api/v1/xccdf/variables`: List customizable variables

Security: All endpoints require authentication via `get_current_user`

3. Pydantic Schemas (`xccdf_schemas.py` - 150 lines)

  • `XCCDFBenchmarkRequest`/`Response`
  • `XCCDFTailoringRequest`/`Response`
  • `XCCDFValidationRequest`/`Response`

XCCDF Structure

Benchmark Example

```xml
<xccdf:Benchmark id="xccdf_com.hanalyx.openwatch_benchmark_nist_800_53r5"
resolved="true">
<xccdf:status date="2025-10-14">draft</xccdf:status>
xccdf:titleNIST SP 800-53 Revision 5</xccdf:title>
<xccdf:version time="2025-10-14T22:00:00Z">1.0.0</xccdf:version>

xccdf:metadata
dc:creatorOpenWatch SCAP Generator</dc:creator>
dc:publisherHanalyx OpenWatch</dc:publisher>
</xccdf:metadata>

<xccdf:Value id="xccdf_com.hanalyx.openwatch_value_var_accounts_tmout"
type="number" interactive="true">
xccdf:titleSession Timeout</xccdf:title>
xccdf:descriptionTimeout for inactive sessions in seconds</xccdf:description>
xccdf:value600</xccdf:value>
xccdf:lower-bound60</xccdf:lower-bound>
xccdf:upper-bound3600</xccdf:upper-bound>
</xccdf:Value>

<xccdf:Group id="xccdf_com.hanalyx.openwatch_group_authentication">
xccdf:titleAuthentication</xccdf:title>

<!-- Rules -->
<xccdf:Rule id="xccdf_com.hanalyx.openwatch_rule_accounts_tmout" 
             severity="medium" selected="true">
  <xccdf:title>Set Interactive Session Timeout</xccdf:title>
  <xccdf:description>Configure TMOUT for session expiration</xccdf:description>
  <xccdf:rationale>Reduces unauthorized access window</xccdf:rationale>
  <xccdf:ident system="http://cce.mitre.org">CCE-27557-8</xccdf:ident>
  
  <xccdf:check system="http://oval.mitre.org/XMLSchema/oval-definitions-5">
    <xccdf:check-content-ref href="oscap-definitions.xml" 
                              name="xccdf_org.ssgproject.content_rule_accounts_tmout"/>
  </xccdf:check>
</xccdf:Rule>

</xccdf:Group>

<xccdf:Profile id="xccdf_com.hanalyx.openwatch_profile_nist_800_53r5">
xccdf:titleNIST 800-53r5</xccdf:title>
xccdf:descriptionProfile for NIST 800-53r5 compliance</xccdf:description>
<xccdf:select idref="xccdf_com.hanalyx.openwatch_rule_accounts_tmout"
selected="true"/>
</xccdf:Profile>
</xccdf:Benchmark>
```

Tailoring Example

```xml
<xccdf:Tailoring id="production_tailoring">
<xccdf:version time="2025-10-14T22:00:00Z">1.0</xccdf:version>
<xccdf:benchmark href="nist-800-53r5.xml"
id="xccdf_com.hanalyx.openwatch_benchmark_nist_800_53r5"/>

<xccdf:Profile id="nist_800_53r5_production" extends="nist_800_53r5">
xccdf:titleProduction Environment Settings</xccdf:title>
xccdf:descriptionTightened timeout for production systems</xccdf:description>

<xccdf:set-value idref="xccdf_com.hanalyx.openwatch_value_var_accounts_tmout">
  300
</xccdf:set-value>

</xccdf:Profile>
</xccdf:Tailoring>
```

Usage

Generate Benchmark via API

```bash
curl -X POST http://localhost:8000/api/v1/xccdf/generate-benchmark \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"benchmark_id": "nist-800-53r5",
"title": "NIST SP 800-53 Revision 5 Security Controls",
"description": "Security controls for federal information systems",
"version": "1.0.0",
"framework": "nist",
"framework_version": "800-53r5"
}' | jq -r '.benchmark_xml' > nist-800-53r5.xml
```

Generate Tailoring via API

```bash
curl -X POST http://localhost:8000/api/v1/xccdf/generate-tailoring \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"tailoring_id": "prod_tailoring",
"benchmark_href": "nist-800-53r5.xml",
"benchmark_version": "1.0.0",
"profile_id": "xccdf_com.hanalyx.openwatch_profile_nist_800_53r5",
"variable_overrides": {
"xccdf_com.hanalyx.openwatch_value_var_accounts_tmout": "300"
},
"title": "Production Environment",
"description": "Tightened security for production"
}' | jq -r '.tailoring_xml' > production.xml
```

Scan with OpenSCAP

```bash

Validate generated files

oscap xccdf validate nist-800-53r5.xml
oscap xccdf validate production.xml

Run compliance scan

oscap xccdf eval \
--profile xccdf_com.hanalyx.openwatch_profile_nist_800_53r5 \
--tailoring-file production.xml \
--results scan-results.xml \
--report scan-report.html \
nist-800-53r5.xml
```

Implementation Details

Variable Constraint Mapping

Variable Type XCCDF Constraints
number `xccdf:lower-bound`, `xccdf:upper-bound`
string (enum) Multiple `xccdf:choice` elements
string (pattern) `xccdf:match` with regex
boolean No additional constraints

Profile Generation Strategy

  1. Query MongoDB for rules with `is_latest: true`
  2. Filter by framework/version if specified
  3. Group rules by framework versions found
  4. Create one Profile per framework version
  5. Add `xccdf:select` for each matching rule

Tailoring File Use Cases

  • Environment-specific: Different timeouts for dev/staging/prod
  • Organization policies: Custom banner text, password policies
  • Temporary exceptions: Temporarily relax constraints during migration
  • Multi-tenant: Different variable values per customer

Testing

Manual Validation

Created sample XCCDF files following spec requirements:

  • ✅ Benchmark ID format: `xccdf_com.hanalyx.openwatch_benchmark_*`
  • ✅ Value ID format: `xccdf_com.hanalyx.openwatch_value_*`
  • ✅ Rule ID format: `xccdf_com.hanalyx.openwatch_rule_*`
  • ✅ Group ID format: `xccdf_com.hanalyx.openwatch_group_*`
  • ✅ Profile ID format: `xccdf_com.hanalyx.openwatch_profile_*`

Integration Testing (Post-Merge)

After PR #97 merges, will test:

  1. Generate benchmark from real MongoDB rules
  2. Validate with `oscap xccdf validate`
  3. Generate HTML guide with `oscap xccdf generate guide`
  4. Run actual scan with variable customization

Next Steps

After this PR merges:

  1. Issue chore(deps): bump actions/download-artifact from 3 to 5 #4: MongoDB-Based Scan Service (5-7 days)

    • Route scans to appropriate scanners (oscap, kubernetes, cloud)
    • Apply tailoring files for variable customization
    • Store scan results in MongoDB
  2. Issue chore(deps): bump node from 18-alpine to 24-alpine in /docker #5: ORSA Plugin Architecture (5-7 days)

    • Execute remediation content (Ansible, Bash)
    • Track remediation status
    • Rollback support
  3. Issue chore(deps): bump actions/upload-artifact from 3 to 4 #6: Scan Configuration API (3-4 days)

    • UI for benchmark selection
    • Variable customization interface
    • Tailoring file management
  4. Issue chore(deps): bump peter-evans/create-pull-request from 5 to 7 #7: Frontend Variable Customization UI (5-7 days)

    • Framework selection dropdowns
    • Variable override forms with constraint validation
    • Real-time XCCDF preview

Documentation

See:

Checklist

  • XCCDF Generator Service implemented (550 lines)
  • XCCDF 1.2 ID naming requirements enforced
  • Benchmark generation with Values, Rules, Groups, Profiles
  • Tailoring file generation with variable overrides
  • API endpoints with authentication
  • Pydantic schemas for requests/responses
  • Helper endpoints (list frameworks, list variables)
  • Manual XCCDF validation completed
  • Integration tests with real MongoDB data (pending PR [Phase 1] Enhanced SCAP Converter with Variable and Remediation Extraction #97)
  • `oscap xccdf validate` passing (pending integration test)

🤖 Generated with Claude Code

Co-Authored-By: Claude noreply@anthropic.com

remyluslosius and others added 3 commits October 14, 2025 22:04
Phase 1, Task 1.1: Enhanced ComplianceRule Model with XCCDF Variables

Implements Solution A (XCCDF Variables) for hybrid scanning architecture,
enabling scan-time customization of compliance checks.

New Model: XCCDFVariable
- Supports string, number, and boolean variable types
- Validation for constraints (min/max, choices, patterns)
- Sensitive variable handling (encrypted storage, masked UI)
- Interactive flag for UI/API customization

ComplianceRule Enhancements:
1. xccdf_variables: Dict[str, XCCDFVariable] - Variables for scan-time customization
2. scanner_type: str - Route rules to appropriate scanner (oscap, python, aws_api, etc.)
3. remediation: Dict - ORSA (Open Remediation Standard Adapter) plugin content

MongoDB Indexes:
- Added 'scanner_type' index for routing performance
- Added compound index: (scanner_type, is_latest) for latest rules by scanner

Testing:
- XCCDFVariable creation and validation ✅
- Constraint validation (numeric, string, regex) ✅
- Model serialization (exclude_none) ✅
- ComplianceRule backward compatibility ✅

References:
- Issue #94
- /docs/REMEDIATION_WITH_XCCDF_VARIABLES.md
- /docs/ADVANCED_SCANNING_ARCHITECTURE.md
- /docs/IMPLEMENTATION_PLAN_7_PHASES.md

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
Implements enhanced SCAP converter with XCCDF variable and remediation
content extraction for Solution A hybrid scanning architecture.

## New Components

### 1. SCAP YAML Parser Service (scap_yaml_parser_service.py)
- **XCCDFVariableExtractor**: Extracts XCCDF variables from SCAP YAML rules
  - Variable type inference (string, number, boolean)
  - Constraint detection (min/max values, string lengths, regex patterns)
  - Sensitive variable marking (passwords, keys, credentials)
  - Template variable extraction

- **RemediationExtractor**: Extracts remediation content
  - Ansible task generation from templates (sysctl, file, service, package)
  - Bash script generation for simple remediation
  - Supports separate remediation files (ansible/shared.yml, bash/shared.sh)

- **ScannerTypeDetector**: Detects scanner type from rule metadata
  - kubernetes: yamlfile_value templates with ocp_data
  - aws_api, azure_api, gcp_api: Cloud-specific templates
  - oscap: Traditional OVAL-based checks (default)

### 2. Enhanced SCAP Converter Updates
- Added --extract-variables flag for XCCDF variable extraction
- Added --extract-remediation flag for remediation content extraction
- Integrated scap_yaml_parser_service for metadata extraction
- Populates xccdf_variables, remediation, scanner_type fields (from Issue #94)

## Testing

Tested with real SCAP content:

**Kubernetes Rule** (ocp_insecure_allowed_registries_for_import):
- Scanner Type: kubernetes ✅
- Variables: check_existence, values ✅

**Mock Sysctl Rule** (net.ipv4.ip_forward):
- Scanner Type: oscap ✅
- Variables: sysctlvar (string), sysctlval (boolean) ✅
- Ansible Remediation: ansible.posix.sysctl task ✅
- Bash Remediation: sysctl -w command + /etc/sysctl.conf ✅

## Usage

```bash
# Extract variables only
python -m backend.app.cli.scap_to_openwatch_converter_enhanced convert \
  --scap-path /path/to/scap/content \
  --output-path /output/dir \
  --format json \
  --extract-variables

# Extract variables + remediation
python -m backend.app.cli.scap_to_openwatch_converter_enhanced convert \
  --scap-path /path/to/scap/content \
  --output-path /output/dir \
  --format json \
  --extract-variables \
  --extract-remediation
```

## Implementation Notes

- Variable extraction handles template vars (most reliable source)
- Type inference from naming conventions (timeout→number, banner→string)
- Remediation extraction supports 12 template types (sysctl, file, service, etc.)
- Scanner detection based on template name and file path
- All Phase 1 fields are optional with defaults (backward compatible)

## Related Issues

- Closes: #96 (Enhanced SCAP Converter with Variable Extraction)
- Requires: #94 (XCCDFVariable Model) - merged
- Blocks: #97 (XCCDF Data-Stream Generator)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Implements XCCDF 1.2 compliant benchmark and tailoring file generation
from MongoDB compliance rules for Solution A hybrid scanning architecture.

## New Components

### 1. XCCDF Generator Service (xccdf_generator_service.py - 550 lines)
- **generate_benchmark()**: Creates XCCDF 1.2 Benchmark XML from MongoDB rules
  - Generates XCCDF Value elements for variables with constraints
  - Creates Groups for rule categorization
  - Generates Profiles per framework (NIST, CIS, STIG, etc.)
  - Proper XCCDF 1.2 ID formatting: xccdf_<reverse-DNS>_<type>_<name>

- **generate_tailoring()**: Creates XCCDF Tailoring files for variable overrides
  - Allows environment-specific customization (dev, staging, prod)
  - set-value elements for variable overrides
  - Extends base profiles without modifying benchmark

- **XCCDF 1.2 Compliance**:
  - Benchmark IDs: xccdf_com.hanalyx.openwatch_benchmark_{name}
  - Rule IDs: xccdf_com.hanalyx.openwatch_rule_{name}
  - Value IDs: xccdf_com.hanalyx.openwatch_value_{name}
  - Group IDs: xccdf_com.hanalyx.openwatch_group_{category}
  - Profile IDs: xccdf_com.hanalyx.openwatch_profile_{framework}_{version}

### 2. XCCDF API Endpoints (xccdf_api.py - 220 lines)
- **POST /api/v1/xccdf/generate-benchmark**: Generate XCCDF Benchmark
- **POST /api/v1/xccdf/generate-tailoring**: Generate XCCDF Tailoring file
- **GET /api/v1/xccdf/frameworks**: List available frameworks and versions
- **GET /api/v1/xccdf/variables**: List customizable XCCDF variables

### 3. Pydantic Schemas (xccdf_schemas.py - 150 lines)
- XCCDFBenchmarkRequest/Response
- XCCDFTailoringRequest/Response
- XCCDFValidationRequest/Response

## XCCDF Structure Generated

### Benchmark XML
\`\`\`xml
<xccdf:Benchmark id="xccdf_com.hanalyx.openwatch_benchmark_nist_800_53r5">
  <xccdf:status>draft</xccdf:status>
  <xccdf:title>NIST 800-53r5 Benchmark</xccdf:title>
  <xccdf:version>1.0.0</xccdf:version>

  <!-- Variables -->
  <xccdf:Value id="xccdf_com.hanalyx.openwatch_value_var_accounts_tmout" type="number">
    <xccdf:title>Session Timeout</xccdf:title>
    <xccdf:value>600</xccdf:value>
    <xccdf:lower-bound>60</xccdf:lower-bound>
    <xccdf:upper-bound>3600</xccdf:upper-bound>
  </xccdf:Value>

  <!-- Groups & Rules -->
  <xccdf:Group id="xccdf_com.hanalyx.openwatch_group_authentication">
    <xccdf:Rule id="xccdf_com.hanalyx.openwatch_rule_accounts_tmout">
      <xccdf:title>Set Interactive Session Timeout</xccdf:title>
      <xccdf:check system="http://oval.mitre.org/XMLSchema/oval-definitions-5">
        <xccdf:check-content-ref href="oscap-definitions.xml"/>
      </xccdf:check>
    </xccdf:Rule>
  </xccdf:Group>

  <!-- Profiles -->
  <xccdf:Profile id="xccdf_com.hanalyx.openwatch_profile_nist_800_53r5">
    <xccdf:title>NIST 800-53r5</xccdf:title>
    <xccdf:select idref="xccdf_com.hanalyx.openwatch_rule_accounts_tmout" selected="true"/>
  </xccdf:Profile>
</xccdf:Benchmark>
\`\`\`

### Tailoring XML
\`\`\`xml
<xccdf:Tailoring id="production_tailoring">
  <xccdf:version>1.0</xccdf:version>
  <xccdf:benchmark href="benchmark.xml"/>

  <xccdf:Profile id="nist_800_53r5_production" extends="nist_800_53r5">
    <xccdf:title>Production Environment</xccdf:title>
    <xccdf:set-value idref="xccdf_com.hanalyx.openwatch_value_var_accounts_tmout">300</xccdf:set-value>
  </xccdf:Profile>
</xccdf:Tailoring>
\`\`\`

## Usage

### Generate Benchmark
\`\`\`python
POST /api/v1/xccdf/generate-benchmark
{
  "benchmark_id": "nist-800-53r5",
  "title": "NIST SP 800-53 Revision 5",
  "description": "Security controls for federal information systems",
  "version": "1.0.0",
  "framework": "nist",
  "framework_version": "800-53r5"
}
\`\`\`

### Generate Tailoring
\`\`\`python
POST /api/v1/xccdf/generate-tailoring
{
  "tailoring_id": "prod_tailoring",
  "benchmark_href": "nist-800-53r5.xml",
  "profile_id": "xccdf_com.hanalyx.openwatch_profile_nist_800_53r5",
  "variable_overrides": {
    "xccdf_com.hanalyx.openwatch_value_var_accounts_tmout": "300"
  }
}
\`\`\`

### Scan with Generated Files
\`\`\`bash
# Generate benchmark via API, save to file
curl -X POST /api/v1/xccdf/generate-benchmark ... > benchmark.xml

# Generate tailoring via API
curl -X POST /api/v1/xccdf/generate-tailoring ... > tailoring.xml

# Scan with oscap
oscap xccdf eval \\
  --profile xccdf_com.hanalyx.openwatch_profile_nist_800_53r5 \\
  --tailoring-file tailoring.xml \\
  --results results.xml \\
  benchmark.xml
\`\`\`

## Implementation Notes

### XCCDF 1.2 ID Requirements
- All IDs must follow pattern: `xccdf_<reverse-DNS>_<type>_<name>`
- Reverse DNS: `com.hanalyx.openwatch`
- Types: benchmark, profile, group, rule, value
- Names: Derived from MongoDB rule_id, category, framework, etc.

### Variable Constraints
- Number types: lower-bound, upper-bound elements
- String types: choice elements for enums, match for regex patterns
- Boolean types: No additional constraints needed

### Profile Generation
- One profile per framework version found in rules
- Profiles automatically select matching rules via xccdf:select
- Tailoring extends profiles without modifying benchmark

## Testing

Manual XCCDF validation shows ID format requirements working correctly:
- ✅ Benchmark IDs properly formatted
- ✅ Value IDs with xccdf_ prefix
- ✅ Rule IDs with xccdf_ prefix
- ✅ Group IDs with xccdf_ prefix
- ✅ Profile IDs with xccdf_ prefix

Integration testing with real MongoDB data pending PR #97 merge.

## Related Issues

- Closes: #98
- Requires: #96 (SCAP converter with variables) - PR #97 pending
- Blocks: #99 (MongoDB-Based Scan Service)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
15 Security Hotspots

See analysis details on SonarQube Cloud

"""
try:
logger.info(
f"User {current_user.get('username')} generating benchmark: {request.benchmark_id}"

Check failure

Code scanning / CodeQL

Log Injection High

This log entry depends on a
user-provided value
.
This log entry depends on a
user-provided value
.

Copilot Autofix

AI 8 months ago

To fix the log injection flaw, sanitize all user-controllable data before including it in the log message. Specifically, for every value logged from request and current_user, strip out any newline (\n, \r\n) and carriage return characters, which can break log integrity.

  • In generate_benchmark, sanitize both current_user.get('username') and request.benchmark_id before including them in logs.
  • Modify line 48 (logging statement) so those values are passed through a replace chain (e.g. .replace('\n', '').replace('\r', '')).
  • No imports are needed—just use Python’s str.replace() method as shown.

Suggested changeset 1
backend/app/api/v1/endpoints/xccdf_api.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/backend/app/api/v1/endpoints/xccdf_api.py b/backend/app/api/v1/endpoints/xccdf_api.py
--- a/backend/app/api/v1/endpoints/xccdf_api.py
+++ b/backend/app/api/v1/endpoints/xccdf_api.py
@@ -44,8 +44,11 @@
     The generated benchmark can be used with oscap for scanning.
     """
     try:
+        # Sanitize user-provided values before logging to prevent log injection
+        username_sanitized = str(current_user.get('username', '')).replace('\n', '').replace('\r', '')
+        benchmark_id_sanitized = str(request.benchmark_id).replace('\n', '').replace('\r', '')
         logger.info(
-            f"User {current_user.get('username')} generating benchmark: {request.benchmark_id}"
+            f"User {username_sanitized} generating benchmark: {benchmark_id_sanitized}"
         )
         
         # Create generator service
EOF
@@ -44,8 +44,11 @@
The generated benchmark can be used with oscap for scanning.
"""
try:
# Sanitize user-provided values before logging to prevent log injection
username_sanitized = str(current_user.get('username', '')).replace('\n', '').replace('\r', '')
benchmark_id_sanitized = str(request.benchmark_id).replace('\n', '').replace('\r', '')
logger.info(
f"User {current_user.get('username')} generating benchmark: {request.benchmark_id}"
f"User {username_sanitized} generating benchmark: {benchmark_id_sanitized}"
)

# Create generator service
Copilot is powered by AI and may make mistakes. Always verify output.
"""
try:
logger.info(
f"User {current_user.get('username')} generating tailoring: {request.tailoring_id}"

Check failure

Code scanning / CodeQL

Log Injection High

This log entry depends on a
user-provided value
.
This log entry depends on a
user-provided value
.

Copilot Autofix

AI 8 months ago

To generally fix log injection issues, all user-provided input (which will end up in log messages) should be sanitized before being passed to any logger (or to any sink vulnerable to this kind of injection). For plain text log files (as in most default loggers), the basic step is to remove CR (\r), LF (\n), and similar line-breaking or control characters from the logged value. This can be accomplished using Python's str.replace() or a regular expression for robustness.

In the code snippet, request.tailoring_id is logged directly. We should sanitize this value before logging, by stripping or replacing any line breaks. Optionally, though not strictly necessary for log injection, we could also sanitize current_user.get('username') if it can be controlled by a user, but the core issue flagged by CodeQL is with request.tailoring_id.

Specifically, in the function at line 106 (generate_tailoring), prior to logging in line 124, add a line that sanitizes (e.g., using .replace('\n', '').replace('\r', '')) the request.tailoring_id value (done within a local variable or directly inline in the log string). Then, the logging call uses this sanitized ID in its message.

No extra imports are required. This is a single code edit in the file: in the body of generate_tailoring.


Suggested changeset 1
backend/app/api/v1/endpoints/xccdf_api.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/backend/app/api/v1/endpoints/xccdf_api.py b/backend/app/api/v1/endpoints/xccdf_api.py
--- a/backend/app/api/v1/endpoints/xccdf_api.py
+++ b/backend/app/api/v1/endpoints/xccdf_api.py
@@ -120,8 +120,12 @@
     The tailoring file is passed to oscap with --tailoring-file flag.
     """
     try:
+        safe_tailoring_id = (
+            request.tailoring_id.replace('\r', '').replace('\n', '')
+            if hasattr(request.tailoring_id, 'replace') else str(request.tailoring_id)
+        )
         logger.info(
-            f"User {current_user.get('username')} generating tailoring: {request.tailoring_id}"
+            f"User {current_user.get('username')} generating tailoring: {safe_tailoring_id}"
         )
         
         # Create generator service
EOF
@@ -120,8 +120,12 @@
The tailoring file is passed to oscap with --tailoring-file flag.
"""
try:
safe_tailoring_id = (
request.tailoring_id.replace('\r', '').replace('\n', '')
if hasattr(request.tailoring_id, 'replace') else str(request.tailoring_id)
)
logger.info(
f"User {current_user.get('username')} generating tailoring: {request.tailoring_id}"
f"User {current_user.get('username')} generating tailoring: {safe_tailoring_id}"
)

# Create generator service
Copilot is powered by AI and may make mistakes. Always verify output.
)

@validator('type')
def validate_type(cls, v):

Check notice

Code scanning / CodeQL

First parameter of a method is not named 'self' Note

Normal methods should have 'self', rather than 'cls', as their first parameter.

Copilot Autofix

AI 8 months ago

Copilot could not generate an autofix suggestion

Copilot could not generate an autofix suggestion for this alert. Try pushing a new commit or if the problem persists contact support.

return v

@validator('constraints')
def validate_constraints(cls, v, values):

Check notice

Code scanning / CodeQL

First parameter of a method is not named 'self' Note

Normal methods should have 'self', rather than 'cls', as their first parameter.

Copilot Autofix

AI 8 months ago

Copilot could not generate an autofix suggestion

Copilot could not generate an autofix suggestion for this alert. Try pushing a new commit or if the problem persists contact support.

"""

import re
from typing import Dict, List, Any, Optional, Set

Check notice

Code scanning / CodeQL

Unused import Note

Import of 'List' is not used.

Copilot Autofix

AI 8 months ago

To fix this issue, remove the unused List import from the typing import statement at line 14 in backend/app/services/scap_yaml_parser_service.py. This will clear unnecessary dependency clutter from the file and make the imports concise and maintainable. The recommended change is to edit line 14 so that only the actually used items from typing are imported: Dict, Any, Optional, and Set.

Suggested changeset 1
backend/app/services/scap_yaml_parser_service.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/backend/app/services/scap_yaml_parser_service.py b/backend/app/services/scap_yaml_parser_service.py
--- a/backend/app/services/scap_yaml_parser_service.py
+++ b/backend/app/services/scap_yaml_parser_service.py
@@ -11,7 +11,7 @@
 """
 
 import re
-from typing import Dict, List, Any, Optional, Set
+from typing import Dict, Any, Optional, Set
 from pathlib import Path
 import logging
 
EOF
@@ -11,7 +11,7 @@
"""

import re
from typing import Dict, List, Any, Optional, Set
from typing import Dict, Any, Optional, Set
from pathlib import Path
import logging

Copilot is powered by AI and may make mistakes. Always verify output.
Returns:
XCCDF Tailoring XML as string
"""
logger.info(f"Generating XCCDF Tailoring: {tailoring_id}")

Check failure

Code scanning / CodeQL

Log Injection High

This log entry depends on a
user-provided value
.

Copilot Autofix

AI 8 months ago

To fix the log injection, any value derived from user input and written to the log should be sanitized to remove carriage return (\r) and line feed (\n) characters, which could be used to inject new log entries or falsify existing ones. The most targeted and low-disruption fix is to sanitize tailoring_id (and any similar user parameters if present) just before logging it.

Specifically:

  • Before writing tailoring_id into the log, replace all instances of \r and \n in the value with the empty string (''). This can be done inline via .replace('\r', '').replace('\n', '').
  • No additional imports are needed.
  • The change should only affect the log message; there is no need to sanitize the in-memory value unless it is used elsewhere in a vulnerable fashion.
  • Only edit logger.info(f"Generating XCCDF Tailoring: {tailoring_id}") at line 147 of backend/app/services/xccdf_generator_service.py to interpolate a sanitized version of tailoring_id.

Suggested changeset 1
backend/app/services/xccdf_generator_service.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/backend/app/services/xccdf_generator_service.py b/backend/app/services/xccdf_generator_service.py
--- a/backend/app/services/xccdf_generator_service.py
+++ b/backend/app/services/xccdf_generator_service.py
@@ -144,7 +144,7 @@
         Returns:
             XCCDF Tailoring XML as string
         """
-        logger.info(f"Generating XCCDF Tailoring: {tailoring_id}")
+        logger.info(f"Generating XCCDF Tailoring: {tailoring_id.replace(chr(13), '').replace(chr(10), '')}")
 
         # Create root Tailoring element
         tailoring = ET.Element(
EOF
@@ -144,7 +144,7 @@
Returns:
XCCDF Tailoring XML as string
"""
logger.info(f"Generating XCCDF Tailoring: {tailoring_id}")
logger.info(f"Generating XCCDF Tailoring: {tailoring_id.replace(chr(13), '').replace(chr(10), '')}")

# Create root Tailoring element
tailoring = ET.Element(
Copilot is powered by AI and may make mistakes. Always verify output.
version_elem.text = "1.0"

# Add benchmark reference
benchmark_elem = ET.SubElement(

Check notice

Code scanning / CodeQL

Unused local variable Note

Variable benchmark_elem is not used.

Copilot Autofix

AI 8 months ago

To fix the problem, the assignment to the unused local variable benchmark_elem should be removed, leaving only the function call. Since the right-hand side (ET.SubElement(...)) has the side effect of adding the benchmark element to the parent XML element (tailoring), it needs to be retained as a statement for the side effect. Specifically, in backend/app/services/xccdf_generator_service.py, within the generate_tailoring method, on line 169, remove the assignment to benchmark_elem and simply call ET.SubElement(...) as a statement. No other changes are necessary, as no logic depends on the variable.

Suggested changeset 1
backend/app/services/xccdf_generator_service.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/backend/app/services/xccdf_generator_service.py b/backend/app/services/xccdf_generator_service.py
--- a/backend/app/services/xccdf_generator_service.py
+++ b/backend/app/services/xccdf_generator_service.py
@@ -166,7 +166,7 @@
         version_elem.text = "1.0"
 
         # Add benchmark reference
-        benchmark_elem = ET.SubElement(
+        ET.SubElement(
             tailoring,
             f"{{{self.NAMESPACES['xccdf']}}}benchmark",
             {
EOF
@@ -166,7 +166,7 @@
version_elem.text = "1.0"

# Add benchmark reference
benchmark_elem = ET.SubElement(
ET.SubElement(
tailoring,
f"{{{self.NAMESPACES['xccdf']}}}benchmark",
{
Copilot is powered by AI and may make mistakes. Always verify output.
{'system': check_system}
)

check_ref = ET.SubElement(

Check notice

Code scanning / CodeQL

Unused local variable Note

Variable check_ref is not used.

Copilot Autofix

AI 8 months ago

To fix the unused local variable issue, we should remove the assignment of ET.SubElement to the unused variable check_ref on line 401 and simply call ET.SubElement(...) as a standalone statement. This preserves the side effect of adding the subelement to the XML tree and makes it clear that there is no intention to use the resulting element. The change affects only a single line within the XCCDFGeneratorService class method (most likely in the _generate_rule_element method), in the file backend/app/services/xccdf_generator_service.py. No new imports, method definitions, or other changes are required.


Suggested changeset 1
backend/app/services/xccdf_generator_service.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/backend/app/services/xccdf_generator_service.py b/backend/app/services/xccdf_generator_service.py
--- a/backend/app/services/xccdf_generator_service.py
+++ b/backend/app/services/xccdf_generator_service.py
@@ -398,7 +398,7 @@
             {'system': check_system}
         )
 
-        check_ref = ET.SubElement(
+        ET.SubElement(
             check,
             f"{{{self.NAMESPACES['xccdf']}}}check-content-ref",
             {
EOF
@@ -398,7 +398,7 @@
{'system': check_system}
)

check_ref = ET.SubElement(
ET.SubElement(
check,
f"{{{self.NAMESPACES['xccdf']}}}check-content-ref",
{
Copilot is powered by AI and may make mistakes. Always verify output.
# Add variable exports if rule has variables
if rule.get('xccdf_variables'):
for var_id in rule['xccdf_variables'].keys():
export = ET.SubElement(

Check notice

Code scanning / CodeQL

Unused local variable Note

Variable export is not used.

Copilot Autofix

AI 8 months ago

The best way to fix this problem is to remove the assignment to the unused variable export and keep only the call to ET.SubElement(...) for its side effect (adding a new child XML element to the parent). This edit is within the _create_xccdf_rule method of the XCCDFGeneratorService class, specifically the block between lines 413 and 420. The rest of the logic remains unchanged, the element is created and attached to its parent, but no reference needs to be stored. No new dependencies or imports are required; removing export = is sufficient.

Suggested changeset 1
backend/app/services/xccdf_generator_service.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/backend/app/services/xccdf_generator_service.py b/backend/app/services/xccdf_generator_service.py
--- a/backend/app/services/xccdf_generator_service.py
+++ b/backend/app/services/xccdf_generator_service.py
@@ -410,7 +410,7 @@
         # Add variable exports if rule has variables
         if rule.get('xccdf_variables'):
             for var_id in rule['xccdf_variables'].keys():
-                export = ET.SubElement(
+                ET.SubElement(
                     check,
                     f"{{{self.NAMESPACES['xccdf']}}}check-export",
                     {
EOF
@@ -410,7 +410,7 @@
# Add variable exports if rule has variables
if rule.get('xccdf_variables'):
for var_id in rule['xccdf_variables'].keys():
export = ET.SubElement(
ET.SubElement(
check,
f"{{{self.NAMESPACES['xccdf']}}}check-export",
{
Copilot is powered by AI and may make mistakes. Always verify output.
rule_name = rule_id.replace('ow-', '')
rule_id = f"xccdf_com.hanalyx.openwatch_rule_{rule_name}"

select = ET.SubElement(

Check notice

Code scanning / CodeQL

Unused local variable Note

Variable select is not used.

Copilot Autofix

AI 8 months ago

The best way to fix the problem is to remove the assignment to the unused variable select on line 526. The call to ET.SubElement(...) has the required side effect of adding the subelement to the profile element, so the assignment is unnecessary. This edit is contained within the body of the _create_profile_element method and affects only line 526. No changes to imports, method signatures, or supporting code are needed.


Suggested changeset 1
backend/app/services/xccdf_generator_service.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/backend/app/services/xccdf_generator_service.py b/backend/app/services/xccdf_generator_service.py
--- a/backend/app/services/xccdf_generator_service.py
+++ b/backend/app/services/xccdf_generator_service.py
@@ -523,7 +523,7 @@
                 rule_name = rule_id.replace('ow-', '')
                 rule_id = f"xccdf_com.hanalyx.openwatch_rule_{rule_name}"
 
-            select = ET.SubElement(
+            ET.SubElement(
                 profile,
                 f"{{{self.NAMESPACES['xccdf']}}}select",
                 {
EOF
@@ -523,7 +523,7 @@
rule_name = rule_id.replace('ow-', '')
rule_id = f"xccdf_com.hanalyx.openwatch_rule_{rule_name}"

select = ET.SubElement(
ET.SubElement(
profile,
f"{{{self.NAMESPACES['xccdf']}}}select",
{
Copilot is powered by AI and may make mistakes. Always verify output.
remyluslosius added a commit that referenced this pull request Oct 15, 2025
…Routing

Implements complete scan orchestration system that routes compliance checks
to appropriate scanners (OSCAP, Kubernetes, cloud APIs) and stores results
in MongoDB.

## New Components

### 1. MongoDB Models (scan_models.py - 280 lines)
- **ScanResult**: Complete scan execution record with rule-level results
- **ScanConfiguration**: Scan settings (target, framework, variables)
- **RuleResult**: Individual rule check result with status and message
- **ScanResultSummary**: Aggregated statistics (pass/fail/error counts)
- **ScanTarget**: Target system definition (SSH host, K8s cluster, cloud account)
- **ScanSchedule**: Future enhancement for recurring scans

### 2. Scanner Interface (base_scanner.py - 180 lines)
- **BaseScanner**: Abstract base class for all scanners
- **scan()**: Execute compliance checks against target
- **_calculate_summary()**: Aggregate rule results into summary
- **_group_by_severity()**: Breakdown by high/medium/low
- **_group_by_scanner()**: Breakdown by scanner type
- Custom exceptions: ScannerNotAvailableError, ScannerExecutionError

### 3. OSCAP Scanner (oscap_scanner.py - 380 lines)
- **OSCAPScanner**: Traditional OVAL-based compliance scanning
- Generates XCCDF benchmark from MongoDB rules
- Creates tailoring files for variable overrides
- Executes oscap (local) or oscap-ssh (remote)
- Parses XCCDF results XML into RuleResult objects
- Supports SSH-based remote scanning

### 4. Kubernetes Scanner (kubernetes_scanner.py - 280 lines)
- **KubernetesScanner**: YAML-based checks for K8s/OpenShift
- Queries Kubernetes API using kubectl + JSONPath
- Evaluates conditions: equals, not_equals, contains, exists, any_exist
- Supports OpenShift-specific resources (image.config.openshift.io)
- Compatible with kubeconfig-based authentication

### 5. Scanner Factory (scanners/__init__.py - 60 lines)
- **ScannerFactory**: Registry and factory for scanner instances
- get_scanner(scanner_type): Create scanner on demand
- register_scanner(): Plugin support for custom scanners
- Available scanners: oscap, kubernetes (more coming: aws_api, azure_api, gcp_api)

### 6. Scan Orchestrator (scan_orchestrator_service.py - 280 lines)
- **ScanOrchestrator**: Central coordinator for multi-scanner execution
- execute_scan(): Main entry point for scan execution
- Queries MongoDB for rules matching framework/version
- Groups rules by scanner_type
- Executes scanners in parallel with asyncio.gather()
- Aggregates results from all scanners
- Stores complete results in MongoDB

### 7. Scan API Endpoints (scans_api.py - 220 lines)
- **POST /api/v1/scans/execute**: Execute compliance scan
- **GET /api/v1/scans/{scan_id}**: Get scan result details
- **GET /api/v1/scans**: List scans with filters (status, pagination)
- **DELETE /api/v1/scans/{scan_id}**: Delete scan result
- **GET /api/v1/scans/statistics/summary**: Aggregated scan statistics

## Scan Execution Flow

```
1. User submits ScanConfiguration via API
   ↓
2. ScanOrchestrator queries MongoDB for rules
   ↓
3. Rules grouped by scanner_type:
   - oscap: 45 rules
   - kubernetes: 12 rules
   ↓
4. Scanners execute in parallel:
   ├─ OSCAPScanner:
   │  ├─ Generate XCCDF benchmark
   │  ├─ Generate tailoring file (if variables provided)
   │  ├─ Execute oscap-ssh on target
   │  └─ Parse results XML → RuleResults
   │
   └─ KubernetesScanner:
      ├─ For each rule:
      │  ├─ Query K8s API via kubectl
      │  ├─ Evaluate condition
      │  └─ Create RuleResult
      └─ Return results
   ↓
5. Orchestrator aggregates results:
   - Combine all RuleResults
   - Calculate summary statistics
   - Store in MongoDB
   ↓
6. Return ScanResult to user
```

## Scanner Capabilities

| Scanner | Target Types | Capabilities | Status |
|---------|-------------|--------------|--------|
| oscap | SSH host, local | OVAL checks, XCCDF variables, tailoring | ✅ Implemented |
| kubernetes | K8s cluster | YAML checks, JSONPath queries, API access | ✅ Implemented |
| aws_api | AWS account | S3, IAM, VPC compliance | 🔜 Planned |
| azure_api | Azure subscription | Resource Manager checks | 🔜 Planned |
| gcp_api | GCP project | Cloud API checks | 🔜 Planned |

## Usage

### Execute Scan via API
```bash
curl -X POST http://localhost:8000/api/v1/scans/execute \\
  -H "Authorization: Bearer $TOKEN" \\
  -H "Content-Type: application/json" \\
  -d '{
    "target": {
      "type": "ssh_host",
      "identifier": "prod-web-01.example.com",
      "credentials": {"username": "root"}
    },
    "framework": "nist",
    "framework_version": "800-53r5",
    "variable_overrides": {
      "xccdf_com.hanalyx.openwatch_value_var_accounts_tmout": "300"
    }
  }'
```

### Check Scan Status
```bash
curl http://localhost:8000/api/v1/scans/{scan_id} \\
  -H "Authorization: Bearer $TOKEN"
```

### Response
```json
{
  "scan_id": "a1b2c3d4-...",
  "status": "completed",
  "started_at": "2025-10-15T08:00:00Z",
  "completed_at": "2025-10-15T08:05:23Z",
  "duration_seconds": 323.5,
  "summary": {
    "total_rules": 57,
    "passed": 45,
    "failed": 10,
    "error": 2,
    "compliance_percentage": 81.8,
    "by_severity": {
      "high": {"total": 15, "passed": 10, "failed": 5},
      "medium": {"total": 30, "passed": 28, "failed": 2},
      "low": {"total": 12, "passed": 7, "failed": 3}
    },
    "by_scanner": {
      "oscap": {"total": 45, "passed": 35, "failed": 8},
      "kubernetes": {"total": 12, "passed": 10, "failed": 2}
    }
  }
}
```

## Implementation Details

### Variable Override Application
OSCAP scanner generates tailoring files:
```xml
<xccdf:Tailoring>
  <xccdf:Profile id="customized" extends="nist_800_53_r5">
    <xccdf:set-value idref="xccdf_...value_var_accounts_tmout">300</xccdf:set-value>
  </xccdf:Profile>
</xccdf:Tailoring>
```

### Kubernetes Query Example
Rule check_content:
```json
{
  "resource_type": "image.config.openshift.io",
  "resource_name": "cluster",
  "yamlpath": ".spec.allowedRegistriesForImport[:].insecure",
  "expected_value": "false",
  "condition": "not_equals"
}
```

Scanner execution:
```bash
kubectl get image.config.openshift.io cluster \\
  -o jsonpath='{.spec.allowedRegistriesForImport[:].insecure}'
```

### Parallel Scanner Execution
```python
scanner_tasks = [
    oscap_scanner.scan(oscap_rules, target, variables),
    k8s_scanner.scan(k8s_rules, target, variables)
]
results = await asyncio.gather(*scanner_tasks)
```

## Testing

Integration testing requires:
- MongoDB with compliance rules (Issue #96)
- OSCAP installed (`oscap --version`)
- Test SSH target or local system
- Optional: Kubernetes cluster for K8s scanner tests

## Next Steps

After this PR merges:

1. **Issue #5**: ORSA Plugin Architecture (5-7 days)
   - Execute remediation content (Ansible, Bash)
   - Track remediation status
   - Rollback support

2. **Issue #6**: Scan Configuration API (3-4 days)
   - UI for benchmark selection
   - Variable customization interface
   - Tailoring file management

3. **Issue #7**: Frontend Variable Customization UI (5-7 days)
   - Framework selection
   - Variable override forms
   - Real-time scan status

## Related Issues

- Closes: #100
- Requires: #98 (XCCDF generator) - PR #99 pending
- Blocks: #5 (ORSA Plugin Architecture)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
remyluslosius added a commit that referenced this pull request Oct 15, 2025
…ash Executors

Implements complete ORSA (OpenWatch Remediation and Security Automation) plugin
architecture for automated remediation execution after compliance scans.

## New Components (7 files, ~2,500 lines)

### 1. MongoDB Models (backend/app/models/remediation_models.py - 330 lines)
**Purpose**: Track remediation execution lifecycle and results

**Key Models**:
- RemediationResult: Complete execution record with rollback support
  - Tracks status: pending → running → completed/failed/rolled_back
  - Stores stdout/stderr, changes made, variables applied
  - Audit log with timestamped actions
  - Rollback content and execution tracking

- BulkRemediationJob: Batch remediation coordinator
  - Progress tracking (total, completed, failed counts)
  - Success rate calculation
  - Individual remediation ID tracking

- RemediationRequest/BulkRemediationRequest: API request schemas
- RemediationSummary: Aggregated statistics

**Indexes**: remediation_id, rule_id, status, executed_by, scan_id, created_at

### 2. Base Executor Interface (backend/app/services/remediators/base_executor.py - 280 lines)
**Purpose**: Abstract interface for all remediation executors

**Design Pattern**: Strategy pattern for pluggable executors

**Key Features**:
- Abstract methods: execute(), rollback(), validate_content(), supports_target()
- Capability system: DRY_RUN, ROLLBACK, IDEMPOTENT, VARIABLE_SUBSTITUTION, REMOTE_EXECUTION
- Custom exceptions: ExecutorNotAvailableError, ExecutorValidationError, ExecutorExecutionError
- Variable substitution: {{var}} and ${var} patterns
- Change extraction for rollback tracking
- Async context manager support

**ExecutorMetadata**: Version, capabilities, supported targets for discovery

### 3. Ansible Executor (backend/app/services/remediators/ansible_executor.py - 380 lines)
**Purpose**: Execute Ansible playbooks from string content

**Capabilities**:
- ✅ Dry-run (--check mode)
- ✅ Rollback support
- ✅ Idempotent operations
- ✅ Variable substitution (--extra-vars)
- ✅ Remote execution (SSH)

**Features**:
- Dynamic inventory generation (local or SSH targets)
- YAML validation via yaml.safe_load()
- SSH key management (temp files with 0600 permissions)
- Ansible output parsing (PLAY RECAP, changed counts)
- Change extraction from task output
- Timeout support with process cleanup

**Execution Flow**:
1. Validate playbook YAML structure
2. Generate inventory file (local or remote)
3. Write SSH key if provided
4. Build ansible-playbook command with --extra-vars, --check, --private-key
5. Execute with timeout
6. Parse PLAY RECAP for change counts
7. Extract changes for rollback tracking

**Example Command**:
```bash
ansible-playbook playbook.yml -i inventory.ini \
  --extra-vars '{"var_tmout": "300"}' \
  --check \
  --private-key ssh_key \
  -e ansible_host_key_checking=False
```

### 4. Bash Executor (backend/app/services/remediators/bash_executor.py - 380 lines)
**Purpose**: Execute bash scripts from string content

**Capabilities**:
- ✅ Variable substitution (export statements)
- ✅ Remote execution (SSH)
- ⚠️ Limited dry-run (syntax check only via bash -n)
- ⚠️ NOT idempotent by default

**Features**:
- Syntax validation: bash -n (noexec mode)
- Variable preparation: Prepends export statements
- Local execution: Direct bash script_file
- Remote execution: SSH with script piped to stdin
- Change extraction: Looks for "Changed:", "Modified:", "Created:" patterns
- Timeout support

**Script Preparation**:
```bash
#!/bin/bash
set -e  # Exit on error

# Environment variables
export var_tmout='300'
export var_password_minlen='14'

# Remediation script
[original content]
```

**Remote Execution**:
```bash
ssh -i ssh_key -o StrictHostKeyChecking=no \
  root@192.168.1.100 'bash -s' < script.sh
```

### 5. Executor Factory (backend/app/services/remediators/__init__.py - 180 lines)
**Purpose**: Registry and factory for executor instantiation

**Pattern**: Factory pattern with runtime registration

**Registry**:
- ansible: AnsibleExecutor
- bash: BashExecutor
- (Future): terraform, kubernetes, python

**Methods**:
- get_executor(type): Instantiate executor
- register_executor(type, class): Runtime plugin registration
- list_executors(available_only): Discovery
- get_executor_metadata(type): Capabilities, version, targets
- get_all_executor_metadata(): All registered executors

**Example**:
```python
executor = RemediationExecutorFactory.get_executor('ansible')
metadata = RemediationExecutorFactory.get_executor_metadata('ansible')
# {"name": "ansible", "version": "2.14.3", "capabilities": [...]}
```

### 6. Remediation Orchestrator (backend/app/services/remediation_orchestrator_service.py - 420 lines)
**Purpose**: Central coordinator for remediation lifecycle

**Workflow**:
1. Query rule from MongoDB
2. Extract remediation content and type
3. Prepare variables (defaults + overrides)
4. Get executor from factory
5. Execute remediation
6. Track status: PENDING → RUNNING → COMPLETED/FAILED
7. Generate rollback content if successful
8. Store RemediationResult in MongoDB
9. Add audit log entries

**Key Methods**:
- execute_remediation(): Single rule execution
- execute_bulk_remediation(): Batch execution from scan results
  - Query failed rules from scan
  - Execute each remediation sequentially
  - Track job progress (completed/failed counts)
  - Calculate success rate
- rollback_remediation(): Execute rollback content
- get_remediation_result(): Query by ID
- list_remediations(): Pagination and filtering
- get_remediation_statistics(): Aggregated stats

**Bulk Remediation Sources**:
1. scan_id: Remediate all failed rules from scan
2. rule_ids: Explicit list of rules
3. rule_filter: Query filter (e.g., {"severity": ["high"]})

**Variable Preparation**:
```python
# Start with rule defaults
if 'xccdf_variables' in rule:
    for var_id, var_def in rule['xccdf_variables'].items():
        variables[var_id] = var_def['default']

# Apply user overrides
variables.update(overrides)
```

### 7. Remediation API (backend/app/api/v1/endpoints/remediation_api.py - 420 lines)
**Purpose**: REST API for remediation operations

**Endpoints**:
- POST /api/v1/remediation/execute - Execute single remediation
- POST /api/v1/remediation/execute-bulk - Batch remediation
- GET /api/v1/remediation/{id} - Get result
- GET /api/v1/remediation/ - List with filters (status, scan_id, pagination)
- POST /api/v1/remediation/{id}/rollback - Rollback remediation
- DELETE /api/v1/remediation/{id} - Delete result
- GET /api/v1/remediation/statistics/summary - Aggregated stats
- GET /api/v1/remediation/executors/available - List executors
- GET /api/v1/remediation/jobs/{id} - Get bulk job status

**Authorization**:
- Users can only view/execute/rollback their own remediations
- Admins can access all remediations

**Query Parameters**:
- skip/limit: Pagination
- status: Filter by RemediationStatus
- scan_id: Filter by source scan

## API Usage Examples

### Execute Single Remediation
```bash
curl -X POST http://localhost:8000/api/v1/remediation/execute \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "rule_id": "xccdf_com.hanalyx.openwatch_rule_accounts_tmout",
    "target": {
      "type": "ssh_host",
      "identifier": "192.168.1.100",
      "credentials": {
        "username": "root",
        "ssh_key": "-----BEGIN OPENSSH PRIVATE KEY-----\n..."
      }
    },
    "variable_overrides": {
      "var_accounts_tmout": "300"
    },
    "dry_run": false
  }'
```

### Bulk Remediation (Failed Rules from Scan)
```bash
curl -X POST http://localhost:8000/api/v1/remediation/execute-bulk \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "scan_id": "550e8400-e29b-41d4-a716-446655440000",
    "rule_filter": {
      "severity": ["high", "critical"]
    },
    "target": {
      "type": "ssh_host",
      "identifier": "prod-web-01.example.com",
      "credentials": {...}
    },
    "dry_run": true
  }'
```

### Rollback Remediation
```bash
curl -X POST http://localhost:8000/api/v1/remediation/{id}/rollback \
  -H "Authorization: Bearer $TOKEN"
```

### List Remediations
```bash
curl "http://localhost:8000/api/v1/remediation/?status=completed&limit=10" \
  -H "Authorization: Bearer $TOKEN"
```

### Get Statistics
```bash
curl "http://localhost:8000/api/v1/remediation/statistics/summary?days=30" \
  -H "Authorization: Bearer $TOKEN"

# Response:
{
  "total": 150,
  "completed": 120,
  "failed": 20,
  "success_rate": 80.0,
  "by_executor": {"ansible": 100, "bash": 50}
}
```

### List Available Executors
```bash
curl http://localhost:8000/api/v1/remediation/executors/available \
  -H "Authorization: Bearer $TOKEN"

# Response:
[
  {
    "name": "ansible",
    "version": "2.14.3",
    "capabilities": ["dry_run", "rollback", "idempotent"],
    "available": true
  }
]
```

## Remediation Execution Flow

```
API Request
    ↓
RemediationOrchestrator.execute_remediation()
    ↓
Query rule from MongoDB
    ↓
Extract remediation: {type: "ansible", content: "..."}
    ↓
Prepare variables: defaults + overrides
    ↓
RemediationExecutorFactory.get_executor(type)
    ↓
AnsibleExecutor.execute(content, target, variables, dry_run)
    ↓
  1. Validate playbook YAML
  2. Generate inventory file
  3. Write SSH key (0600 permissions)
  4. Build command: ansible-playbook --extra-vars --check --private-key
  5. Execute with timeout
  6. Parse PLAY RECAP for changes
    ↓
RemediationExecutionResult: {success, stdout, stderr, changes_made, duration}
    ↓
Update RemediationResult:
  - status: RUNNING → COMPLETED/FAILED
  - execution_result: {...}
  - rollback_available: true (if changes made)
  - rollback_content: "..." (future: auto-generated)
    ↓
Save to MongoDB
    ↓
Add audit log entry: "Completed"
    ↓
Return RemediationResult
```

## Executor Capabilities Matrix

| Executor | Dry-Run | Rollback | Idempotent | Variables | Remote | Version Detection |
|----------|---------|----------|------------|-----------|--------|-------------------|
| Ansible  | ✅ Full | ✅ Yes   | ✅ Yes     | ✅ Yes    | ✅ SSH | ansible-playbook --version |
| Bash     | ⚠️ Syntax only | ✅ Yes | ❌ No | ✅ Yes | ✅ SSH | bash --version |
| Terraform (future) | ✅ Plan | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Providers | terraform version |
| Kubernetes (future) | ✅ --dry-run | ✅ Yes | ✅ Yes | ✅ Yes | ✅ kubectl | kubectl version |

## Security Considerations

1. **SSH Key Storage**:
   - Temporary files with 0600 permissions
   - Deleted after execution
   - TODO: Encrypt in MongoDB (currently plaintext in credentials dict)

2. **Script Execution**:
   - Bash scripts run with 'set -e' (exit on error)
   - Ansible uses --check for dry-run validation
   - Timeout enforcement prevents runaway processes

3. **Authorization**:
   - Users can only access their own remediations
   - Admins can access all
   - Rollback requires original execution ownership

4. **Audit Trail**:
   - All status changes logged with timestamps
   - Username tracking: executed_by
   - Complete stdout/stderr capture

## Dependencies

**Python Packages** (add to requirements.txt):
- ansible-core>=2.14.0 (for Ansible executor)
- paramiko>=3.0.0 (for SSH in Bash executor)
- pyyaml>=6.0 (for Ansible playbook parsing)

**System Tools**:
- ansible-playbook (Ansible executor)
- bash (Bash executor)
- ssh (remote execution)

## Known Limitations

1. **Rollback Generation**: Not yet implemented
   - Currently returns None in _generate_rollback_content()
   - Future: Parse changes_made and generate inverse operations

2. **Bash Idempotency**: Scripts are NOT idempotent by default
   - Users must write idempotent scripts manually
   - Consider adding idempotency wrappers (check-before-change)

3. **Ansible Vault**: Not yet implemented
   - No support for encrypted playbooks or variables
   - Future: Add --vault-password-file support

4. **Parallel Bulk Execution**: Sequential only
   - execute_bulk_remediation() runs remediations one-by-one
   - Future: Use asyncio.gather() for parallel execution

5. **Credential Encryption**: SSH keys stored plaintext in MongoDB
   - TODO: Integrate with Fernet encryption (like scan credentials)

## Testing Strategy

**Unit Tests Needed**:
- Executor validation methods
- Variable substitution
- Change extraction from output
- Error handling (timeouts, invalid content)

**Integration Tests Needed**:
- Ansible playbook execution against test VM
- Bash script execution (local and remote)
- Rollback functionality
- Bulk remediation workflow

**E2E Tests**:
1. Scan → Identify Failed Rules
2. Remediate (dry-run=true) → Verify no changes
3. Remediate (dry-run=false) → Apply changes
4. Re-scan → Verify rules now pass
5. Rollback → Restore original state
6. Re-scan → Verify rules fail again

## Future Enhancements

1. **Additional Executors**:
   - TerraformExecutor: Infrastructure remediation
   - KubernetesExecutor: Manifest application
   - PythonExecutor: Custom Python scripts

2. **Intelligent Rollback**:
   - Parse execution output for changes
   - Generate inverse operations automatically
   - Store pre-remediation state snapshots

3. **Approval Workflow**:
   - Require manual approval for production systems
   - Multi-stage approval (dev → staging → prod)

4. **Parallel Execution**:
   - Bulk remediation with asyncio.gather()
   - Dependency resolution for ordered execution

5. **Remediation Templates**:
   - Pre-built remediation content library
   - Template variables with validation

## Phase 1 Status

✅ Completed (5/7 tasks):
1. ✅ Enhanced ComplianceRule Model (PR #95 - merged)
2. ✅ Enhanced SCAP Converter (PR #97 - pending)
3. ✅ XCCDF Generator (PR #99 - pending)
4. ✅ Scan Service (PR #101 - pending)
5. ✅ **ORSA Remediation Engine (this commit)**

🚧 Remaining (2/7 tasks):
6. ⏳ Scan Configuration API (3-4 days)
7. ⏳ Frontend Variable Customization UI (5-7 days)

## Related Issues

- Implements: #102
- Depends on: #96 (PR #97 - remediation field)
- Blocks: #6 (Scan Configuration API)

Co-Authored-By: Claude <noreply@anthropic.com>
remyluslosius added a commit that referenced this pull request Oct 15, 2025
…very & Template Management

Implements complete scan configuration API for framework selection, variable
management, and template-based scan configurations.

New Components (4 files, ~1,800 lines)

1. Scan Configuration Models (backend/app/models/scan_config_models.py - 280 lines)
Purpose: MongoDB models and API schemas for templates and framework metadata

Key Models:
- ScanTemplate: Saved scan configuration with framework, variables, filters, sharing
- VariableDefinition: XCCDF variable with type and constraints
- FrameworkMetadata: Framework discovery with counts and versions
- API Schemas: Request/response models

2. Framework Metadata Service (backend/app/services/framework_metadata_service.py - 420 lines)
Purpose: Discover frameworks and validate variable values

Key Methods:
- list_frameworks(): Aggregate framework metadata
- get_framework_details(): Complete framework/version info
- get_variables(): Extract variables from rules
- validate_variable_value(): Constraint validation (type, range, choices, regex)
- validate_variables(): Batch validation

3. Scan Template Service (backend/app/services/scan_template_service.py - 380 lines)
Purpose: CRUD operations for scan templates

Key Methods:
- create_template(), list_templates(), update_template(), delete_template()
- apply_template(): Generate scan configuration
- set_as_default(), clone_template()
- share_template()/unshare_template(): Access control

4. Scan Configuration API (backend/app/api/v1/endpoints/scan_config_api.py - 720 lines)
Purpose: REST API with 14 endpoints

Framework Discovery:
- GET /frameworks
- GET /frameworks/{framework}/{version}
- GET /frameworks/{framework}/{version}/variables
- POST /frameworks/{framework}/{version}/validate

Template Management:
- POST /templates
- GET /templates
- GET /templates/{id}
- PUT /templates/{id}
- DELETE /templates/{id}
- POST /templates/{id}/apply
- POST /templates/{id}/clone
- POST /templates/{id}/set-default
- GET /statistics

Phase 1 Status: 6/7 tasks completed (86%)

Related Issues:
- Implements: #104
- Depends on: #98 (PR #99), #100 (PR #101)
- Blocks: #7 (Frontend UI)

Co-Authored-By: Claude <noreply@anthropic.com>
@remyluslosius remyluslosius merged commit 34293e6 into main Oct 15, 2025
16 of 27 checks passed
@remyluslosius remyluslosius deleted the feature/xccdf-generator branch October 15, 2025 14:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

XCCDF Data-Stream Generator from MongoDB

2 participants