Supply Chain Security: SBOM, Dependency Scanning, and Software Security
TL;DR: This guide covers securing your software supply chain. Learn to generate SBOMs, scan dependencies for vulnerabilities, implement software signing, and prevent supply chain attacks.
Introduction
Software supply chain attacks have increased 300% in recent years. Attackers target:
- Dependencies - Compromise upstream packages
- Build systems - Inject malicious code during build
- Package repositories - Upload malicious packages
- Development tools - Compromise IDEs and tooling
A comprehensive supply chain security program includes:
- SBOM generation - Know what’s in your software
- Dependency scanning - Find vulnerable components
- Software signing - Verify authenticity
- Secure build pipelines - Prevent tampering
Software Bill of Materials (SBOM)
SBOM Formats
| Format | Description | Tools |
|---|---|---|
| SPDX | Linux Foundation standard | spdx-builder |
| CycloneDX | OWASP standard | cyclonedx |
| SWID | ISO standard | swidtools |
Generating SBOMs
Python SBOM with CycloneDX
# Install cyclonedx-bom
pip install cyclonedx-bom
# Generate SBOM
cyclonedx-py -r -o bom.xml
Node.js SBOM
# Using npm
npm install -g @cyclonedx/bom
# Generate
cyclonedx-npm -o bom.xml
SBOM Generation Code
from cyclonedx.model import (
Bom,
Component,
ComponentType,
Dependency,
Property
)
from cyclonedx.parser import Parser
from cyclonedx.writer import Writer
class SBOMGenerator:
def __init__(self, project_path: str):
self.project_path = project_path
self.components = []
def generate_sbom(self, output_format: str = 'json') -> Bom:
"""Generate SBOM for the project"""
bom = Bom()
# Add main component
main_component = Component(
name='my-application',
version='1.0.0',
component_type=ComponentType.APPLICATION
)
bom.components.append(main_component)
# Add dependencies
dependencies = self._scan_dependencies()
for dep in dependencies:
component = Component(
name=dep['name'],
version=dep['version'],
component_type=ComponentType.LIBRARY,
purl=dep['purl']
)
bom.components.append(component)
bom.dependencies.append(Dependency(ref=dep['purl']))
return bom
def _scan_dependencies(self) -> list:
"""Scan project dependencies"""
# Implementation for different package managers
return []
def export(self, bom: Bom, format: str, filename: str):
"""Export SBOM to file"""
writer = Writer()
writer.write(bom, filename, format)
Dependency Vulnerability Scanning
Trivy for Container Scanning
# Scan container image
trivy image --security-checks vuln,config myapp:latest
# Scan filesystem
trivy fs --security-checks vuln,secret /path/to/project
# Scan Git repository
trivy repo --security-checks vuln https://github.com/user/repo
Python Dependency Scanning
# Using safety
pip install safety
safety check
# Using pip-audit
pip install pip-audit
pip-audit
Dependency Scan Integration
import subprocess
import json
class DependencyScanner:
def __init__(self, language: str):
self.language = language
def scan(self, project_path: str) -> list:
"""Scan dependencies for vulnerabilities"""
if self.language == 'python':
return self._scan_python(project_path)
elif self.language == 'javascript':
return self._scan_javascript(project_path)
elif self.language == 'go':
return self._scan_go(project_path)
return []
def _scan_python(self, project_path: str) -> list:
"""Scan Python dependencies"""
result = subprocess.run(
['pip-audit', '--format', 'json'],
capture_output=True,
cwd=project_path
)
if result.returncode == 0:
data = json.loads(result.stdout)
return self._parse_pip_audit(data)
return []
def _parse_pip_audit(self, data: dict) -> list:
"""Parse pip-audit results"""
vulnerabilities = []
for dep in data.get('dependencies', []):
for vuln in dep.get('vulns', []):
vulnerabilities.append({
'package': dep['name'],
'version': dep['version'],
'vulnerability': vuln['id'],
'severity': vuln['severity'],
'description': vuln.get('description', '')
})
return vulnerabilities
def _scan_javascript(self, project_path: str) -> list:
"""Scan JavaScript dependencies"""
result = subprocess.run(
['npm', 'audit', '--json'],
capture_output=True,
cwd=project_path
)
data = json.loads(result.stdout)
# Parse npm audit results
return []
def _scan_go(self, project_path: str) -> list:
"""Scan Go dependencies"""
result = subprocess.run(
['govulncheck', './...'],
capture_output=True,
cwd=project_path
)
return []
Software Signing
Sigstore Integration
# Install cosign
cosign generate-key-pair
# Sign container image
cosign sign myregistry/myimage:latest
# Verify signature
cosign verify myregistry/myimage:latest
Signing Artifacts
import subprocess
class SoftwareSigner:
def __init__(self, key_path: str):
self.key_path = key_path
def sign_file(self, file_path: str) -> str:
"""Sign a file using cosign"""
result = subprocess.run(
['cosign', 'sign-blob', '--key', self.key_path, file_path],
capture_output=True,
text=True
)
return result.stdout.strip()
def verify_signature(self, file_path: str, cert_path: str) -> bool:
"""Verify file signature"""
result = subprocess.run(
['cosign', 'verify-blob', '--key', cert_path, file_path],
capture_output=True
)
return result.returncode == 0
Secure Build Pipelines
GitHub Actions Workflow
name: Secure Build
on: [push, pull_request]
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Trivy scanner
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload results
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: 'trivy-results.sarif'
- name: Dependency Review
uses: actions/dependency-review-action@v3
- name: SBOM Generation
uses:CycloneDX/gh-action-create-sbom@v1
Build Integrity Verification
class BuildIntegrity:
def __init__(self):
self.measurements = []
def add_measurement(self, step: str, hash: str):
"""Add build step measurement"""
self.measurements.append({
'step': step,
'hash': hash,
'timestamp': datetime.utcnow()
})
def generate_provenance(self) -> dict:
"""Generate build provenance"""
return {
'buildType': 'https://buildtools.google.com/build#generic',
'invocation': {
'configuration': {...}
},
'runDetails': {
'builder': {...},
'buildNumber': '123'
},
'materials': [...],
'runEntries': self.measurements
}
Package Repository Security
Private Registry Configuration
# requirements.txt with pinned versions
requests==2.31.0
flask==3.0.0
numpy==1.26.0
Verify Package Integrity
import hashlib
import subprocess
class PackageVerifier:
def __init__(self):
self.trusted_hash = 'abc123...' # Pre-configured trusted hash
def verify_package(self, package_path: str) -> bool:
"""Verify package integrity"""
with open(package_path, 'rb') as f:
package_hash = hashlib.sha256(f.read()).hexdigest()
return package_hash == self.trusted_hash
def verify_with_pgp(self, package_path: str, signature_path: str) -> bool:
"""Verify package with PGP signature"""
result = subprocess.run(
['gpg', '--verify', signature_path, package_path],
capture_output=True
)
return result.returncode == 0
Conclusion
Supply chain security requires defense in depth:
- SBOM generation - Know all components in your software
- Continuous scanning - Find vulnerabilities in dependencies
- Software signing - Verify authenticity and integrity
- Secure pipelines - Prevent build-time tampering
- Package verification - Validate packages from registries
Comments