From 9e78e4ab663627ad1d5cd7185044fed4ea770887 Mon Sep 17 00:00:00 2001 From: Kelly Thomas Reardon Date: Tue, 12 May 2026 22:35:49 -0500 Subject: [PATCH 1/9] adding security tool stack, pre-commit steps, and gitignore additions --- .gitea/workflows/security.yaml | 78 ++++++++++++++++++++++++++++++++++ .gitignore | 16 +++++++ .pre-commit-config.yaml | 29 +++++++++++++ .vscode/settings.json | 3 +- 4 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 .gitea/workflows/security.yaml create mode 100644 .pre-commit-config.yaml diff --git a/.gitea/workflows/security.yaml b/.gitea/workflows/security.yaml new file mode 100644 index 0000000..a8b8d23 --- /dev/null +++ b/.gitea/workflows/security.yaml @@ -0,0 +1,78 @@ +name: Security + +on: + pull_request: + push: + branches: + - main + - develop + +jobs: + security: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + ########################################################### + # GITLEAKS + ########################################################### + + - name: Gitleaks + run: | + docker run --rm \ + -v ${{ github.workspace }}:/repo \ + ghcr.io/gitleaks/gitleaks:latest \ + detect \ + --source /repo + + ########################################################### + # SEMGREP + ########################################################### + + - name: Semgrep + run: | + docker run --rm \ + -v ${{ github.workspace }}:/src \ + semgrep/semgrep \ + semgrep scan \ + --config auto \ + /src + + ########################################################### + # TRIVY FS + ########################################################### + + - name: Trivy Filesystem + run: | + docker run --rm \ + -v ${{ github.workspace }}:/workspace \ + aquasec/trivy:latest \ + fs \ + --scanners vuln,secret,misconfig \ + --severity HIGH,CRITICAL \ + --exit-code 1 \ + /workspace + + ########################################################### + # DOCKER IMAGE BUILD + ########################################################### + + - name: Build Image + run: | + docker build -t app:${{ github.sha }} . + + ########################################################### + # TRIVY IMAGE + ########################################################### + + - name: Trivy Image Scan + run: | + docker run --rm \ + -v /var/run/docker.sock:/var/run/docker.sock \ + aquasec/trivy:latest \ + image \ + --severity HIGH,CRITICAL \ + --exit-code 1 \ + app:${{ github.sha }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 9daf30a..e0aed67 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,19 @@ debug.ps1 .postmate/postmate-history.json .postmate/postmate-envs.json +# Secrets +.env +.env.* +*.pem +*.key +*.pfx +*.p12 +secrets/ +credentials/ +config.local.* + +# VS Code local settings +.vscode/settings.json + +# PowerShell +*.psd1.local \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..845a76d --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,29 @@ +repos: + - repo: https://github.com/gitleaks/gitleaks + rev: v8.24.2 + hooks: + - id: gitleaks + + - repo: local + hooks: + - id: semgrep + name: semgrep + entry: bash -c 'docker run --rm -v "$(realpath .)":/src:Z docker.io/semgrep/semgrep semgrep scan --config auto /src' + language: system + pass_filenames: false + + - repo: local + hooks: + - id: trivy + name: trivy filesystem scan + entry: bash -c 'docker run --rm -v "$(pwd)":/workspace docker.io/aquasec/trivy fs --scanners vuln,secret,misconfig --severity HIGH,CRITICAL /workspace' + language: system + pass_filenames: false + + - repo: local + hooks: + - id: psscriptanalyzer + name: powershell static analysis + entry: pwsh -NoProfile -Command "Import-Module PSScriptAnalyzer; Invoke-ScriptAnalyzer -Path . -Recurse -Severity Error" + language: system + pass_filenames: false \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index e106e53..5a11575 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,4 @@ { - "trivy.secretScanning": true + "trivy.secretScanning": true, + "semgrep.scan.onlyGitDirty": false } \ No newline at end of file From acce7f7424cc62a1ed0bf9b9ec1d380a6c6e4860 Mon Sep 17 00:00:00 2001 From: Kelly Thomas Reardon Date: Tue, 12 May 2026 22:38:29 -0500 Subject: [PATCH 2/9] updated security actions to include all branches --- .gitea/workflows/security.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitea/workflows/security.yaml b/.gitea/workflows/security.yaml index a8b8d23..ff13985 100644 --- a/.gitea/workflows/security.yaml +++ b/.gitea/workflows/security.yaml @@ -4,8 +4,7 @@ on: pull_request: push: branches: - - main - - develop + - "**" jobs: security: From d32cc1c1db4ad58e5cf9f1b8cf03626e2caa456a Mon Sep 17 00:00:00 2001 From: Kelly Thomas Reardon Date: Wed, 13 May 2026 07:47:03 -0500 Subject: [PATCH 3/9] updating security scan as required before build and issue creation for findings --- .gitea/workflows/docker-build.yaml | 39 ++--- .gitea/workflows/security.yaml | 253 ++++++++++++++++++++++++++++- 2 files changed, 263 insertions(+), 29 deletions(-) diff --git a/.gitea/workflows/docker-build.yaml b/.gitea/workflows/docker-build.yaml index 0bbd5cf..f197efb 100644 --- a/.gitea/workflows/docker-build.yaml +++ b/.gitea/workflows/docker-build.yaml @@ -1,31 +1,25 @@ name: Build and Push Docker Image on: - push: - branches: - - "**" + workflow_run: + workflows: + - Security + types: + - completed + +permissions: + contents: read jobs: build: + if: ${{ gitea.event.workflow_run.event == 'push' && gitea.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - - # - name: Run Trivy vulnerability scanner in repo mode - # uses: aquasecurity/trivy-action@v0.36.0 - # with: - # scan-type: 'fs' - # ignore-unfixed: true - # format: 'sarif' - # output: 'trivy-results.sarif' - # severity: 'CRITICAL' - - # - name: Upload Trivy scan results to GitHub Security tab - # uses: github/codeql-action/upload-sarif@v4 - # with: - # sarif_file: 'trivy-results.sarif' + with: + ref: ${{ gitea.event.workflow_run.head_sha }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -39,7 +33,7 @@ jobs: - name: Compute image tag id: tag run: | - BRANCH="${{ gitea.ref_name }}" + BRANCH="${{ gitea.event.workflow_run.head_branch }}" if [ "$BRANCH" = "main" ]; then TAG="latest" @@ -49,7 +43,8 @@ jobs: TAG="test" fi - echo "tag=$TAG" >> $GITHUB_OUTPUT + echo "tag=$TAG" >> "$GITEA_OUTPUT" + echo "branch=$BRANCH" >> "$GITEA_OUTPUT" - name: Build and push uses: docker/build-push-action@v6 @@ -66,7 +61,7 @@ jobs: -d "{ \"tags\": \"all\", \"title\": \"Gitea Build Succeeded\", - \"body\": \"Repo: ${{ gitea.repository }}\\nBranch: ${{ gitea.ref_name }}\\nImage tag built successfully\" + \"body\": \"Repo: ${{ gitea.repository }}\\nBranch: ${{ steps.tag.outputs.branch }}\\nImage tag built successfully\" }" \ ${{ secrets.APPRISE_URL }} @@ -78,6 +73,6 @@ jobs: -d "{ \"tags\": \"all\", \"title\": \"Gitea Build Failed\", - \"body\": \"Repo: ${{ gitea.repository }}\\nBranch: ${{ gitea.ref_name }}\\nCheck logs in Gitea\" + \"body\": \"Repo: ${{ gitea.repository }}\\nBranch: ${{ steps.tag.outputs.branch }}\\nCheck logs in Gitea\" }" \ - ${{ secrets.APPRISE_URL }} \ No newline at end of file + ${{ secrets.APPRISE_URL }} diff --git a/.gitea/workflows/security.yaml b/.gitea/workflows/security.yaml index ff13985..e016cc9 100644 --- a/.gitea/workflows/security.yaml +++ b/.gitea/workflows/security.yaml @@ -5,6 +5,11 @@ on: push: branches: - "**" + workflow_dispatch: + +permissions: + contents: read + issues: write jobs: security: @@ -14,17 +19,26 @@ jobs: - name: Checkout uses: actions/checkout@v4 + - name: Prepare security reports + run: | + mkdir -p security-results + ########################################################### # GITLEAKS ########################################################### - name: Gitleaks run: | + set +e docker run --rm \ - -v ${{ github.workspace }}:/repo \ + -v ${{ gitea.workspace }}:/repo \ ghcr.io/gitleaks/gitleaks:latest \ detect \ - --source /repo + --source /repo \ + --report-format json \ + --report-path /repo/security-results/gitleaks.json \ + --exit-code 1 + echo "$?" > security-results/gitleaks.exit ########################################################### # SEMGREP @@ -32,12 +46,17 @@ jobs: - name: Semgrep run: | + set +e docker run --rm \ - -v ${{ github.workspace }}:/src \ + -v ${{ gitea.workspace }}:/src \ semgrep/semgrep \ semgrep scan \ --config auto \ + --json \ + --output /src/security-results/semgrep.json \ + --error \ /src + echo "$?" > security-results/semgrep.exit ########################################################### # TRIVY FS @@ -45,22 +64,28 @@ jobs: - name: Trivy Filesystem run: | + set +e docker run --rm \ - -v ${{ github.workspace }}:/workspace \ + -v ${{ gitea.workspace }}:/workspace \ aquasec/trivy:latest \ fs \ --scanners vuln,secret,misconfig \ --severity HIGH,CRITICAL \ + --format json \ + --output /workspace/security-results/trivy-fs.json \ --exit-code 1 \ /workspace + echo "$?" > security-results/trivy-fs.exit ########################################################### # DOCKER IMAGE BUILD ########################################################### - - name: Build Image + - name: Build Image for scan run: | - docker build -t app:${{ github.sha }} . + set +e + docker build -t app:${{ gitea.sha }} . + echo "$?" > security-results/docker-build.exit ########################################################### # TRIVY IMAGE @@ -68,10 +93,224 @@ jobs: - name: Trivy Image Scan run: | + set +e + if [ "$(cat security-results/docker-build.exit)" != "0" ]; then + echo "Skipping image scan because the image build failed." + echo "1" > security-results/trivy-image.exit + exit 0 + fi + docker run --rm \ -v /var/run/docker.sock:/var/run/docker.sock \ + -v ${{ gitea.workspace }}:/workspace \ aquasec/trivy:latest \ image \ --severity HIGH,CRITICAL \ + --format json \ + --output /workspace/security-results/trivy-image.json \ --exit-code 1 \ - app:${{ github.sha }} \ No newline at end of file + app:${{ gitea.sha }} + echo "$?" > security-results/trivy-image.exit + + - name: Create Gitea issues for security findings + if: always() + env: + GITEA_API_URL: ${{ gitea.api_url }} + GITEA_REPOSITORY: ${{ gitea.repository }} + GITEA_REF_NAME: ${{ gitea.ref_name }} + GITEA_RUN_ID: ${{ gitea.run_id }} + GITEA_SERVER_URL: ${{ gitea.server_url }} + GITEA_SHA: ${{ gitea.sha }} + GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} + run: | + python3 <<'PY' + import json + import os + import urllib.parse + import urllib.request + + token = os.environ.get("GITEA_TOKEN", "") + api_url = os.environ["GITEA_API_URL"].rstrip("/") + repo = os.environ["GITEA_REPOSITORY"] + ref_name = os.environ.get("GITEA_REF_NAME", "") + sha = os.environ.get("GITEA_SHA", "") + run_id = os.environ.get("GITEA_RUN_ID", "") + server_url = os.environ.get("GITEA_SERVER_URL", "").rstrip("/") + + if not token: + print("GITEA_TOKEN is unavailable; skipping issue creation.") + raise SystemExit(0) + + owner, name = repo.split("/", 1) + issues_url = f"{api_url}/repos/{urllib.parse.quote(owner)}/{urllib.parse.quote(name)}/issues" + headers = { + "Authorization": f"token {token}", + "Content-Type": "application/json", + } + + def load_json(path, fallback): + try: + with open(path, "r", encoding="utf-8") as handle: + return json.load(handle) + except FileNotFoundError: + return fallback + except json.JSONDecodeError as exc: + print(f"Could not parse {path}: {exc}") + return fallback + + def trim(value, limit): + value = str(value or "unknown") + return value if len(value) <= limit else value[: limit - 3] + "..." + + def find_existing(title): + query = urllib.parse.urlencode({"state": "open", "type": "issues", "q": title}) + request = urllib.request.Request(f"{issues_url}?{query}", headers=headers) + with urllib.request.urlopen(request, timeout=30) as response: + issues = json.load(response) + return any(issue.get("title") == title for issue in issues) + + def create_issue(title, body): + if find_existing(title): + print(f"Open issue already exists: {title}") + return + payload = json.dumps({"title": title, "body": body}).encode("utf-8") + request = urllib.request.Request(issues_url, data=payload, headers=headers, method="POST") + with urllib.request.urlopen(request, timeout=30) as response: + created = json.load(response) + print(f"Created issue #{created.get('number')}: {title}") + + def body(scanner, summary, details): + run_url = f"{server_url}/{repo}/actions/runs/{run_id}" if server_url and run_id else "Unavailable" + lines = [ + f"Security scanner: `{scanner}`", + f"Summary: {summary}", + f"Repository: `{repo}`", + f"Branch/ref: `{ref_name}`", + f"Commit: `{sha}`", + f"Action run: {run_url}", + "", + "Details:", + ] + lines.extend(f"- {key}: {value}" for key, value in details if value not in (None, "")) + return "\n".join(lines) + + findings = [] + + for finding in load_json("security-results/gitleaks.json", []): + rule = finding.get("RuleID") or finding.get("Description") or "secret" + file_path = finding.get("File", "unknown") + line = finding.get("StartLine", "unknown") + title = trim(f"[security][gitleaks] {rule} in {file_path}:{line}", 255) + findings.append(( + title, + body("gitleaks", f"{rule} in `{file_path}:{line}`", [ + ("Rule", rule), + ("Description", finding.get("Description")), + ("File", file_path), + ("Line", line), + ("Fingerprint", finding.get("Fingerprint")), + ]), + )) + + semgrep = load_json("security-results/semgrep.json", {}) + for result in semgrep.get("results", []): + extra = result.get("extra", {}) + start = result.get("start", {}) + check_id = result.get("check_id", "semgrep finding") + file_path = result.get("path", "unknown") + line = start.get("line", "unknown") + title = trim(f"[security][semgrep] {check_id} in {file_path}:{line}", 255) + findings.append(( + title, + body("semgrep", extra.get("message", check_id), [ + ("Check", check_id), + ("Severity", extra.get("severity")), + ("File", file_path), + ("Line", line), + ("Message", extra.get("message")), + ]), + )) + + def add_trivy_findings(path, scanner): + report = load_json(path, {}) + for result in report.get("Results", []): + target = result.get("Target", "unknown") + for vuln in result.get("Vulnerabilities", []) or []: + vuln_id = vuln.get("VulnerabilityID", "vulnerability") + package = vuln.get("PkgName", "unknown package") + severity = vuln.get("Severity", "UNKNOWN") + title = trim(f"[security][{scanner}] {severity} {vuln_id} in {package} on {target}", 255) + findings.append(( + title, + body(scanner, vuln.get("Title") or vuln_id, [ + ("Type", "Vulnerability"), + ("Severity", severity), + ("Target", target), + ("Package", package), + ("Installed version", vuln.get("InstalledVersion")), + ("Fixed version", vuln.get("FixedVersion")), + ("Vulnerability ID", vuln_id), + ("Primary URL", vuln.get("PrimaryURL")), + ]), + )) + for misconfig in result.get("Misconfigurations", []) or []: + misconfig_id = misconfig.get("ID", "misconfiguration") + severity = misconfig.get("Severity", "UNKNOWN") + title = trim(f"[security][{scanner}] {severity} {misconfig_id} in {target}", 255) + findings.append(( + title, + body(scanner, misconfig.get("Title") or misconfig_id, [ + ("Type", "Misconfiguration"), + ("Severity", severity), + ("Target", target), + ("ID", misconfig_id), + ("Message", misconfig.get("Message")), + ("Resolution", misconfig.get("Resolution")), + ]), + )) + for secret in result.get("Secrets", []) or []: + rule = secret.get("RuleID", "secret") + line = secret.get("StartLine", "unknown") + title = trim(f"[security][{scanner}] {rule} in {target}:{line}", 255) + findings.append(( + title, + body(scanner, f"{rule} in `{target}:{line}`", [ + ("Type", "Secret"), + ("Rule", rule), + ("Category", secret.get("Category")), + ("Severity", secret.get("Severity")), + ("Target", target), + ("Line", line), + ]), + )) + + add_trivy_findings("security-results/trivy-fs.json", "trivy-fs") + add_trivy_findings("security-results/trivy-image.json", "trivy-image") + + if not findings: + print("No security findings found in scanner reports.") + raise SystemExit(0) + + for title, issue_body in findings: + try: + create_issue(title, issue_body) + except Exception as exc: + print(f"Failed to create issue for {title}: {exc}") + PY + + - name: Stop on failed security scans + if: always() + run: | + failed=0 + for result in security-results/*.exit; do + name="$(basename "$result" .exit)" + code="$(cat "$result")" + if [ "$code" != "0" ]; then + echo "$name failed with exit code $code" + failed=1 + fi + done + + if [ "$failed" != "0" ]; then + exit 1 + fi From 4d711859ace27e2930cf85f4e4a13ffa2f737697 Mon Sep 17 00:00:00 2001 From: Kelly Thomas Reardon Date: Wed, 13 May 2026 08:00:08 -0500 Subject: [PATCH 4/9] fixing security scan mount points --- .gitea/workflows/security.yaml | 50 ++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/.gitea/workflows/security.yaml b/.gitea/workflows/security.yaml index e016cc9..9101f64 100644 --- a/.gitea/workflows/security.yaml +++ b/.gitea/workflows/security.yaml @@ -30,15 +30,19 @@ jobs: - name: Gitleaks run: | set +e - docker run --rm \ - -v ${{ gitea.workspace }}:/repo \ + cid="$(docker create \ ghcr.io/gitleaks/gitleaks:latest \ detect \ --source /repo \ --report-format json \ --report-path /repo/security-results/gitleaks.json \ - --exit-code 1 - echo "$?" > security-results/gitleaks.exit + --exit-code 1)" + docker cp . "$cid:/repo" + docker start -a "$cid" + status="$?" + docker cp "$cid:/repo/security-results/gitleaks.json" security-results/gitleaks.json || true + docker rm -f "$cid" >/dev/null 2>&1 || true + echo "$status" > security-results/gitleaks.exit ########################################################### # SEMGREP @@ -47,16 +51,21 @@ jobs: - name: Semgrep run: | set +e - docker run --rm \ - -v ${{ gitea.workspace }}:/src \ + cid="$(docker create \ + --workdir /src \ semgrep/semgrep \ semgrep scan \ --config auto \ --json \ --output /src/security-results/semgrep.json \ --error \ - /src - echo "$?" > security-results/semgrep.exit + /src)" + docker cp . "$cid:/src" + docker start -a "$cid" + status="$?" + docker cp "$cid:/src/security-results/semgrep.json" security-results/semgrep.json || true + docker rm -f "$cid" >/dev/null 2>&1 || true + echo "$status" > security-results/semgrep.exit ########################################################### # TRIVY FS @@ -65,8 +74,7 @@ jobs: - name: Trivy Filesystem run: | set +e - docker run --rm \ - -v ${{ gitea.workspace }}:/workspace \ + cid="$(docker create \ aquasec/trivy:latest \ fs \ --scanners vuln,secret,misconfig \ @@ -74,8 +82,13 @@ jobs: --format json \ --output /workspace/security-results/trivy-fs.json \ --exit-code 1 \ - /workspace - echo "$?" > security-results/trivy-fs.exit + /workspace)" + docker cp . "$cid:/workspace" + docker start -a "$cid" + status="$?" + docker cp "$cid:/workspace/security-results/trivy-fs.json" security-results/trivy-fs.json || true + docker rm -f "$cid" >/dev/null 2>&1 || true + echo "$status" > security-results/trivy-fs.exit ########################################################### # DOCKER IMAGE BUILD @@ -100,17 +113,20 @@ jobs: exit 0 fi - docker run --rm \ + cid="$(docker create \ -v /var/run/docker.sock:/var/run/docker.sock \ - -v ${{ gitea.workspace }}:/workspace \ aquasec/trivy:latest \ image \ --severity HIGH,CRITICAL \ --format json \ - --output /workspace/security-results/trivy-image.json \ + --output /tmp/trivy-image.json \ --exit-code 1 \ - app:${{ gitea.sha }} - echo "$?" > security-results/trivy-image.exit + app:${{ gitea.sha }})" + docker start -a "$cid" + status="$?" + docker cp "$cid:/tmp/trivy-image.json" security-results/trivy-image.json || true + docker rm -f "$cid" >/dev/null 2>&1 || true + echo "$status" > security-results/trivy-image.exit - name: Create Gitea issues for security findings if: always() From 9bd06a85ab6301fe9f0c7652cb6a85914a48d9e1 Mon Sep 17 00:00:00 2001 From: Kelly Thomas Reardon Date: Wed, 13 May 2026 10:41:48 -0500 Subject: [PATCH 5/9] updating dockerfile to use an ubuntu 24.04 based image to resolve security vulnerabilities --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index bed4acb..be7114c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/powershell +FROM mcr.microsoft.com/powershell:7.5-ubuntu-24.04 USER 1000:1000 From ef66b632b5558ba2d88318bcc5d6f33a8f0fd211 Mon Sep 17 00:00:00 2001 From: Kelly Thomas Reardon Date: Wed, 13 May 2026 10:47:15 -0500 Subject: [PATCH 6/9] added forced OS updates within the docker container --- Dockerfile | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Dockerfile b/Dockerfile index be7114c..1807f3f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,12 @@ FROM mcr.microsoft.com/powershell:7.5-ubuntu-24.04 +USER root + +RUN apt-get update \ + && apt-get dist-upgrade -y \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + USER 1000:1000 WORKDIR /data From b92dd4ee304798cf477a7368645509b5445ccc9f Mon Sep 17 00:00:00 2001 From: Kelly Thomas Reardon Date: Wed, 13 May 2026 10:53:02 -0500 Subject: [PATCH 7/9] resolving DS-0017 finding --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 1807f3f..e737354 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,6 +3,7 @@ FROM mcr.microsoft.com/powershell:7.5-ubuntu-24.04 USER root RUN apt-get update \ + && apt-get install -y --no-install-recommends ca-certificates \ && apt-get dist-upgrade -y \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* @@ -12,4 +13,4 @@ USER 1000:1000 WORKDIR /data COPY ["Start-DoneTickNotifier.ps1", "/data/"] -ENTRYPOINT ["pwsh", "-Command", "/data/Start-DoneTickNotifier.ps1"] \ No newline at end of file +ENTRYPOINT ["pwsh", "-Command", "/data/Start-DoneTickNotifier.ps1"] From 8046e78bb6acc7915e64c243bd4b6019bfe634a9 Mon Sep 17 00:00:00 2001 From: Kelly Thomas Reardon Date: Wed, 13 May 2026 11:00:41 -0500 Subject: [PATCH 8/9] fixing build pipeline to do security scan and docker build together --- .gitea/workflows/docker-build.yaml | 11 +---- .gitea/workflows/security.yaml | 77 ++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 9 deletions(-) diff --git a/.gitea/workflows/docker-build.yaml b/.gitea/workflows/docker-build.yaml index f197efb..1fb56a2 100644 --- a/.gitea/workflows/docker-build.yaml +++ b/.gitea/workflows/docker-build.yaml @@ -1,25 +1,18 @@ name: Build and Push Docker Image on: - workflow_run: - workflows: - - Security - types: - - completed + workflow_dispatch: permissions: contents: read jobs: build: - if: ${{ gitea.event.workflow_run.event == 'push' && gitea.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - with: - ref: ${{ gitea.event.workflow_run.head_sha }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -33,7 +26,7 @@ jobs: - name: Compute image tag id: tag run: | - BRANCH="${{ gitea.event.workflow_run.head_branch }}" + BRANCH="${{ gitea.ref_name }}" if [ "$BRANCH" = "main" ]; then TAG="latest" diff --git a/.gitea/workflows/security.yaml b/.gitea/workflows/security.yaml index 9101f64..ede07d5 100644 --- a/.gitea/workflows/security.yaml +++ b/.gitea/workflows/security.yaml @@ -330,3 +330,80 @@ jobs: if [ "$failed" != "0" ]; then exit 1 fi + + - name: Notify Apprise (failure) + if: failure() + run: | + curl -X POST \ + -H "Content-Type: application/json" \ + -d "{ + \"tags\": \"all\", + \"title\": \"Gitea Security Scan Failed\", + \"body\": \"Repo: ${{ gitea.repository }}\\nBranch: ${{ gitea.ref_name }}\\nSecurity scan failed; check logs and generated issues in Gitea\" + }" \ + ${{ secrets.APPRISE_URL }} + + build: + needs: security + if: ${{ gitea.event_name == 'push' }} + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Compute image tag + id: tag + run: | + BRANCH="${{ gitea.ref_name }}" + + if [ "$BRANCH" = "main" ]; then + TAG="latest" + elif [[ "$BRANCH" == v* ]]; then + TAG="$BRANCH" + else + TAG="test" + fi + + echo "tag=$TAG" >> "$GITEA_OUTPUT" + echo "branch=$BRANCH" >> "$GITEA_OUTPUT" + + - name: Build and push + uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: blinkfink182/donetick-notifier:${{ steps.tag.outputs.tag }} + + - name: Notify Apprise (success) + if: success() + run: | + curl -X POST \ + -H "Content-Type: application/json" \ + -d "{ + \"tags\": \"all\", + \"title\": \"Gitea Build Succeeded\", + \"body\": \"Repo: ${{ gitea.repository }}\\nBranch: ${{ steps.tag.outputs.branch }}\\nImage tag built successfully\" + }" \ + ${{ secrets.APPRISE_URL }} + + - name: Notify Apprise (failure) + if: failure() + run: | + curl -X POST \ + -H "Content-Type: application/json" \ + -d "{ + \"tags\": \"all\", + \"title\": \"Gitea Build Failed\", + \"body\": \"Repo: ${{ gitea.repository }}\\nBranch: ${{ steps.tag.outputs.branch }}\\nCheck logs in Gitea\" + }" \ + ${{ secrets.APPRISE_URL }} From 1f800317159205c13410706d55a6589868fe6c55 Mon Sep 17 00:00:00 2001 From: Kelly Thomas Reardon Date: Wed, 13 May 2026 11:11:47 -0500 Subject: [PATCH 9/9] optimized pipeline to only build the docker image once --- .gitea/workflows/security.yaml | 75 +++++++++++++++------------------- 1 file changed, 33 insertions(+), 42 deletions(-) diff --git a/.gitea/workflows/security.yaml b/.gitea/workflows/security.yaml index ede07d5..1ce07ed 100644 --- a/.gitea/workflows/security.yaml +++ b/.gitea/workflows/security.yaml @@ -23,6 +23,21 @@ jobs: run: | mkdir -p security-results + - name: Compute image tag + id: tag + run: | + BRANCH="${{ gitea.ref_name }}" + + if [ "$BRANCH" = "main" ]; then + TAG="latest" + elif [[ "$BRANCH" == v* ]]; then + TAG="$BRANCH" + else + TAG="test" + fi + + echo "tag=$TAG" >> "$GITEA_OUTPUT" + ########################################################### # GITLEAKS ########################################################### @@ -97,7 +112,10 @@ jobs: - name: Build Image for scan run: | set +e - docker build -t app:${{ gitea.sha }} . + docker build \ + -t app:${{ gitea.sha }} \ + -t blinkfink182/donetick-notifier:${{ steps.tag.outputs.tag }} \ + . echo "$?" > security-results/docker-build.exit ########################################################### @@ -343,67 +361,40 @@ jobs: }" \ ${{ secrets.APPRISE_URL }} - build: - needs: security - if: ${{ gitea.event_name == 'push' }} - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Login to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Compute image tag - id: tag + id: docker-login + if: ${{ gitea.event_name == 'push' }} run: | - BRANCH="${{ gitea.ref_name }}" + echo "${{ secrets.DOCKER_PASSWORD }}" | docker login \ + --username "${{ secrets.DOCKER_USERNAME }}" \ + --password-stdin - if [ "$BRANCH" = "main" ]; then - TAG="latest" - elif [[ "$BRANCH" == v* ]]; then - TAG="$BRANCH" - else - TAG="test" - fi - - echo "tag=$TAG" >> "$GITEA_OUTPUT" - echo "branch=$BRANCH" >> "$GITEA_OUTPUT" - - - name: Build and push - uses: docker/build-push-action@v6 - with: - context: . - push: true - tags: blinkfink182/donetick-notifier:${{ steps.tag.outputs.tag }} + - name: Push scanned image + id: push + if: ${{ gitea.event_name == 'push' }} + run: | + docker push blinkfink182/donetick-notifier:${{ steps.tag.outputs.tag }} - name: Notify Apprise (success) - if: success() + if: ${{ success() && gitea.event_name == 'push' }} run: | curl -X POST \ -H "Content-Type: application/json" \ -d "{ \"tags\": \"all\", \"title\": \"Gitea Build Succeeded\", - \"body\": \"Repo: ${{ gitea.repository }}\\nBranch: ${{ steps.tag.outputs.branch }}\\nImage tag built successfully\" + \"body\": \"Repo: ${{ gitea.repository }}\\nBranch: ${{ gitea.ref_name }}\\nImage tag ${{ steps.tag.outputs.tag }} pushed successfully\" }" \ ${{ secrets.APPRISE_URL }} - name: Notify Apprise (failure) - if: failure() + if: ${{ failure() && (steps.docker-login.outcome == 'failure' || steps.push.outcome == 'failure') }} run: | curl -X POST \ -H "Content-Type: application/json" \ -d "{ \"tags\": \"all\", \"title\": \"Gitea Build Failed\", - \"body\": \"Repo: ${{ gitea.repository }}\\nBranch: ${{ steps.tag.outputs.branch }}\\nCheck logs in Gitea\" + \"body\": \"Repo: ${{ gitea.repository }}\\nBranch: ${{ gitea.ref_name }}\\nCheck logs in Gitea\" }" \ ${{ secrets.APPRISE_URL }}