|
1 | | -name: Security Tests |
| 1 | +name: OpenMRS O3 Security Tests - CVSS 4.0 |
2 | 2 |
|
3 | 3 | on: |
4 | 4 | push: |
5 | | - branches: [ main, develop ] |
| 5 | + branches: [ main, cvss-4.0-* ] |
6 | 6 | pull_request: |
7 | | - branches: [ main, develop ] |
| 7 | + branches: [ main ] |
8 | 8 | schedule: |
9 | | - - cron: "0 0 * * *" |
| 9 | + # Run daily at 2 AM UTC |
| 10 | + - cron: '0 2 * * *' |
10 | 11 | workflow_dispatch: |
11 | 12 |
|
12 | 13 | jobs: |
13 | 14 | security-tests: |
14 | 15 | runs-on: ubuntu-latest |
15 | | - timeout-minutes: 15 |
16 | | - |
| 16 | + |
17 | 17 | 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