From bc4d20d76046b4cd89c318223ef0009bc2a18261 Mon Sep 17 00:00:00 2001 From: OscarLlamas6 Date: Thu, 6 Nov 2025 14:07:32 -0600 Subject: [PATCH 01/13] add: snyk scan custom action --- .github/workflows/snyk-scan.yaml | 105 +++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 .github/workflows/snyk-scan.yaml diff --git a/.github/workflows/snyk-scan.yaml b/.github/workflows/snyk-scan.yaml new file mode 100644 index 0000000..8a7bc10 --- /dev/null +++ b/.github/workflows/snyk-scan.yaml @@ -0,0 +1,105 @@ +name: Snyk Security Scan + +on: + workflow_call: + inputs: + fail-on-issues: + required: false + type: boolean + default: true + description: "Whether to fail the build if vulnerabilities are found" + + severity-threshold: + required: false + type: string + default: "high" + description: "Minimum severity to fail on (low|medium|high|critical)" + + target-file: + required: false + type: string + description: "Specific file to scan (e.g., package.json, go.mod, pom.xml)" + + command: + required: false + type: string + default: "test" + description: "Snyk command to run (test|monitor|code)" + + args: + required: false + type: string + default: "" + description: "Additional arguments to pass to Snyk CLI" + + outputs: + vulnerabilities-found: + description: "Whether vulnerabilities were found (true/false)" + value: ${{ jobs.snyk-scan.outputs.vulnerabilities-found }} + +jobs: + snyk-scan: + runs-on: ubuntu-latest + outputs: + vulnerabilities-found: ${{ steps.snyk-scan.outputs.vulnerabilities-found }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Snyk + uses: snyk/actions/setup@master + + - name: Run Snyk Security Scan + id: snyk-scan + env: + SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} + run: | + set +e # Don't exit on error immediately + + # Build the Snyk command + SNYK_CMD="snyk ${{ inputs.command }}" + + # Add severity threshold + SNYK_CMD="$SNYK_CMD --severity-threshold=${{ inputs.severity-threshold }}" + + # Add target file if specified + if [ -n "${{ inputs.target-file }}" ]; then + SNYK_CMD="$SNYK_CMD --file=${{ inputs.target-file }}" + fi + + # Add additional args if specified + if [ -n "${{ inputs.args }}" ]; then + SNYK_CMD="$SNYK_CMD ${{ inputs.args }}" + fi + + echo "Running: $SNYK_CMD" + + # Run Snyk scan + eval $SNYK_CMD + SNYK_EXIT_CODE=$? + + # Set output based on exit code + if [ $SNYK_EXIT_CODE -eq 0 ]; then + echo "vulnerabilities-found=false" >> $GITHUB_OUTPUT + echo "✅ No vulnerabilities found" + else + echo "vulnerabilities-found=true" >> $GITHUB_OUTPUT + echo "⚠️ Vulnerabilities found" + fi + + # Handle fail-on-issues flag + if [ "${{ inputs.fail-on-issues }}" = "true" ]; then + echo "fail-on-issues is enabled, preserving exit code" + exit $SNYK_EXIT_CODE + else + echo "fail-on-issues is disabled, reporting only" + exit 0 + fi + + - name: Upload Snyk results + if: always() + uses: github/codeql-action/upload-sarif@v3 + continue-on-error: true + with: + sarif_file: snyk.sarif From 5142ffbed4c1ac72f6c2cc5d01ee72f408445708 Mon Sep 17 00:00:00 2001 From: OscarLlamas6 Date: Thu, 6 Nov 2025 14:12:55 -0600 Subject: [PATCH 02/13] chore: making linter happy --- .github/workflows/snyk-scan.yaml | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/snyk-scan.yaml b/.github/workflows/snyk-scan.yaml index 8a7bc10..0c1b1b4 100644 --- a/.github/workflows/snyk-scan.yaml +++ b/.github/workflows/snyk-scan.yaml @@ -1,3 +1,5 @@ +# .github/workflows/snyk-scan.yaml + name: Snyk Security Scan on: @@ -24,7 +26,7 @@ on: required: false type: string default: "test" - description: "Snyk command to run (test|monitor|code)" + description: "Snyk command to run (test|monitor|iac test|container test)" args: required: false @@ -76,22 +78,22 @@ jobs: echo "Running: $SNYK_CMD" # Run Snyk scan - eval $SNYK_CMD + eval "$SNYK_CMD" SNYK_EXIT_CODE=$? # Set output based on exit code - if [ $SNYK_EXIT_CODE -eq 0 ]; then - echo "vulnerabilities-found=false" >> $GITHUB_OUTPUT + if [ "$SNYK_EXIT_CODE" -eq 0 ]; then + echo "vulnerabilities-found=false" >> "$GITHUB_OUTPUT" echo "✅ No vulnerabilities found" else - echo "vulnerabilities-found=true" >> $GITHUB_OUTPUT + echo "vulnerabilities-found=true" >> "$GITHUB_OUTPUT" echo "⚠️ Vulnerabilities found" fi # Handle fail-on-issues flag if [ "${{ inputs.fail-on-issues }}" = "true" ]; then echo "fail-on-issues is enabled, preserving exit code" - exit $SNYK_EXIT_CODE + exit "$SNYK_EXIT_CODE" else echo "fail-on-issues is disabled, reporting only" exit 0 From c6db7747417bec835d26f0a19c39c6bc10888d58 Mon Sep 17 00:00:00 2001 From: OscarLlamas6 Date: Thu, 6 Nov 2025 14:32:12 -0600 Subject: [PATCH 03/13] add: temporal debug job --- .github/workflows/snyk-scan.yaml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.github/workflows/snyk-scan.yaml b/.github/workflows/snyk-scan.yaml index 0c1b1b4..63e5b54 100644 --- a/.github/workflows/snyk-scan.yaml +++ b/.github/workflows/snyk-scan.yaml @@ -40,6 +40,21 @@ on: value: ${{ jobs.snyk-scan.outputs.vulnerabilities-found }} jobs: + + snyk-debug: + name: Debug Snyk Token + runs-on: ubuntu-latest + steps: + - name: Setup Snyk + uses: snyk/actions/setup@master + + - name: Test Snyk Auth + env: + SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} + run: | + snyk auth "$SNYK_TOKEN" + snyk config get org + snyk-scan: runs-on: ubuntu-latest outputs: From 70d5580cc87b470a2d08eb5a0f0a91313d7d7e3a Mon Sep 17 00:00:00 2001 From: OscarLlamas6 Date: Thu, 6 Nov 2025 14:35:34 -0600 Subject: [PATCH 04/13] chore: new action input definition --- .github/workflows/snyk-scan.yaml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/workflows/snyk-scan.yaml b/.github/workflows/snyk-scan.yaml index 63e5b54..f04aaaa 100644 --- a/.github/workflows/snyk-scan.yaml +++ b/.github/workflows/snyk-scan.yaml @@ -33,6 +33,12 @@ on: type: string default: "" description: "Additional arguments to pass to Snyk CLI" + + upload-sarif: + required: false + type: boolean + default: false + description: "Whether to upload SARIF results to GitHub Security" outputs: vulnerabilities-found: @@ -102,7 +108,7 @@ jobs: echo "✅ No vulnerabilities found" else echo "vulnerabilities-found=true" >> "$GITHUB_OUTPUT" - echo "⚠️ Vulnerabilities found" + echo "⚠️ Vulnerabilities found or scan error occurred" fi # Handle fail-on-issues flag @@ -114,9 +120,8 @@ jobs: exit 0 fi - - name: Upload Snyk results - if: always() + - name: Upload Snyk SARIF results + if: always() && inputs.upload-sarif == true && hashFiles('snyk.sarif') != '' uses: github/codeql-action/upload-sarif@v3 - continue-on-error: true with: sarif_file: snyk.sarif From 8635aa767d43fd3671f4c4289fb8dbd43d28cc9b Mon Sep 17 00:00:00 2001 From: OscarLlamas6 Date: Thu, 6 Nov 2025 14:45:10 -0600 Subject: [PATCH 05/13] chore: add debug option --- .github/workflows/snyk-scan.yaml | 37 ++++++++++++++++---------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/.github/workflows/snyk-scan.yaml b/.github/workflows/snyk-scan.yaml index f04aaaa..84c98e2 100644 --- a/.github/workflows/snyk-scan.yaml +++ b/.github/workflows/snyk-scan.yaml @@ -1,5 +1,3 @@ -# .github/workflows/snyk-scan.yaml - name: Snyk Security Scan on: @@ -34,11 +32,16 @@ on: default: "" description: "Additional arguments to pass to Snyk CLI" - upload-sarif: + snyk-org: + required: false + type: string + description: "Snyk organization slug or ID" + + debug: required: false type: boolean default: false - description: "Whether to upload SARIF results to GitHub Security" + description: "Enable debug mode (-d flag) for verbose output" outputs: vulnerabilities-found: @@ -46,21 +49,6 @@ on: value: ${{ jobs.snyk-scan.outputs.vulnerabilities-found }} jobs: - - snyk-debug: - name: Debug Snyk Token - runs-on: ubuntu-latest - steps: - - name: Setup Snyk - uses: snyk/actions/setup@master - - - name: Test Snyk Auth - env: - SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} - run: | - snyk auth "$SNYK_TOKEN" - snyk config get org - snyk-scan: runs-on: ubuntu-latest outputs: @@ -83,6 +71,17 @@ jobs: # Build the Snyk command SNYK_CMD="snyk ${{ inputs.command }}" + # Add debug flag if enabled + if [ "${{ inputs.debug }}" = "true" ]; then + SNYK_CMD="$SNYK_CMD -d" + echo "🐛 Debug mode enabled" + fi + + # Add organization if specified + if [ -n "${{ inputs.snyk-org }}" ]; then + SNYK_CMD="$SNYK_CMD --org=${{ inputs.snyk-org }}" + fi + # Add severity threshold SNYK_CMD="$SNYK_CMD --severity-threshold=${{ inputs.severity-threshold }}" From 09f639cc1a814760db3762a57f6706ecec984abc Mon Sep 17 00:00:00 2001 From: OscarLlamas6 Date: Thu, 6 Nov 2025 14:47:05 -0600 Subject: [PATCH 06/13] chore: restore upload-sarif input --- .github/workflows/snyk-scan.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/snyk-scan.yaml b/.github/workflows/snyk-scan.yaml index 84c98e2..48b10f5 100644 --- a/.github/workflows/snyk-scan.yaml +++ b/.github/workflows/snyk-scan.yaml @@ -42,6 +42,12 @@ on: type: boolean default: false description: "Enable debug mode (-d flag) for verbose output" + + upload-sarif: + required: false + type: boolean + default: false + description: "Whether to upload SARIF results to GitHub Security" outputs: vulnerabilities-found: From 760270f0b55eb9dd505c0defe0113bb0c7e59167 Mon Sep 17 00:00:00 2001 From: OscarLlamas6 Date: Thu, 6 Nov 2025 15:13:36 -0600 Subject: [PATCH 07/13] chore: Snyk PAT as explicit input --- .github/workflows/snyk-scan.yaml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/.github/workflows/snyk-scan.yaml b/.github/workflows/snyk-scan.yaml index 48b10f5..c416454 100644 --- a/.github/workflows/snyk-scan.yaml +++ b/.github/workflows/snyk-scan.yaml @@ -49,6 +49,11 @@ on: default: false description: "Whether to upload SARIF results to GitHub Security" + secrets: + snyk-token: + required: true + description: "Snyk API token" + outputs: vulnerabilities-found: description: "Whether vulnerabilities were found (true/false)" @@ -70,10 +75,18 @@ jobs: - name: Run Snyk Security Scan id: snyk-scan env: - SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} + SNYK_TOKEN: ${{ secrets.snyk-token }} run: | set +e # Don't exit on error immediately + # Verify token is set + if [ -z "$SNYK_TOKEN" ]; then + echo "❌ ERROR: SNYK_TOKEN is not set" + exit 1 + fi + + echo "✅ SNYK_TOKEN is set (length: ${#SNYK_TOKEN})" + # Build the Snyk command SNYK_CMD="snyk ${{ inputs.command }}" From d59c2fecea00692ee4d9f920af878433af6f8809 Mon Sep 17 00:00:00 2001 From: OscarLlamas6 Date: Thu, 6 Nov 2025 15:18:48 -0600 Subject: [PATCH 08/13] chore: renaming PAT variable --- .github/workflows/snyk-scan.yaml | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/.github/workflows/snyk-scan.yaml b/.github/workflows/snyk-scan.yaml index c416454..58d3e9b 100644 --- a/.github/workflows/snyk-scan.yaml +++ b/.github/workflows/snyk-scan.yaml @@ -49,11 +49,6 @@ on: default: false description: "Whether to upload SARIF results to GitHub Security" - secrets: - snyk-token: - required: true - description: "Snyk API token" - outputs: vulnerabilities-found: description: "Whether vulnerabilities were found (true/false)" @@ -67,7 +62,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup Snyk uses: snyk/actions/setup@master @@ -75,17 +70,17 @@ jobs: - name: Run Snyk Security Scan id: snyk-scan env: - SNYK_TOKEN: ${{ secrets.snyk-token }} + SNYK_API_TOKEN: ${{ secrets.SNYK_API_TOKEN }} run: | set +e # Don't exit on error immediately # Verify token is set - if [ -z "$SNYK_TOKEN" ]; then - echo "❌ ERROR: SNYK_TOKEN is not set" + if [ -z "$SNYK_API_TOKEN" ]; then + echo "❌ ERROR: SNYK_API_TOKEN is not set" exit 1 fi - echo "✅ SNYK_TOKEN is set (length: ${#SNYK_TOKEN})" + echo "✅ SNYK_API_TOKEN is set (length: ${#SNYK_API_TOKEN})" # Build the Snyk command SNYK_CMD="snyk ${{ inputs.command }}" From 2086688bc9dc7359c3878d01fa699fee65d261c6 Mon Sep 17 00:00:00 2001 From: OscarLlamas6 Date: Thu, 6 Nov 2025 15:41:38 -0600 Subject: [PATCH 09/13] chore: proper env variable name value --- .github/workflows/snyk-scan.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/snyk-scan.yaml b/.github/workflows/snyk-scan.yaml index 58d3e9b..5bbd3ff 100644 --- a/.github/workflows/snyk-scan.yaml +++ b/.github/workflows/snyk-scan.yaml @@ -70,7 +70,7 @@ jobs: - name: Run Snyk Security Scan id: snyk-scan env: - SNYK_API_TOKEN: ${{ secrets.SNYK_API_TOKEN }} + SNYK_TOKEN: ${{ secrets.SNYK_API_TOKEN }} run: | set +e # Don't exit on error immediately From ceb5e436f1b9298e7de9a774cf64de104a107f66 Mon Sep 17 00:00:00 2001 From: OscarLlamas6 Date: Thu, 6 Nov 2025 15:43:53 -0600 Subject: [PATCH 10/13] chore: proper env variable name value --- .github/workflows/snyk-scan.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/snyk-scan.yaml b/.github/workflows/snyk-scan.yaml index 5bbd3ff..c4f7e23 100644 --- a/.github/workflows/snyk-scan.yaml +++ b/.github/workflows/snyk-scan.yaml @@ -70,17 +70,17 @@ jobs: - name: Run Snyk Security Scan id: snyk-scan env: - SNYK_TOKEN: ${{ secrets.SNYK_API_TOKEN }} + SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} run: | set +e # Don't exit on error immediately # Verify token is set - if [ -z "$SNYK_API_TOKEN" ]; then - echo "❌ ERROR: SNYK_API_TOKEN is not set" + if [ -z "$SNYK_TOKEN" ]; then + echo "❌ ERROR: SNYK_TOKEN is not set" exit 1 fi - echo "✅ SNYK_API_TOKEN is set (length: ${#SNYK_API_TOKEN})" + echo "✅ SNYK_TOKEN is set (length: ${#SNYK_TOKEN})" # Build the Snyk command SNYK_CMD="snyk ${{ inputs.command }}" From bdf74e38eaca268489997ada3db8e8416ad30c79 Mon Sep 17 00:00:00 2001 From: OscarLlamas6 Date: Sat, 15 Nov 2025 14:33:30 -0600 Subject: [PATCH 11/13] chore: uploadd sarif logic adjustments and docs --- .github/workflows/snyk-scan.yaml | 75 +++++++- docs/README.md | 53 +++++- docs/lint-workflows/README.md | 177 +++++++++++++++++++ docs/snyk-scan/README.md | 280 ++++++++++++++++++++++++++++++ docs/validate-kustomize/README.md | 235 +++++++++++++++++++++++++ 5 files changed, 812 insertions(+), 8 deletions(-) create mode 100644 docs/lint-workflows/README.md create mode 100644 docs/snyk-scan/README.md create mode 100644 docs/validate-kustomize/README.md diff --git a/.github/workflows/snyk-scan.yaml b/.github/workflows/snyk-scan.yaml index c4f7e23..ddb39f6 100644 --- a/.github/workflows/snyk-scan.yaml +++ b/.github/workflows/snyk-scan.yaml @@ -46,8 +46,20 @@ on: upload-sarif: required: false type: boolean - default: false + default: true description: "Whether to upload SARIF results to GitHub Security" + + sarif-file-output: + required: false + type: string + default: "snyk.sarif" + description: "Path for SARIF output file" + + json-file-output: + required: false + type: string + default: "snyk.json" + description: "Path for JSON output file" outputs: vulnerabilities-found: @@ -57,6 +69,10 @@ on: jobs: snyk-scan: runs-on: ubuntu-latest + permissions: + contents: read + security-events: write + actions: read outputs: vulnerabilities-found: ${{ steps.snyk-scan.outputs.vulnerabilities-found }} @@ -99,6 +115,14 @@ jobs: # Add severity threshold SNYK_CMD="$SNYK_CMD --severity-threshold=${{ inputs.severity-threshold }}" + # Add SARIF output if upload is enabled + if [ "${{ inputs.upload-sarif }}" = "true" ]; then + SNYK_CMD="$SNYK_CMD --sarif-file-output=${{ inputs.sarif-file-output }}" + fi + + # Add JSON output for detailed reporting + SNYK_CMD="$SNYK_CMD --json-file-output=${{ inputs.json-file-output }}" + # Add target file if specified if [ -n "${{ inputs.target-file }}" ]; then SNYK_CMD="$SNYK_CMD --file=${{ inputs.target-file }}" @@ -133,8 +157,51 @@ jobs: exit 0 fi - - name: Upload Snyk SARIF results - if: always() && inputs.upload-sarif == true && hashFiles('snyk.sarif') != '' + - name: Parse Snyk results and create summary + if: always() && hashFiles(inputs.json-file-output) != '' + run: | + echo "## 🔍 Snyk Security Scan Results" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + if [ -f "${{ inputs.json-file-output }}" ]; then + # Extract vulnerability counts by severity + CRITICAL=$(jq '[.vulnerabilities[]? | select(.severity=="critical")] | length' ${{ inputs.json-file-output }} 2>/dev/null || echo "0") + HIGH=$(jq '[.vulnerabilities[]? | select(.severity=="high")] | length' ${{ inputs.json-file-output }} 2>/dev/null || echo "0") + MEDIUM=$(jq '[.vulnerabilities[]? | select(.severity=="medium")] | length' ${{ inputs.json-file-output }} 2>/dev/null || echo "0") + LOW=$(jq '[.vulnerabilities[]? | select(.severity=="low")] | length' ${{ inputs.json-file-output }} 2>/dev/null || echo "0") + + echo "| Severity | Count |" >> $GITHUB_STEP_SUMMARY + echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY + echo "| 🔴 Critical | $CRITICAL |" >> $GITHUB_STEP_SUMMARY + echo "| 🟠 High | $HIGH |" >> $GITHUB_STEP_SUMMARY + echo "| 🟡 Medium | $MEDIUM |" >> $GITHUB_STEP_SUMMARY + echo "| 🟢 Low | $LOW |" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + TOTAL=$((CRITICAL + HIGH + MEDIUM + LOW)) + if [ "$TOTAL" -gt 0 ]; then + echo "⚠️ **Total vulnerabilities found: $TOTAL**" >> $GITHUB_STEP_SUMMARY + else + echo "✅ **No vulnerabilities found**" >> $GITHUB_STEP_SUMMARY + fi + else + echo "ℹ️ No JSON output file found" >> $GITHUB_STEP_SUMMARY + fi + + - name: Upload Snyk SARIF results to GitHub Security + if: always() && inputs.upload-sarif == true && hashFiles(inputs.sarif-file-output) != '' uses: github/codeql-action/upload-sarif@v3 with: - sarif_file: snyk.sarif + sarif_file: ${{ inputs.sarif-file-output }} + category: snyk-${{ inputs.command }} + + - name: Upload Snyk results as artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: snyk-results-${{ github.run_id }} + path: | + ${{ inputs.sarif-file-output }} + ${{ inputs.json-file-output }} + retention-days: 30 + if-no-files-found: ignore diff --git a/docs/README.md b/docs/README.md index cb8470c..2df297a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,9 +1,54 @@ # Reusable GitHub Actions This directory contains documentation for the reusable GitHub actions that are -available. +available for use across the organization. -## Actions +## Available Actions -- [Publish Docker Images to Artifact Registry](./publish-docker/) -- [Publish Kustomize Bundle to GHCR](./publish-kustomize-bundle/) +### Security & Compliance + +- [**Snyk Security Scan**](./snyk-scan/) - Comprehensive security vulnerability scanning for dependencies, IaC, containers, and code using Snyk + +### Publishing & Distribution + +- [**Publish Docker Images**](./publish-docker/) - Build and push Docker images to GitHub Container Registry +- [**Publish Kustomize Bundle**](./publish-kustomize-bundle/) - Build and push Kustomize bundles to GitHub Container Registry + +### Validation & Linting + +- [**Lint GitHub Actions Workflows**](./lint-workflows/) - Validate workflow files using actionlint to catch errors and best practice violations +- [**Validate Kustomize Configurations**](./validate-kustomize/) - Validate all Kustomize configurations by building them and checking for errors + +## Usage + +All workflows are designed to be used as reusable workflows. Reference them in your repository workflows using: + +```yaml +jobs: + job-name: + uses: datum-cloud/actions/.github/workflows/.yaml@v1 + with: + # inputs here + secrets: inherit +``` + +## Best Practices + +- **Use tagged versions**: Always reference workflows with a specific version tag (e.g., `@v1`) to prevent unexpected breaking changes +- **Inherit secrets**: Use `secrets: inherit` to pass repository secrets to reusable workflows +- **Set permissions**: Explicitly define required permissions in your workflow +- **Review documentation**: Check individual action documentation for specific requirements and examples + +## Contributing + +When adding new reusable workflows: + +1. Create the workflow file in `.github/workflows/` +2. Add comprehensive documentation in `docs//README.md` +3. Follow the established documentation structure +4. Update this README to include the new action +5. Create a release with appropriate version tags + +## Support + +For issues or questions about these actions, please open an issue in the repository. diff --git a/docs/lint-workflows/README.md b/docs/lint-workflows/README.md new file mode 100644 index 0000000..333d707 --- /dev/null +++ b/docs/lint-workflows/README.md @@ -0,0 +1,177 @@ +# Lint GitHub Actions Workflows + +The `.github/workflows/lint-workflows.yaml` reusable GitHub Action validates +GitHub Actions workflow files using **actionlint** to catch common errors and +best practice violations. + +## 🚀 Usage + +### Inputs + +This workflow does not require any inputs. + +### Automatic Triggers + +The workflow automatically runs when: +- Workflow files in `.github/workflows/*.yaml` are modified +- Called as a reusable workflow from another repository + +### Required Permissions + +```yaml +permissions: + contents: read +``` + +## Features + +- ✅ **Syntax validation**: Catches YAML syntax errors +- ✅ **Workflow validation**: Validates GitHub Actions workflow structure +- ✅ **Best practices**: Identifies common mistakes and anti-patterns +- ✅ **Shell script validation**: Checks shell scripts in `run` steps using shellcheck +- ✅ **Expression validation**: Validates GitHub Actions expressions +- ✅ **Colored output**: Easy-to-read error messages with color coding + +## Best Practices + +- Always use a tagged version when referencing the GitHub action workflow to prevent unexpected breaking changes. +- Run this workflow on every pull request to catch issues before merging. +- Consider adding this as a required status check for branch protection. + +## Workflow Examples + +### Basic Usage + +Create a workflow in your repository (e.g., `.github/workflows/lint.yaml`) +that calls this reusable action: + +```yaml +name: Lint Workflows + +on: + pull_request: + push: + paths: + - '.github/workflows/*.yaml' + +jobs: + lint: + uses: datum-cloud/actions/.github/workflows/lint-workflows.yaml@v1 +``` + +### With Branch Protection + +Combine with other checks as required status: + +```yaml +name: CI + +on: + pull_request: + +jobs: + lint-workflows: + uses: datum-cloud/actions/.github/workflows/lint-workflows.yaml@v1 + + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Run tests + run: make test +``` + +### Standalone in Actions Repository + +For the actions repository itself: + +```yaml +name: Lint Workflows + +on: + push: + paths: + - '.github/workflows/*.yaml' + pull_request: + paths: + - '.github/workflows/*.yaml' + +jobs: + lint: + uses: datum-cloud/actions/.github/workflows/lint-workflows.yaml@v1 +``` + +## Common Issues Detected + +### Syntax Errors + +- Invalid YAML syntax +- Incorrect indentation +- Missing required fields + +### Workflow Structure + +- Invalid job dependencies +- Incorrect `needs` references +- Invalid trigger configurations + +### Expression Errors + +- Malformed GitHub Actions expressions +- Invalid context access +- Type mismatches in expressions + +### Shell Script Issues + +- Unquoted variables +- Missing error handling +- Unsafe command usage + +## Example Error Output + +``` +.github/workflows/example.yaml:15:7: "needs" section has unknown job "typo-job" [unknown-job] + | +15 | needs: typo-job + | ^~~~~~ + +.github/workflows/example.yaml:23:15: shellcheck reported issue in this script: SC2086:info:1:6: Double quote to prevent globbing and word splitting [shellcheck] + | +23 | run: echo $VARIABLE + | ^~~~~~~~~~~~ +``` + +## Actionlint Features + +The workflow uses [actionlint](https://github.com/rhysd/actionlint) which provides: + +- **Fast validation**: Lints workflows in seconds +- **Comprehensive checks**: Covers syntax, semantics, and best practices +- **Shell script integration**: Uses shellcheck for `run` steps +- **Expression validation**: Validates GitHub Actions expressions +- **Detailed error messages**: Clear explanations with line numbers + +## Troubleshooting + +### False Positives + +If actionlint reports a false positive, you can: + +1. Add a comment to disable specific checks: + ```yaml + # actionlint-ignore: rule-name + ``` + +2. Configure actionlint with a config file (`.github/actionlint.yaml`) + +### Workflow Not Triggering + +- Verify the workflow file is in `.github/workflows/` directory +- Check that file extensions are `.yaml` or `.yml` +- Ensure the path filter matches your file changes + +## Additional Resources + +- [actionlint Documentation](https://github.com/rhysd/actionlint) +- [GitHub Actions Workflow Syntax](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions) +- [GitHub Actions Best Practices](https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions) diff --git a/docs/snyk-scan/README.md b/docs/snyk-scan/README.md new file mode 100644 index 0000000..ead1b95 --- /dev/null +++ b/docs/snyk-scan/README.md @@ -0,0 +1,280 @@ +# Snyk Security Scan + +The `.github/workflows/snyk-scan.yaml` reusable GitHub Action performs security +vulnerability scanning using **Snyk** for dependencies, Infrastructure as Code (IaC), +containers, and code. + +## 🚀 Usage + +### Inputs + +- **command** (optional, default: `test`): Snyk command to run. Options: `test`, `monitor`, `iac test`, `container test`, `code test`. +- **fail-on-issues** (optional, default: `true`): Whether to fail the build if vulnerabilities are found. +- **severity-threshold** (optional, default: `high`): Minimum severity to fail on. Options: `low`, `medium`, `high`, `critical`. +- **target-file** (optional): Specific file to scan (e.g., `package.json`, `go.mod`, `pom.xml`). +- **args** (optional): Additional arguments to pass to Snyk CLI. +- **snyk-org** (optional): Snyk organization slug or ID. +- **debug** (optional, default: `false`): Enable debug mode (`-d` flag) for verbose output. +- **upload-sarif** (optional, default: `true`): Whether to upload SARIF results to GitHub Security. +- **sarif-file-output** (optional, default: `snyk.sarif`): Path for SARIF output file. +- **json-file-output** (optional, default: `snyk.json`): Path for JSON output file. + +### Outputs + +- **vulnerabilities-found**: Whether vulnerabilities were found (`true`/`false`). + +### Required Secrets + +- **SNYK_TOKEN**: Snyk API token for authentication. Must be configured in repository or organization secrets. + +### Required Permissions + +```yaml +permissions: + contents: read + security-events: write + actions: read +``` + +## Features + +- ✅ **Multi-scan support**: Dependencies, IaC, containers, and code scanning +- ✅ **GitHub Security integration**: Automatic SARIF upload to Security tab +- ✅ **Detailed reporting**: JSON output with vulnerability breakdown +- ✅ **Workflow summary**: Visual table with severity counts +- ✅ **Artifact retention**: Results saved for 30 days for audit purposes +- ✅ **Flexible configuration**: Customizable thresholds and failure modes +- ✅ **Monitor mode**: Track vulnerabilities over time in Snyk dashboard + +## Best Practices + +- Always use a tagged version when referencing the GitHub action workflow to prevent unexpected breaking changes. +- Enable `upload-sarif: true` to integrate with GitHub Security features. +- Use `fail-on-issues: true` in pull requests to prevent merging vulnerable code. +- Configure `monitor` command on main/master branches to track vulnerabilities over time. +- Set appropriate `severity-threshold` based on your project's security requirements. +- Review artifacts regularly for compliance and audit purposes. + +## Workflow Examples + +### Basic IaC Scan + +Scan Infrastructure as Code files (Terraform, CloudFormation, Kubernetes, etc.): + +```yaml +name: Snyk Security Scan + +on: + push: + pull_request: + +jobs: + snyk-iac: + permissions: + contents: read + security-events: write + actions: read + uses: datum-cloud/actions/.github/workflows/snyk-scan.yaml@v1 + with: + command: "iac test" + fail-on-issues: false + severity-threshold: "high" + upload-sarif: true + snyk-org: ${{ vars.SNYK_ORG }} + secrets: inherit +``` + +### Dependency Scan with Strict Mode + +Fail on critical vulnerabilities in dependencies: + +```yaml +name: Snyk Dependency Scan + +on: + pull_request: + +jobs: + snyk-dependencies: + permissions: + contents: read + security-events: write + actions: read + uses: datum-cloud/actions/.github/workflows/snyk-scan.yaml@v1 + with: + command: "test" + fail-on-issues: true + severity-threshold: "critical" + upload-sarif: true + snyk-org: ${{ vars.SNYK_ORG }} + secrets: inherit +``` + +### Container Image Scan + +Scan Docker container images for vulnerabilities: + +```yaml +name: Snyk Container Scan + +on: + push: + branches: [main] + +jobs: + snyk-container: + permissions: + contents: read + security-events: write + actions: read + uses: datum-cloud/actions/.github/workflows/snyk-scan.yaml@v1 + with: + command: "container test" + args: "myapp:latest" + fail-on-issues: true + severity-threshold: "high" + upload-sarif: true + snyk-org: ${{ vars.SNYK_ORG }} + secrets: inherit +``` + +### Multi-Scan with Monitor + +Combine test and monitor for comprehensive security: + +```yaml +name: Snyk Security + +on: + push: + branches: [main, master] + +jobs: + snyk-test: + permissions: + contents: read + security-events: write + actions: read + uses: datum-cloud/actions/.github/workflows/snyk-scan.yaml@v1 + with: + command: "test" + fail-on-issues: false + upload-sarif: true + snyk-org: ${{ vars.SNYK_ORG }} + secrets: inherit + + snyk-monitor: + needs: snyk-test + permissions: + contents: read + security-events: write + actions: read + uses: datum-cloud/actions/.github/workflows/snyk-scan.yaml@v1 + with: + command: "monitor" + fail-on-issues: false + upload-sarif: false + snyk-org: ${{ vars.SNYK_ORG }} + secrets: inherit +``` + +### Scheduled Security Audit + +Run daily security scans to detect new vulnerabilities: + +```yaml +name: Scheduled Security Scan + +on: + schedule: + - cron: '0 2 * * *' # Daily at 2 AM UTC + +jobs: + snyk-scan: + permissions: + contents: read + security-events: write + actions: read + uses: datum-cloud/actions/.github/workflows/snyk-scan.yaml@v1 + with: + command: "test" + fail-on-issues: false + severity-threshold: "medium" + upload-sarif: true + snyk-org: ${{ vars.SNYK_ORG }} + secrets: inherit +``` + +## Results Visualization + +### GitHub Security Tab + +SARIF results are automatically uploaded to the Security tab: +- Navigate to **Security** → **Code scanning** → **Snyk** +- View detailed vulnerability information with remediation advice + +### Workflow Summary + +Each workflow run displays a summary table with vulnerability counts: + +| Severity | Count | +|----------|-------| +| 🔴 Critical | X | +| 🟠 High | X | +| 🟡 Medium | X | +| 🟢 Low | X | + +### Artifacts + +JSON and SARIF files are saved as artifacts for 30 days, accessible from the workflow run page. + +### Snyk Dashboard + +When using `monitor` command, view historical trends and detailed analysis in the Snyk dashboard. + +## Supported Commands + +| Command | Description | Generates SARIF | +|---------|-------------|-----------------| +| `test` | Scan dependencies for vulnerabilities | ✅ | +| `monitor` | Send snapshot to Snyk for continuous monitoring | ❌ | +| `iac test` | Scan Infrastructure as Code files | ✅ | +| `container test` | Scan container images | ✅ | +| `code test` | Scan source code (SAST) | ✅ | + +## Troubleshooting + +### Error: SNYK_TOKEN is not set + +- Verify the `SNYK_TOKEN` secret exists in repository or organization settings +- Ensure `secrets: inherit` is specified in the workflow + +### SARIF upload fails + +- Verify `upload-sarif: true` is set +- Check that `security-events: write` permission is granted +- Confirm the Snyk command supports SARIF output (e.g., `monitor` does not) + +### Workflow fails unexpectedly + +- Enable `debug: true` temporarily for verbose output +- Review the "Run Snyk Security Scan" step logs +- Verify the command is compatible with your project type + +## Configuration Variables + +### Repository Variables (Optional) + +- **SNYK_ORG**: Snyk organization slug or ID for centralized configuration + +### Repository Secrets (Required) + +- **SNYK_TOKEN**: Snyk API authentication token + - Obtain from: https://app.snyk.io/account + - Add to: Settings → Secrets and variables → Actions → New repository secret + +## Additional Resources + +- [Snyk CLI Documentation](https://docs.snyk.io/snyk-cli) +- [GitHub Code Scanning](https://docs.github.com/en/code-security/code-scanning) +- [SARIF Format Specification](https://docs.github.com/en/code-security/code-scanning/integrating-with-code-scanning/sarif-support-for-code-scanning) diff --git a/docs/validate-kustomize/README.md b/docs/validate-kustomize/README.md new file mode 100644 index 0000000..a0e060b --- /dev/null +++ b/docs/validate-kustomize/README.md @@ -0,0 +1,235 @@ +# Validate Kustomize Configurations + +The `.github/workflows/validate-kustomize.yaml` reusable GitHub Action validates +all Kustomize configurations in a repository by building them and checking for +errors. + +## 🚀 Usage + +### Inputs + +This workflow does not require any inputs. + +### Automatic Triggers + +The workflow automatically runs when: +- Workflow files in `.github/workflows/*.yaml` are modified +- Called as a reusable workflow from another repository + +### Required Permissions + +No special permissions required (uses default `contents: read`). + +## Features + +- ✅ **Automatic discovery**: Finds all `kustomization.yaml` files in the repository +- ✅ **Build validation**: Validates that each kustomization can be built successfully +- ✅ **Helm support**: Enables Helm chart inflation with `--enable-helm` flag +- ✅ **Detailed reporting**: Shows validation results in workflow summary +- ✅ **Error highlighting**: Clear error messages for failed validations +- ✅ **Multi-kustomization**: Validates all kustomizations in a single run + +## Best Practices + +- Always use a tagged version when referencing the GitHub action workflow to prevent unexpected breaking changes. +- Run this workflow on every pull request to catch configuration errors early. +- Organize kustomizations in separate directories for better maintainability. +- Use overlays for environment-specific configurations. + +## Workflow Examples + +### Basic Usage + +Create a workflow in your repository (e.g., `.github/workflows/validate.yaml`) +that calls this reusable action: + +```yaml +name: Validate Kustomize + +on: + pull_request: + push: + branches: [main] + +jobs: + validate: + uses: datum-cloud/actions/.github/workflows/validate-kustomize.yaml@v1 +``` + +### With Path Filtering + +Only run when Kubernetes manifests change: + +```yaml +name: Validate Kustomize + +on: + pull_request: + paths: + - 'config/**' + - 'overlays/**' + - '**/kustomization.yaml' + +jobs: + validate: + uses: datum-cloud/actions/.github/workflows/validate-kustomize.yaml@v1 +``` + +### Combined with Other Checks + +Run alongside other validation workflows: + +```yaml +name: CI + +on: + pull_request: + +jobs: + validate-kustomize: + uses: datum-cloud/actions/.github/workflows/validate-kustomize.yaml@v1 + + lint-yaml: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Lint YAML + run: yamllint . + + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Run tests + run: make test +``` + +## Validation Process + +The workflow performs the following steps: + +1. **Discovery**: Searches for all `kustomization.yaml` files recursively +2. **Build**: Runs `kustomize build` on each directory containing a kustomization +3. **Report**: Generates a summary with validation results +4. **Fail**: Exits with error code if any kustomization fails to build + +## Workflow Summary Output + +### All Valid + +``` +# Kustomize Validation Results + +### 🎉 All kustomizations are valid! +``` + +### With Errors + +``` +# Kustomize Validation Results + +### ❌ Error in `./config/base` +``` +Error: accumulating resources: accumulation err='accumulating resources from 'deployment.yaml': +evalsymlink failure on '/path/deployment.yaml' : lstat /path/deployment.yaml: no such file or directory' +``` +``` + +## Supported Kustomize Features + +- ✅ **Resources**: Standard Kubernetes manifests +- ✅ **Patches**: Strategic merge and JSON patches +- ✅ **Overlays**: Environment-specific configurations +- ✅ **Components**: Reusable configuration pieces +- ✅ **Helm charts**: Helm chart inflation (with `--enable-helm`) +- ✅ **Generators**: ConfigMap and Secret generators +- ✅ **Transformers**: Common labels, annotations, and name prefixes + +## Common Validation Errors + +### Missing Resource Files + +``` +Error: accumulating resources: accumulation err='accumulating resources from 'missing.yaml': +evalsymlink failure on '/path/missing.yaml' : lstat /path/missing.yaml: no such file or directory' +``` + +**Solution**: Verify all files referenced in `kustomization.yaml` exist. + +### Invalid YAML Syntax + +``` +Error: yaml: line 5: mapping values are not allowed in this context +``` + +**Solution**: Check YAML syntax in the referenced files. + +### Invalid Patch Target + +``` +Error: no matches for Id ~G_v1_ConfigMap|~X|my-config; failed to find unique target for patch +``` + +**Solution**: Ensure patch targets exist in the base resources. + +### Circular Dependencies + +``` +Error: cycle detected in kustomization +``` + +**Solution**: Review resource and base references to eliminate circular dependencies. + +## Repository Structure Example + +``` +. +├── config/ +│ ├── base/ +│ │ ├── kustomization.yaml +│ │ ├── deployment.yaml +│ │ └── service.yaml +│ └── overlays/ +│ ├── dev/ +│ │ ├── kustomization.yaml +│ │ └── patch-replicas.yaml +│ └── prod/ +│ ├── kustomization.yaml +│ └── patch-replicas.yaml +└── .github/ + └── workflows/ + └── validate.yaml +``` + +The workflow will validate: +- `./config/base/kustomization.yaml` +- `./config/overlays/dev/kustomization.yaml` +- `./config/overlays/prod/kustomization.yaml` + +## Troubleshooting + +### No kustomization.yaml files found + +If the workflow reports no kustomization files: +- Verify files are named exactly `kustomization.yaml` (not `kustomization.yml`) +- Check that files are committed to the repository +- Ensure files are not in `.gitignore` + +### Helm chart inflation fails + +If using Helm charts: +- Verify Helm chart references are correct +- Check that chart repositories are accessible +- Ensure chart versions are specified + +### Build succeeds locally but fails in CI + +- Check for environment-specific paths or references +- Verify all files are committed +- Ensure no local-only files are referenced + +## Additional Resources + +- [Kustomize Documentation](https://kubectl.docs.kubernetes.io/references/kustomize/) +- [Kustomize GitHub Repository](https://github.com/kubernetes-sigs/kustomize) +- [Kubernetes Documentation](https://kubernetes.io/docs/tasks/manage-kubernetes-objects/kustomization/) From 59769c197eef6e792c22365a03b1b674033b4657 Mon Sep 17 00:00:00 2001 From: OscarLlamas6 Date: Sat, 15 Nov 2025 14:35:38 -0600 Subject: [PATCH 12/13] chore: making linter happy --- .github/workflows/snyk-scan.yaml | 34 +++++++++++++++++--------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/.github/workflows/snyk-scan.yaml b/.github/workflows/snyk-scan.yaml index ddb39f6..253061e 100644 --- a/.github/workflows/snyk-scan.yaml +++ b/.github/workflows/snyk-scan.yaml @@ -160,32 +160,34 @@ jobs: - name: Parse Snyk results and create summary if: always() && hashFiles(inputs.json-file-output) != '' run: | - echo "## 🔍 Snyk Security Scan Results" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY + echo "## 🔍 Snyk Security Scan Results" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" if [ -f "${{ inputs.json-file-output }}" ]; then # Extract vulnerability counts by severity - CRITICAL=$(jq '[.vulnerabilities[]? | select(.severity=="critical")] | length' ${{ inputs.json-file-output }} 2>/dev/null || echo "0") - HIGH=$(jq '[.vulnerabilities[]? | select(.severity=="high")] | length' ${{ inputs.json-file-output }} 2>/dev/null || echo "0") - MEDIUM=$(jq '[.vulnerabilities[]? | select(.severity=="medium")] | length' ${{ inputs.json-file-output }} 2>/dev/null || echo "0") - LOW=$(jq '[.vulnerabilities[]? | select(.severity=="low")] | length' ${{ inputs.json-file-output }} 2>/dev/null || echo "0") + CRITICAL=$(jq '[.vulnerabilities[]? | select(.severity=="critical")] | length' "${{ inputs.json-file-output }}" 2>/dev/null || echo "0") + HIGH=$(jq '[.vulnerabilities[]? | select(.severity=="high")] | length' "${{ inputs.json-file-output }}" 2>/dev/null || echo "0") + MEDIUM=$(jq '[.vulnerabilities[]? | select(.severity=="medium")] | length' "${{ inputs.json-file-output }}" 2>/dev/null || echo "0") + LOW=$(jq '[.vulnerabilities[]? | select(.severity=="low")] | length' "${{ inputs.json-file-output }}" 2>/dev/null || echo "0") - echo "| Severity | Count |" >> $GITHUB_STEP_SUMMARY - echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY - echo "| 🔴 Critical | $CRITICAL |" >> $GITHUB_STEP_SUMMARY - echo "| 🟠 High | $HIGH |" >> $GITHUB_STEP_SUMMARY - echo "| 🟡 Medium | $MEDIUM |" >> $GITHUB_STEP_SUMMARY - echo "| 🟢 Low | $LOW |" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY + { + echo "| Severity | Count |" + echo "|----------|-------|" + echo "| 🔴 Critical | $CRITICAL |" + echo "| 🟠 High | $HIGH |" + echo "| 🟡 Medium | $MEDIUM |" + echo "| 🟢 Low | $LOW |" + echo "" + } >> "$GITHUB_STEP_SUMMARY" TOTAL=$((CRITICAL + HIGH + MEDIUM + LOW)) if [ "$TOTAL" -gt 0 ]; then - echo "⚠️ **Total vulnerabilities found: $TOTAL**" >> $GITHUB_STEP_SUMMARY + echo "⚠️ **Total vulnerabilities found: $TOTAL**" >> "$GITHUB_STEP_SUMMARY" else - echo "✅ **No vulnerabilities found**" >> $GITHUB_STEP_SUMMARY + echo "✅ **No vulnerabilities found**" >> "$GITHUB_STEP_SUMMARY" fi else - echo "ℹ️ No JSON output file found" >> $GITHUB_STEP_SUMMARY + echo "ℹ️ No JSON output file found" >> "$GITHUB_STEP_SUMMARY" fi - name: Upload Snyk SARIF results to GitHub Security From a9625b00d5c72756e6bc4036bfafe2bf4237407c Mon Sep 17 00:00:00 2001 From: OscarLlamas6 Date: Sat, 15 Nov 2025 15:14:41 -0600 Subject: [PATCH 13/13] chore: update codeql-action version --- .github/workflows/snyk-scan.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/snyk-scan.yaml b/.github/workflows/snyk-scan.yaml index 253061e..76b49da 100644 --- a/.github/workflows/snyk-scan.yaml +++ b/.github/workflows/snyk-scan.yaml @@ -192,7 +192,7 @@ jobs: - name: Upload Snyk SARIF results to GitHub Security if: always() && inputs.upload-sarif == true && hashFiles(inputs.sarif-file-output) != '' - uses: github/codeql-action/upload-sarif@v3 + uses: github/codeql-action/upload-sarif@v4 with: sarif_file: ${{ inputs.sarif-file-output }} category: snyk-${{ inputs.command }}