Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
c87df22
Add project README, runbook, troubleshooting and screenshots
rayenmabrouk Sep 23, 2026
ff7a1f7
Merge branch 'feature/docs' (README, runbook, troubleshooting, screen…
rayenmabrouk Sep 24, 2026
e083f50
Harden Terraform: tags, variables, SSH opt-in, ECR lifecycle, backups…
rayenmabrouk Sep 24, 2026
483e6bb
Add daily SQLite backup/restore to S3; prune old images on deploy
rayenmabrouk Sep 24, 2026
b642b9b
Harden CI/CD supply chain, add Dockerfile lint, smoke test and secret…
rayenmabrouk Sep 24, 2026
6ac4642
Rewrite README for reviewers; update runbook and cost for new resources
rayenmabrouk Sep 24, 2026
8d02c2e
CI smoke test: use a valid dpaste lexer (_text)
rayenmabrouk Sep 24, 2026
d2ec224
README: document unfixed base-image findings reported by ECR scanning
rayenmabrouk Sep 24, 2026
b5286f5
Merge pull request #8 from rayenmabrouk/feature/portfolio-hardening
rayenmabrouk Sep 24, 2026
484f62c
Bootstrap the backup bucket outside Terraform (AWS Academy SCP)
rayenmabrouk Sep 24, 2026
69d0922
Merge pull request #9 from rayenmabrouk/fix/backup-bucket-scp
rayenmabrouk Sep 24, 2026
80ca0d8
Docs: record what was verified in AWS on 2026-09-24
rayenmabrouk Sep 24, 2026
f8fe1a0
Merge pull request #10 from rayenmabrouk/docs/verified-2026-09-24
rayenmabrouk Sep 24, 2026
28ae075
docs: remove 'How this was built' section from README
rayenmabrouk Sep 25, 2026
f573e78
Merge pull request #11 from rayenmabrouk/docs/readme-cleanup
rayenmabrouk Sep 25, 2026
4ef25c8
chore: refresh repository metadata
rayenmabrouk Sep 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .deploystack/docker-run.txt

This file was deleted.

6 changes: 4 additions & 2 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ venv
Dockerfile*
docker-compose*
.dockerignore
.travis.yml
.gitattributes
docs
terraform
monitoring
scripts
*.md
!setup.cfg
!README.md
!README.md.hadolint.yaml
.gitleaksignore
.trivyignore
ruff.toml
27 changes: 0 additions & 27 deletions .github/ISSUE_TEMPLATE/bug_report.md

This file was deleted.

12 changes: 0 additions & 12 deletions .github/ISSUE_TEMPLATE/feature.md

This file was deleted.

8 changes: 0 additions & 8 deletions .github/ISSUE_TEMPLATE/task.md

This file was deleted.

36 changes: 28 additions & 8 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,31 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates

# Weekly dependency update PRs. Every PR runs the full CI (tests, lint, image
# build + smoke test + Trivy) before it can be merged.
version: 2
updates:
- package-ecosystem: "pip" # See documentation for possible values
directory: "/" # Location of package manifests
# Keeps the SHA-pinned actions in .github/workflows current
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
groups:
github-actions:
patterns: ["*"]

# Base images in Dockerfile.hardened
- package-ecosystem: docker
directory: /
schedule:
interval: weekly

# AWS / random provider versions (terraform/.terraform.lock.hcl)
- package-ecosystem: terraform
directory: /terraform
schedule:
interval: weekly

# Python dependencies of the inherited dpaste application
- package-ecosystem: pip
directory: /
schedule:
interval: "weekly"
interval: weekly
open-pull-requests-limit: 5
78 changes: 46 additions & 32 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
# CloudPulse - CD pipeline
# Build -> Trivy gate -> push to ECR -> deploy to EC2 via SSM Run Command
# -> HTTPS smoke test. Runs on merges to master that change the app,
# the image or the deploy script; can also be started manually.
# the image or the on-instance scripts; can also be started manually.
# AWS credentials are only configured after the image has passed the scan,
# so the build and third-party scanner never run with cloud access.
# ============================================================
name: CD

Expand All @@ -18,6 +20,7 @@ on:
- "package.json"
- "package-lock.json"
- "scripts/deploy.sh"
- "scripts/backup.sh"
- ".github/workflows/cd.yml"
workflow_dispatch:

Expand All @@ -38,75 +41,82 @@ jobs:
build-scan-push:
name: Build, scan, push
runs-on: ubuntu-latest
timeout-minutes: 20
outputs:
image_tag: ${{ steps.meta.outputs.tag }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0

- name: Image tag = short commit SHA
id: meta
run: echo "tag=${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT"

# AWS Academy session credentials (OIDC is blocked in the Learner Lab).
# They expire with the lab session; refreshed by scripts/refresh-github-aws-secrets.ps1
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-session-token: ${{ secrets.AWS_SESSION_TOKEN }}
aws-region: ${{ env.AWS_REGION }}

- name: Log in to Amazon ECR
id: ecr
uses: aws-actions/amazon-ecr-login@v2

- uses: docker/setup-buildx-action@v3
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0

# provenance/sbom off: an attestation turns the push into an image index,
# which ECR basic scanning cannot scan
- name: Build image
uses: docker/build-push-action@v6
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2
with:
context: .
file: Dockerfile.hardened
load: true
push: false
provenance: false
sbom: false
tags: ${{ steps.ecr.outputs.registry }}/${{ env.ECR_REPOSITORY }}:${{ steps.meta.outputs.tag }}
tags: ${{ env.ECR_REPOSITORY }}:${{ steps.meta.outputs.tag }}

# Same policy as CI: fail on fixable CRITICAL/HIGH. Nothing is pushed if this fails.
- name: Trivy scan (release gate)
uses: aquasecurity/trivy-action@master
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
with:
image-ref: ${{ steps.ecr.outputs.registry }}/${{ env.ECR_REPOSITORY }}:${{ steps.meta.outputs.tag }}
image-ref: ${{ env.ECR_REPOSITORY }}:${{ steps.meta.outputs.tag }}
format: table
exit-code: "1"
severity: CRITICAL,HIGH
ignore-unfixed: true
trivyignores: .trivyignore

# AWS Academy session credentials (OIDC is blocked in the Learner Lab).
# They expire with the lab session; refreshed by scripts/refresh-github-aws-secrets.ps1
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4.3.1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-session-token: ${{ secrets.AWS_SESSION_TOKEN }}
aws-region: ${{ env.AWS_REGION }}

- name: Log in to Amazon ECR
id: ecr
uses: aws-actions/amazon-ecr-login@03f1aad4c6c7ffd436567f42f9384779290529bd # v2.1.7

# ECR tags are immutable: a re-run for the same commit must not fail on push
- name: Push image to ECR
env:
REGISTRY: ${{ steps.ecr.outputs.registry }}
TAG: ${{ steps.meta.outputs.tag }}
run: |
TAG="${{ steps.meta.outputs.tag }}"
if aws ecr describe-images --repository-name "$ECR_REPOSITORY" --image-ids imageTag="$TAG" >/dev/null 2>&1; then
echo "Tag $TAG already exists in ECR (immutable) - skipping push"
else
docker push "${{ steps.ecr.outputs.registry }}/${{ env.ECR_REPOSITORY }}:$TAG"
docker tag "$ECR_REPOSITORY:$TAG" "$REGISTRY/$ECR_REPOSITORY:$TAG"
docker push "$REGISTRY/$ECR_REPOSITORY:$TAG"
fi

deploy:
name: Deploy to EC2 via SSM
needs: build-scan-push
runs-on: ubuntu-latest
timeout-minutes: 15
environment:
name: production
url: ${{ steps.deploy.outputs.app_url }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0

- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4.3.1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
Expand All @@ -129,13 +139,17 @@ jobs:
--query "Reservations[0].Instances[0].PublicIpAddress" --output text)
echo "Deploying tag ${IMAGE_TAG} to ${INSTANCE_ID} (${PUBLIC_IP})"

# Ship this commit's deploy.sh with the command, so the instance
# always runs the reviewed version from Git.
SCRIPT_B64=$(base64 -w0 scripts/deploy.sh)
jq -n --arg b64 "$SCRIPT_B64" --arg tag "$IMAGE_TAG" '{commands: [
# Ship this commit's deploy.sh and backup.sh with the command, so the
# instance always runs the reviewed versions from Git (gzip keeps the
# SSM parameter small).
DEPLOY_B64=$(gzip -9c scripts/deploy.sh | base64 -w0)
BACKUP_B64=$(gzip -9c scripts/backup.sh | base64 -w0)
jq -n --arg deploy "$DEPLOY_B64" --arg backup "$BACKUP_B64" --arg tag "$IMAGE_TAG" '{commands: [
"set -e",
"echo \($b64) | base64 -d > /opt/cloudpulse/deploy.sh",
"chmod 0755 /opt/cloudpulse/deploy.sh",
"mkdir -p /opt/cloudpulse",
"echo \($deploy) | base64 -d | gunzip > /opt/cloudpulse/deploy.sh",
"echo \($backup) | base64 -d | gunzip > /opt/cloudpulse/backup.sh",
"chmod 0755 /opt/cloudpulse/deploy.sh /opt/cloudpulse/backup.sh",
"/opt/cloudpulse/deploy.sh \($tag)"
]}' > ssm-params.json

Expand Down Expand Up @@ -174,4 +188,4 @@ jobs:
APP_URL: ${{ steps.deploy.outputs.app_url }}
run: |
curl -sS --fail --retry 10 --retry-delay 5 --retry-all-errors \
-o /dev/null -w "GET / -> HTTP %{http_code} in %{time_total}s\n" "${APP_URL}/"
-o /dev/null -w "GET / -> HTTP %{http_code} in %{time_total}s\n" "${APP_URL}/"
Loading
Loading