Skip to content

Commit 835772d

Browse files
committed
build pipeline
1 parent 0261207 commit 835772d

3 files changed

Lines changed: 388 additions & 0 deletions

File tree

Lines changed: 365 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,365 @@
1+
name: DevSecOps Pipeline
2+
3+
on:
4+
push:
5+
branches: [ main, master, develop ]
6+
pull_request:
7+
branches: [ main, master, develop ]
8+
workflow_dispatch:
9+
10+
env:
11+
REGISTRY: ghcr.io
12+
IMAGE_NAME: solar-system
13+
DOCKER_IMAGE_TAG: ${{ github.sha }}
14+
15+
jobs:
16+
# Stage 1 & 2: Code Checkout and Dependency Installation
17+
build-and-test:
18+
name: Build and Unit Tests
19+
runs-on: ubuntu-latest
20+
21+
steps:
22+
- name: Checkout code
23+
uses: actions/checkout@v4
24+
25+
- name: Setup Node.js
26+
uses: actions/setup-node@v4
27+
with:
28+
node-version: '18'
29+
cache: 'npm'
30+
31+
- name: Install dependencies
32+
run: npm install
33+
34+
- name: Run unit tests
35+
run: npm test
36+
env:
37+
MONGO_URI: ${{ secrets.MONGO_URI }}
38+
MONGO_USERNAME: ${{ secrets.MONGO_USERNAME }}
39+
MONGO_PASSWORD: ${{ secrets.MONGO_PASSWORD }}
40+
41+
- name: Upload test results
42+
if: always()
43+
uses: actions/upload-artifact@v4
44+
with:
45+
name: test-results
46+
path: test-results.xml
47+
48+
# Stage 4: Code Coverage
49+
code-coverage:
50+
name: Code Coverage Analysis
51+
runs-on: ubuntu-latest
52+
needs: build-and-test
53+
54+
steps:
55+
- name: Checkout code
56+
uses: actions/checkout@v4
57+
58+
- name: Setup Node.js
59+
uses: actions/setup-node@v4
60+
with:
61+
node-version: '18'
62+
cache: 'npm'
63+
64+
- name: Install dependencies
65+
run: npm install
66+
67+
- name: Run coverage analysis
68+
run: npm run coverage
69+
env:
70+
MONGO_URI: ${{ secrets.MONGO_URI }}
71+
MONGO_USERNAME: ${{ secrets.MONGO_USERNAME }}
72+
MONGO_PASSWORD: ${{ secrets.MONGO_PASSWORD }}
73+
74+
- name: Upload coverage reports
75+
uses: actions/upload-artifact@v4
76+
with:
77+
name: coverage-reports
78+
path: |
79+
coverage/
80+
.nyc_output/
81+
82+
# Stage 5: SAST - Static Application Security Testing
83+
sast-semgrep:
84+
name: SAST - Semgrep
85+
runs-on: ubuntu-latest
86+
87+
steps:
88+
- name: Checkout code
89+
uses: actions/checkout@v4
90+
91+
- name: Run Semgrep
92+
uses: returntocorp/semgrep-action@v1
93+
with:
94+
config: >-
95+
p/security-audit
96+
p/nodejs
97+
p/owasp-top-ten
98+
p/javascript
99+
100+
- name: Upload Semgrep results
101+
if: always()
102+
uses: actions/upload-artifact@v4
103+
with:
104+
name: semgrep-results
105+
path: semgrep-results.json
106+
107+
# Stage 6: Dependency Scanning
108+
dependency-scan:
109+
name: Dependency Scanning - Snyk
110+
runs-on: ubuntu-latest
111+
112+
steps:
113+
- name: Checkout code
114+
uses: actions/checkout@v4
115+
116+
- name: Setup Node.js
117+
uses: actions/setup-node@v4
118+
with:
119+
node-version: '18'
120+
121+
- name: Install dependencies
122+
run: npm install
123+
124+
- name: Run Snyk to check for vulnerabilities
125+
uses: snyk/actions/node@master
126+
continue-on-error: true
127+
env:
128+
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
129+
with:
130+
args: --severity-threshold=high --json-file-output=snyk-results.json
131+
132+
- name: Upload Snyk results
133+
if: always()
134+
uses: actions/upload-artifact@v4
135+
with:
136+
name: snyk-results
137+
path: snyk-results.json
138+
139+
- name: Run npm audit
140+
run: npm audit --json > npm-audit-results.json
141+
continue-on-error: true
142+
143+
- name: Upload npm audit results
144+
if: always()
145+
uses: actions/upload-artifact@v4
146+
with:
147+
name: npm-audit-results
148+
path: npm-audit-results.json
149+
150+
# Stage 7: Secret Detection
151+
secret-scan:
152+
name: Secret Detection - TruffleHog
153+
runs-on: ubuntu-latest
154+
155+
steps:
156+
- name: Checkout code
157+
uses: actions/checkout@v4
158+
with:
159+
fetch-depth: 0 # Full history for secret scanning
160+
161+
- name: TruffleHog OSS
162+
uses: trufflesecurity/trufflehog@main
163+
with:
164+
path: ./
165+
base: ${{ github.event.repository.default_branch }}
166+
head: HEAD
167+
extra_args: --debug --only-verified
168+
169+
# Stage 8: Docker Build and Push
170+
docker-build:
171+
name: Docker Build and Push
172+
runs-on: ubuntu-latest
173+
needs: [build-and-test, code-coverage, sast-semgrep, dependency-scan, secret-scan]
174+
permissions:
175+
contents: read
176+
packages: write
177+
178+
steps:
179+
- name: Checkout code
180+
uses: actions/checkout@v4
181+
182+
- name: Set up Docker Buildx
183+
uses: docker/setup-buildx-action@v3
184+
185+
- name: Log in to GitHub Container Registry
186+
uses: docker/login-action@v3
187+
with:
188+
registry: ${{ env.REGISTRY }}
189+
username: ${{ github.actor }}
190+
password: ${{ secrets.GITHUB_TOKEN }}
191+
192+
- name: Extract metadata for Docker
193+
id: meta
194+
uses: docker/metadata-action@v5
195+
with:
196+
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
197+
tags: |
198+
type=sha,prefix={{branch}}-
199+
type=ref,event=branch
200+
type=ref,event=pr
201+
type=semver,pattern={{version}}
202+
type=semver,pattern={{major}}.{{minor}}
203+
type=raw,value=latest,enable={{is_default_branch}}
204+
205+
- name: Build and push Docker image
206+
uses: docker/build-push-action@v5
207+
with:
208+
context: .
209+
push: true
210+
tags: ${{ steps.meta.outputs.tags }}
211+
labels: ${{ steps.meta.outputs.labels }}
212+
cache-from: type=gha
213+
cache-to: type=gha,mode=max
214+
215+
- name: Save image name for later stages
216+
run: |
217+
echo "FULL_IMAGE_NAME=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-${{ github.sha }}" >> $GITHUB_ENV
218+
echo "Image pushed: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-${{ github.sha }}"
219+
220+
# Stage 9: Container Scanning
221+
container-scan:
222+
name: Container Scanning - Trivy
223+
runs-on: ubuntu-latest
224+
needs: docker-build
225+
permissions:
226+
contents: read
227+
packages: read
228+
security-events: write
229+
230+
steps:
231+
- name: Log in to GitHub Container Registry
232+
uses: docker/login-action@v3
233+
with:
234+
registry: ${{ env.REGISTRY }}
235+
username: ${{ github.actor }}
236+
password: ${{ secrets.GITHUB_TOKEN }}
237+
238+
- name: Run Trivy vulnerability scanner
239+
uses: aquasecurity/trivy-action@master
240+
with:
241+
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-${{ github.sha }}
242+
format: 'sarif'
243+
output: 'trivy-results.sarif'
244+
severity: 'CRITICAL,HIGH,MEDIUM'
245+
246+
- name: Upload Trivy results to GitHub Security
247+
uses: github/codeql-action/upload-sarif@v3
248+
if: always()
249+
with:
250+
sarif_file: 'trivy-results.sarif'
251+
252+
- name: Run Trivy for JSON output
253+
uses: aquasecurity/trivy-action@master
254+
with:
255+
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-${{ github.sha }}
256+
format: 'json'
257+
output: 'trivy-results.json'
258+
259+
- name: Upload Trivy results
260+
if: always()
261+
uses: actions/upload-artifact@v4
262+
with:
263+
name: trivy-results
264+
path: trivy-results.json
265+
266+
# Stage 10: DAST - Dynamic Application Security Testing
267+
dast-zap:
268+
name: DAST - OWASP ZAP
269+
runs-on: ubuntu-latest
270+
needs: docker-build
271+
permissions:
272+
contents: read
273+
packages: read
274+
275+
steps:
276+
- name: Checkout code
277+
uses: actions/checkout@v4
278+
279+
- name: Log in to GitHub Container Registry
280+
uses: docker/login-action@v3
281+
with:
282+
registry: ${{ env.REGISTRY }}
283+
username: ${{ github.actor }}
284+
password: ${{ secrets.GITHUB_TOKEN }}
285+
286+
- name: Start application container
287+
run: |
288+
docker run -d --name solar-system-app \
289+
-p 3000:3000 \
290+
-e MONGO_URI="${{ secrets.MONGO_URI }}" \
291+
-e MONGO_USERNAME="${{ secrets.MONGO_USERNAME }}" \
292+
-e MONGO_PASSWORD="${{ secrets.MONGO_PASSWORD }}" \
293+
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-${{ github.sha }}
294+
295+
# Wait for application to be ready
296+
sleep 10
297+
curl -f http://localhost:3000/ready || exit 1
298+
299+
- name: Run OWASP ZAP Baseline Scan
300+
uses: zaproxy/action-baseline@v0.12.0
301+
with:
302+
target: 'http://localhost:3000/'
303+
rules_file_name: '.zap/rules.tsv'
304+
cmd_options: '-a'
305+
allow_issue_writing: false
306+
307+
- name: Stop application container
308+
if: always()
309+
run: docker stop solar-system-app && docker rm solar-system-app
310+
311+
# Stage 11: Deploy to Azure VM
312+
deploy-azure:
313+
name: Deploy to Azure VM
314+
runs-on: ubuntu-latest
315+
needs: [container-scan, dast-zap]
316+
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master'
317+
permissions:
318+
contents: read
319+
packages: read
320+
environment:
321+
name: production
322+
url: http://${{ secrets.AZURE_VM_IP }}:3000
323+
324+
steps:
325+
- name: Deploy on Azure VM
326+
uses: appleboy/ssh-action@master
327+
with:
328+
host: ${{ secrets.AZURE_VM_IP }}
329+
username: ${{ secrets.AZURE_VM_USERNAME }}
330+
key: ${{ secrets.AZURE_VM_SSH_KEY }}
331+
script: |
332+
# Log in to GitHub Container Registry
333+
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
334+
335+
# Pull the latest image
336+
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-${{ github.sha }}
337+
338+
# Stop and remove old container if exists
339+
docker stop solar-system || true
340+
docker rm solar-system || true
341+
342+
# Run new container
343+
docker run -d --name solar-system \
344+
-p 3000:3000 \
345+
--restart unless-stopped \
346+
-e MONGO_URI="${{ secrets.MONGO_URI }}" \
347+
-e MONGO_USERNAME="${{ secrets.MONGO_USERNAME }}" \
348+
-e MONGO_PASSWORD="${{ secrets.MONGO_PASSWORD }}" \
349+
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-${{ github.sha }}
350+
351+
# Clean up old images (keep last 3)
352+
docker image prune -af --filter "until=72h"
353+
354+
# Verify deployment
355+
sleep 5
356+
curl -f http://localhost:3000/ready || exit 1
357+
358+
echo "Deployment successful!"
359+
docker ps | grep solar-system
360+
361+
- name: Health check
362+
run: |
363+
sleep 10
364+
curl -f http://${{ secrets.AZURE_VM_IP }}:3000/ready || exit 1
365+
echo "Application is healthy and running!"

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
node_modules
2+
solar-system.png
3+
.nyc_output
4+
.talismanrc
5+
coverage
6+
test-results.xml

Dockerfile

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
FROM node:18-alpine3.17
2+
3+
WORKDIR /usr/app
4+
5+
COPY package*.json /usr/app/
6+
7+
RUN npm install
8+
9+
COPY . .
10+
11+
ENV MONGO_URI=uriPlaceholder
12+
ENV MONGO_USERNAME=usernamePlaceholder
13+
ENV MONGO_PASSWORD=passwordPlaceholder
14+
15+
EXPOSE 3000
16+
17+
CMD [ "npm", "start" ]

0 commit comments

Comments
 (0)