diff --git a/.github/workflows/universal-dependency-track.yml b/.github/workflows/universal-dependency-track.yml new file mode 100644 index 0000000..d116925 --- /dev/null +++ b/.github/workflows/universal-dependency-track.yml @@ -0,0 +1,174 @@ +name: Dependency Track SBOM Scan + +on: + push: + branches: + - main + - master + pull_request: + branches: + - main + - master + schedule: + # Run daily at 9am UTC + - cron: '0 9 * * *' + workflow_dispatch: # Allow manual trigger + +jobs: + dependency-scan: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js (if needed) + if: hashFiles('**/package.json') != '' + uses: actions/setup-node@v4 + with: + node-version: '18' + + - name: Setup Python (if needed) + if: hashFiles('**/requirements.txt', '**/pyproject.toml', '**/setup.py') != '' + uses: actions/setup-python@v5 + with: + python-version: '3.x' + + - name: Setup Go (if needed) + if: hashFiles('**/go.mod') != '' + uses: actions/setup-go@v5 + with: + go-version: 'stable' + + - name: Setup Java (if needed) + if: hashFiles('**/pom.xml', '**/build.gradle', '**/build.gradle.kts') != '' + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '17' + + - name: Install dependencies (all languages, including monorepos) + run: | + echo "๐Ÿ” Detecting and installing dependencies..." + + # Install pnpm if any pnpm-lock.yaml exists (needed for mixed monorepos) + if find . -name "pnpm-lock.yaml" -not -path "*/node_modules/*" | grep -q .; then + echo "๐Ÿ“ฆ Installing pnpm globally (detected pnpm projects)" + npm install -g pnpm + fi + + # Node.js: Check root first for workspace-level install + if [ -f "pnpm-lock.yaml" ]; then + echo "๐Ÿ“ฆ Detected pnpm workspace at root" + pnpm install --no-frozen-lockfile --ignore-scripts 2>/dev/null || echo "Note: pnpm install failed, continuing..." + elif [ -f "yarn.lock" ]; then + echo "๐Ÿ“ฆ Detected yarn workspace at root" + yarn install --ignore-scripts 2>/dev/null || echo "Note: yarn install failed, continuing..." + elif [ -f "package-lock.json" ]; then + echo "๐Ÿ“ฆ Found npm project at root" + npm ci 2>/dev/null || npm install 2>/dev/null || echo "Note: npm install failed, continuing..." + elif [ -f "package.json" ]; then + echo "๐Ÿ“ฆ Found Node.js project at root (no lock file)" + npm install --package-lock-only 2>/dev/null || echo "Note: npm install failed, continuing..." + fi + + # Find all subdirectories and install based on their lock files (for mixed monorepos) + find . -type d -not -path "*/node_modules/*" -not -path "*/.git/*" -not -path "." | while read -r dir; do + if [ -f "$dir/pnpm-lock.yaml" ]; then + echo "๐Ÿ“ฆ Found pnpm project in: $dir" + (cd "$dir" && pnpm install --no-frozen-lockfile --ignore-scripts 2>/dev/null || echo "Note: pnpm install failed in $dir") + elif [ -f "$dir/yarn.lock" ]; then + echo "๐Ÿ“ฆ Found yarn project in: $dir" + (cd "$dir" && yarn install --ignore-scripts 2>/dev/null || echo "Note: yarn install failed in $dir") + elif [ -f "$dir/package-lock.json" ]; then + echo "๐Ÿ“ฆ Found npm project in: $dir" + (cd "$dir" && (npm ci 2>/dev/null || npm install 2>/dev/null || echo "Note: npm install failed in $dir")) + elif [ -f "$dir/package.json" ]; then + echo "๐Ÿ“ฆ Found Node.js project in: $dir (no lock file)" + (cd "$dir" && npm install --package-lock-only 2>/dev/null || echo "Note: npm install failed in $dir") + fi + done + + # Python projects + find . -name "requirements.txt" -not -path "*/.git/*" -not -path "*/venv/*" | while read -r req; do + dir=$(dirname "$req") + echo "๐Ÿ Found Python project in: $dir" + (cd "$dir" && pip install -r requirements.txt 2>/dev/null || echo "Note: pip install failed in $dir") + done + + # Go projects + find . -name "go.mod" -not -path "*/.git/*" | while read -r gomod; do + dir=$(dirname "$gomod") + echo "๐Ÿ”ท Found Go project in: $dir" + (cd "$dir" && go mod download 2>/dev/null || echo "Note: go mod download failed in $dir") + done + + # Java/Maven projects + find . -name "pom.xml" -not -path "*/.git/*" | while read -r pom; do + dir=$(dirname "$pom") + echo "โ˜• Found Maven project in: $dir" + (cd "$dir" && mvn dependency:resolve 2>/dev/null || echo "Note: mvn dependency:resolve failed in $dir") + done + + # Java/Gradle projects + find . -name "build.gradle*" -not -path "*/.git/*" | while read -r gradle; do + dir=$(dirname "$gradle") + echo "โ˜• Found Gradle project in: $dir" + (cd "$dir" && (./gradlew dependencies 2>/dev/null || gradle dependencies 2>/dev/null || echo "Note: gradle dependencies failed in $dir")) + done + + echo "โœ… Dependency installation complete" + continue-on-error: true + + - name: Install Trivy + run: | + curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin + + - name: Generate SBOM with Trivy + run: | + echo "๐Ÿ” Scanning repository for dependencies and licenses..." + trivy fs --format cyclonedx --scanners license --license-full . -o bom-raw.json + + echo "๐Ÿงน Cleaning SBOM (removing lock files and false positives)..." + cat bom-raw.json | jq ' + .components |= map(select( + .name != "package-lock.json" and + .name != "yarn.lock" and + .name != "pnpm-lock.yaml" and + .name != "go.mod" and + .name != "go.sum" and + .name != "uv.lock" and + .name != "poetry.lock" and + .name != "Pipfile.lock" and + .name != "Cargo.lock" and + .name != "composer.lock" and + (.name | test(".*lock\\.json$") | not) + )) + ' > bom.json + + echo "" + echo "๐Ÿ“Š SBOM Statistics:" + cat bom.json | jq '{ + totalComponents: (.components | length), + componentsWithLicenses: ([.components[] | select(.licenses)] | length) + }' + + - name: Upload BOM to Dependency-Track + uses: DependencyTrack/gh-upload-sbom@v3 + with: + serverhostname: '44.194.0.211' + port: '8091' + protocol: 'http' + apikey: ${{ secrets.DEPENDENCYTRACK_API_KEY }} + projectname: ${{ github.event.repository.name }} + projectversion: 'main' + bomfilename: 'bom.json' + autocreate: 'true' + + - name: Archive SBOM artifact + uses: actions/upload-artifact@v4 + if: always() + with: + name: sbom-cyclonedx + path: bom.json + retention-days: 90