Skip to content

Commit 92954a2

Browse files
committed
Phase 2: Add CVSS 4.0 historical tracking with SQLite database and dashboard improvements
1 parent bbd94f7 commit 92954a2

File tree

4 files changed

+923
-575
lines changed

4 files changed

+923
-575
lines changed
Lines changed: 88 additions & 158 deletions
Original file line numberDiff line numberDiff line change
@@ -1,169 +1,99 @@
1-
name: Security Tests
1+
name: OpenMRS O3 Security Tests - CVSS 4.0
22

33
on:
44
push:
5-
branches: [ main, develop ]
5+
branches: [ main, cvss-4.0-* ]
66
pull_request:
7-
branches: [ main, develop ]
7+
branches: [ main ]
88
schedule:
9-
- cron: "0 0 * * *"
9+
# Run daily at 2 AM UTC
10+
- cron: '0 2 * * *'
1011
workflow_dispatch:
1112

1213
jobs:
1314
security-tests:
1415
runs-on: ubuntu-latest
15-
timeout-minutes: 15
16-
16+
1717
steps:
18-
- name: Checkout code
19-
uses: actions/checkout@v4
20-
21-
# Starting early since OpenMRS takes time to spin up; this allows it to initialize
22-
# in the background while dependencies are being installed.
23-
- name: Start an OpenMRS instance
24-
run: docker compose -f docker-compose.yml up -d
25-
26-
- name: Set up Python
27-
uses: actions/setup-python@v5
28-
with:
29-
python-version: '3.11'
30-
31-
- name: Install Python dependencies
32-
run: |
33-
python -m pip install --upgrade pip
34-
35-
echo "Searching for requirements.txt files..."
36-
find . -name "requirements.txt" -type f | while read req; do
37-
echo "Installing dependencies from: $req"
38-
pip install -r "$req"
39-
done
40-
41-
- name: Install Playwright browsers
42-
run: |
43-
python -m playwright install chromium
44-
45-
- name: Wait for the OpenMRS instance to start
46-
id: wait-for-omrs-instance
47-
run: while [[ "$(echo $(curl -s -o /dev/null -w '%{http_code}' http://localhost/openmrs/health/started))" != "200" ]]; do echo "$(curl -i http://localhost/openmrs/health/started)"; sleep 10; done
48-
49-
- name: Run Security Tests
50-
if: always() && (steps.wait-for-omrs-instance.outcome == 'success')
51-
run: |
52-
# Run pytest and capture ALL output (including CVSS scores)
53-
pytest tests/ \
54-
--html=report.html \
55-
--self-contained-html \
56-
--json-report \
57-
--json-report-file=report.json \
58-
-v -s 2>&1 | tee test_output.log
59-
continue-on-error: true
60-
61-
- name: Extract CVSS Scores and Generate Dashboard
62-
if: always()
63-
run: python scripts/generate_security_dashboard.py
64-
65-
- name: Upload Security Dashboard
66-
if: always()
67-
uses: actions/upload-artifact@v4
68-
with:
69-
name: security-dashboard
70-
path: security_dashboard.html
71-
retention-days: 30
72-
73-
- name: Upload HTML Report
74-
if: always()
75-
uses: actions/upload-artifact@v4
76-
with:
77-
name: security-test-report-html
78-
path: report.html
79-
retention-days: 30
80-
81-
- name: Upload JSON Results
82-
if: always()
83-
uses: actions/upload-artifact@v4
84-
with:
85-
name: security-test-report-json
86-
path: |
87-
report.json
88-
security_results.json
89-
retention-days: 30
90-
91-
- name: Prepare GitHub Pages Deployment
92-
if: github.ref == 'refs/heads/main'
93-
run: |
94-
mkdir -p gh-pages
95-
cp security_dashboard.html gh-pages/index.html
96-
cp report.html gh-pages/detailed-report.html
97-
cp security_results.json gh-pages/results.json
98-
99-
- name: Deploy to GitHub Pages
100-
if: github.ref == 'refs/heads/main'
101-
uses: peaceiris/actions-gh-pages@v3
102-
with:
103-
github_token: ${{ secrets.GITHUB_TOKEN }}
104-
publish_dir: ./gh-pages
105-
cname: cvss-report.openmrs.org
106-
keep_files: false
107-
108-
- name: Comment on PR
109-
if: ${{ github.event_name == 'pull_request' && github.repository_owner == 'openmrs' }}
110-
continue-on-error: true
111-
uses: actions/github-script@v7
112-
with:
113-
script: |
114-
const fs = require('fs');
115-
116-
let summary = '## 🔒 Security Test Results\n\n';
117-
118-
if (fs.existsSync('security_results.json')) {
119-
const data = JSON.parse(fs.readFileSync('security_results.json', 'utf8'));
120-
121-
summary += `- **Total Tests:** ${data.total_tests}\n`;
122-
summary += `- **Completed:** ✅ ${data.passed}\n`;
123-
summary += `- **Errors:** ❌ ${data.failed}\n\n`;
124-
125-
summary += '### Test Details with CVSS Scores\n\n';
126-
summary += '| Test | Execution | CVSS Score | Severity | Duration |\n';
127-
summary += '|------|-----------|------------|----------|----------|\n';
128-
129-
data.tests.forEach(test => {
130-
const icon = test.outcome === 'passed' ? '✅' : '❌';
131-
const status = test.outcome === 'passed' ? 'Completed' : 'Error';
132-
const name = test.name.replace(/_/g, ' ');
133-
const cvss = test.cvss_score ? test.cvss_score.toFixed(1) : 'N/A';
134-
let severity = 'N/A';
135-
if (test.cvss_score) {
136-
if (test.cvss_score >= 9.0) severity = '🔴 CRITICAL';
137-
else if (test.cvss_score >= 7.0) severity = '🟠 HIGH';
138-
else if (test.cvss_score >= 4.0) severity = '🟡 MEDIUM';
139-
else severity = '🟢 LOW';
140-
}
141-
summary += `| ${name} | ${icon} ${status} | ${cvss} | ${severity} | ${test.duration.toFixed(2)}s |\n`;
142-
});
143-
144-
summary += '\n📊 [Download Security Dashboard](../actions/runs/${{ github.run_id }})\n';
145-
} else {
146-
summary += '❌ No test results available\n';
147-
}
148-
149-
github.rest.issues.createComment({
150-
issue_number: context.issue.number,
151-
owner: context.repo.owner,
152-
repo: context.repo.repo,
153-
body: summary
154-
});
155-
156-
- name: Capture Server Logs
157-
if: always()
158-
uses: jwalton/gh-docker-logs@v2
159-
with:
160-
dest: "./logs"
161-
162-
- name: Upload Logs as Artifact
163-
uses: actions/upload-artifact@v4
164-
if: always()
165-
with:
166-
name: server-logs
167-
path: "./logs"
168-
retention-days: 2
169-
overwrite: true
18+
- name: Checkout code
19+
uses: actions/checkout@v3
20+
21+
- name: Set up Python
22+
uses: actions/setup-python@v4
23+
with:
24+
python-version: '3.10'
25+
26+
- name: Install dependencies
27+
run: |
28+
python -m pip install --upgrade pip
29+
pip install -r requirements.txt
30+
playwright install chromium
31+
32+
- name: Start OpenMRS O3 with Docker
33+
run: |
34+
docker-compose up -d
35+
echo "Waiting for OpenMRS to be ready..."
36+
sleep 120
37+
38+
- name: Download Previous Database
39+
continue-on-error: true
40+
run: |
41+
# Download the most recent database artifact from previous runs
42+
# This allows us to track CVSS scores over time
43+
gh run download --name test-database --dir . || echo "No previous database found (this is OK for first run)"
44+
env:
45+
GH_TOKEN: ${{ github.token }}
46+
47+
- name: Run CVSS 4.0 Security Tests
48+
run: |
49+
pytest tests/authentication/test_01_brute_force_password.py -v --tb=short
50+
env:
51+
GITHUB_SHA: ${{ github.sha }}
52+
53+
- name: Upload Database Artifact
54+
if: always()
55+
uses: actions/upload-artifact@v3
56+
with:
57+
name: test-database
58+
path: test_results.db
59+
retention-days: 90
60+
61+
- name: Generate Security Dashboard
62+
if: always()
63+
run: |
64+
python scripts/generate_security_dashboard.py
65+
66+
- name: Deploy Dashboard to GitHub Pages
67+
if: always()
68+
uses: peaceiris/actions-gh-pages@v3
69+
with:
70+
github_token: ${{ secrets.GITHUB_TOKEN }}
71+
publish_dir: ./scripts
72+
publish_branch: gh-pages
73+
enable_jekyll: false
74+
75+
- name: Display Test Summary
76+
if: always()
77+
run: |
78+
echo "==================================================================="
79+
echo "CVSS 4.0 Security Test Results"
80+
echo "==================================================================="
81+
if [ -f test_results.db ]; then
82+
python -c "
83+
from scripts.database import SecurityTestDatabase
84+
db = SecurityTestDatabase()
85+
scores = db.get_all_current_scores()
86+
for score in scores:
87+
print(f\"Test: {score['test_name']}\")
88+
print(f\" Baseline: {score['baseline_score']:.1f}\")
89+
print(f\" Current: {score['current_score']:.1f}\")
90+
print(f\" Improvement: {score['relative_score']:+.1f}\")
91+
print()
92+
db.close()
93+
"
94+
else
95+
echo "No database found"
96+
fi
97+
echo "==================================================================="
98+
echo "Dashboard URL: https://cvss-report.openmrs.org"
99+
echo "==================================================================="

0 commit comments

Comments
 (0)