
Dependency Track
- 44 installs
- 6 repo stars
- Updated July 22, 2026
- julianobarbosa/claude-code-skills
Manage software composition and supply chain security with dependency tracking.
About
Dependency-Track Skill. Comprehensive guide for implementing, deploying, and operating Dependency-Track - an intelligent Software Composition Analysis platform.
- Helm Chart: 0.40.0
- App Version: 4.13.6
Dependency Track by the numbers
- 44 all-time installs (skills.sh)
- +1 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #432 of 911 Databases skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/julianobarbosa/claude-code-skills --skill dependency-trackAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 44 |
|---|---|
| repo stars | ★ 6 |
| Last updated | July 22, 2026 |
| Repository | julianobarbosa/claude-code-skills ↗ |
What it does
Manage software composition and supply chain security with dependency tracking.
Files
Dependency-Track Skill
Comprehensive guide for implementing, deploying, and operating Dependency-Track - an intelligent Software Composition Analysis (SCA) platform that identifies and reduces risk in the software supply chain through SBOM management.
Current Versions:
- Helm Chart:
0.40.0 - App Version:
4.13.6 - Helm Repository:
https://dependencytrack.github.io/helm-charts
Overview
Dependency-Track is an API-first platform that:
- Consumes and produces CycloneDX SBOMs and VEX documents
- Monitors components for known vulnerabilities across the entire portfolio
- Integrates with NVD, GitHub Advisories, OSS Index, Snyk, Trivy, OSV, and VulnDB
- Provides policy enforcement for security, license, and operational compliance
- Supports OAuth 2.0, OIDC, LDAP, Active Directory authentication
- Supports EPSS (Exploit Prediction Scoring System) for prioritization
- Identifies APIs and external service components
Quick Reference
| Resource | Path |
|---|---|
| Deployment Templates | references/deployment/ |
| CI/CD Integration | references/cicd/ |
| API Examples | references/api/ |
| Policy Templates | references/policies/ |
| Troubleshooting | references/troubleshooting.md |
---
1. Deployment Options
Docker Compose (Recommended for Production)
# Download official docker-compose
curl -LO https://dependencytrack.org/docker-compose.yml
# Start services
docker compose up -d
# Access UI at http://localhost:8080
# Default credentials: admin / adminMinimum Requirements:
- API Server: 4.5GB RAM, 2 CPU cores
- Frontend: 512MB RAM, 1 CPU core
Recommended Requirements:
- API Server: 16GB RAM, 4 CPU cores
- Frontend: 1GB RAM, 2 CPU cores
Kubernetes with Helm
# Add Helm repository
helm repo add dependency-track https://dependencytrack.github.io/helm-charts
helm repo update
# View available charts
helm search repo dependency-track
# Install with custom values
helm install dtrack dependency-track/dependency-track \
--namespace dtrack \
--create-namespace \
-f values-production.yaml
# Upgrade existing installation
helm upgrade dtrack dependency-track/dependency-track \
--namespace dtrack \
-f values-production.yamlAvailable Charts:
| Chart | Description | Status |
|---|---|---|
dependency-track/dependency-track | Monolithic deployment (v4.x) | Production Ready |
dependency-track/hyades | Microservices deployment (v5.x) | Incubating (Not GA) |
See references/deployment/ for complete manifests and Helm values.
ArgoCD GitOps Deployment (AKS)
For enterprise Kubernetes deployments using ArgoCD with multi-source ApplicationSets:
Directory Structure:
infra-team/
applicationset/
dependency-track.yaml # ArgoCD ApplicationSet definition
argo-cd-helm-values/
kube-addons/
dependency-track/
azure-ad-setup.sh # Azure AD App Registration script
cafehyna-dev/
values.yaml # Environment-specific Helm values
secretproviderclass.yaml # Azure Key Vault CSI integrationApplicationSet Example (Multi-Source):
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: dependency-track
namespace: argocd
spec:
generators:
- list:
elements:
- cluster: cafehyna-dev
url: https://aks-cluster-url:443
project: kube-addons
branch: main
environment: development
urlSuffix: ".dev"
template:
metadata:
name: "{{cluster}}-dependency-track"
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: "{{project}}"
sources:
# Source 1: Helm chart from official repository
- chart: dependency-track
repoURL: https://dependencytrack.github.io/helm-charts
targetRevision: "0.40.0"
helm:
releaseName: dependency-track
valueFiles:
- $values/argo-cd-helm-values/kube-addons/dependency-track/{{cluster}}/values.yaml
# Source 2: Values repository reference
- repoURL: https://your-git-repo.git
targetRevision: "{{branch}}"
ref: values
# Source 3: Additional manifests (SecretProviderClass)
- repoURL: https://your-git-repo.git
targetRevision: "{{branch}}"
path: argo-cd-helm-values/kube-addons/dependency-track/{{cluster}}
directory:
exclude: values.yaml
destination:
server: "{{url}}"
namespace: dependency-track
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- ServerSideApply=true
- CreateNamespace=trueAzure Key Vault CSI Driver (SecretProviderClass):
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: dependency-track-secrets-kv
namespace: dependency-track
spec:
provider: azure
parameters:
usePodIdentity: "false"
useVMManagedIdentity: "true"
userAssignedIdentityID: "<managed-identity-client-id>"
keyvaultName: "<key-vault-name>"
tenantId: "<tenant-id>"
objects: |
array:
- |
objectName: "dtrack-oidc-client-id"
objectType: "secret"
objectAlias: "oidc-client-id"
- |
objectName: "dtrack-oidc-client-secret"
objectType: "secret"
objectAlias: "oidc-client-secret"
secretObjects:
- secretName: dependency-track-secrets
type: Opaque
data:
- objectName: oidc-client-id
key: oidc-client-id
- objectName: oidc-client-secret
key: oidc-client-secretAKS Spot Instance Tolerations (Cost Optimization):
apiServer:
tolerations:
- key: kubernetes.azure.com/scalesetpriority
operator: Equal
value: spot
effect: NoSchedule
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions:
- key: kubernetes.azure.com/scalesetpriority
operator: In
values:
- spotKey ignoreDifferences for ArgoCD:
ignoreDifferences:
# StatefulSet VolumeClaimTemplates (ServerSideApply drift)
- group: apps
kind: StatefulSet
jqPathExpressions:
- .spec.volumeClaimTemplates[]?.apiVersion
- .spec.volumeClaimTemplates[]?.kind
- .spec.volumeClaimTemplates[]?.status
# PVC dynamic fields
- group: ""
kind: PersistentVolumeClaim
jsonPointers:
- /status
- /spec/volumeName
# Azure Key Vault synced secrets
- group: ""
kind: Secret
name: dependency-track-secrets
jsonPointers:
- /dataSee references/deployment/argocd/ for complete ApplicationSet and values examples.
Hyades (Next-Generation Architecture)
Hyades is the incubating project for Dependency-Track v5, decoupling the monolithic API server into separate, scalable microservices.
Components:
hyades-apiserver- Core API serverhyades-frontend- Web UIhyades-notification-publisher- Notification handlinghyades-repository-meta-analyzer- Repository metadata analysishyades-vulnerability-analyzer- Vulnerability scanning
Requirements:
- External PostgreSQL database
- Apache Kafka cluster
- Kubernetes 1.19+
# Install Hyades (NOT PRODUCTION READY)
helm install hyades dependency-track/hyades \
--namespace dtrack \
--create-namespace \
--set common.database.jdbcUrl="jdbc:postgresql://postgres:5432/dtrack" \
--set common.database.username="dtrack" \
--set common.database.password="secret" \
--set common.kafka.bootstrapServers="kafka:9092"Warning: Hyades is NOT generally available. Breaking changes may occur without notice. Use only in test environments. GA roadmap: https://github.com/DependencyTrack/hyades/issues/860
---
2. Initial Configuration
First-Time Setup
1. Wait for initialization (10-30+ minutes):
- Creates default users, teams, permissions
- Mirrors NVD, GitHub Advisories (do not interrupt)
- Check progress:
docker logs -f dependency-track-apiserver
2. Change default password immediately after first login
3. Enable analyzers in Administration > Analyzers:
- Internal Analyzer (built-in)
- OSS Index (requires API token from ossindex.sonatype.org)
- Snyk (optional, requires token)
- Trivy (optional)
Database Configuration (Production)
# docker-compose override for PostgreSQL
services:
apiserver:
environment:
ALPINE_DATABASE_MODE: external
ALPINE_DATABASE_URL: jdbc:postgresql://postgres:5432/dtrack
ALPINE_DATABASE_DRIVER: org.postgresql.Driver
ALPINE_DATABASE_USERNAME: dtrack
ALPINE_DATABASE_PASSWORD: ${DB_PASSWORD}LDAP/Active Directory
# Active Directory configuration
ALPINE_LDAP_ENABLED=true
ALPINE_LDAP_SERVER_URL=ldap://ldap.example.com:3268
ALPINE_LDAP_BASEDN=dc=example,dc=com
ALPINE_LDAP_SECURITY_AUTH=simple
ALPINE_LDAP_AUTH_USERNAME_FORMAT=%s@example.com
ALPINE_LDAP_ATTRIBUTE_NAME=userPrincipalName
ALPINE_LDAP_ATTRIBUTE_MAIL=mail
ALPINE_LDAP_GROUPS_FILTER=(&(objectClass=group)(objectCategory=Group))
ALPINE_LDAP_USER_GROUPS_FILTER=(&(objectClass=group)(member:1.2.840.113556.1.4.1941:={USER_DN}))OpenID Connect (Azure AD, Okta, Keycloak)
ALPINE_OIDC_ENABLED=true
ALPINE_OIDC_ISSUER=https://login.microsoftonline.com/{tenant}/v2.0
ALPINE_OIDC_CLIENT_ID=your-client-id
ALPINE_OIDC_USERNAME_CLAIM=preferred_username
ALPINE_OIDC_TEAMS_CLAIM=groups
ALPINE_OIDC_USER_PROVISIONING=true
ALPINE_OIDC_TEAM_SYNCHRONIZATION=trueAzure AD OIDC Groups Integration (Detailed Guide)
How OIDC Groups Work in Dependency-Track
1. Group Claim Flow:
- User authenticates via Azure AD
- Azure AD returns ID token with
groupsclaim (array of Group Object IDs) - Dependency-Track reads the
groupsclaim (configured viaALPINE_OIDC_TEAMS_CLAIM) - Groups are matched to Teams via OpenID Connect Groups mappings
2. Important Behavior:
- Groups only appear in Administration > OpenID Connect Groups after a user from that group authenticates
- Teams must exist before group mapping (auto-created if
ALPINE_OIDC_TEAM_SYNCHRONIZATION=true) - Group Object IDs (UUIDs) are used, not display names
Method 1: API-Based Setup (Recommended)
Use the REST API to create teams, OIDC groups, and mappings:
# Set environment variables
export DTRACK_URL="https://dtrack.example.com"
export DTRACK_API_KEY="your-api-key"
# Azure AD Group Object ID
AZURE_GROUP_ID="31d6daa5-5cc2-4e5f-9bf5-75ee8e09198c"
# 1. Create a Team (if doesn't exist)
curl -X PUT "${DTRACK_URL}/api/v1/team" \
-H "X-Api-Key: ${DTRACK_API_KEY}" \
-H "Content-Type: application/json" \
-d '{"name": "Administrators"}'
# 2. Get the Team UUID
TEAM_UUID=$(curl -s -H "X-Api-Key: ${DTRACK_API_KEY}" \
"${DTRACK_URL}/api/v1/team" | jq -r '.[] | select(.name=="Administrators") | .uuid')
# 3. Create OIDC Group (required before mapping)
# NOTE: Use Azure AD Group Object ID as both uuid and name
curl -X PUT "${DTRACK_URL}/api/v1/oidc/group" \
-H "X-Api-Key: ${DTRACK_API_KEY}" \
-H "Content-Type: application/json" \
-d "{\"uuid\": \"${AZURE_GROUP_ID}\", \"name\": \"${AZURE_GROUP_ID}\"}"
# 4. Get the created OIDC Group UUID
OIDC_GROUP_UUID=$(curl -s -H "X-Api-Key: ${DTRACK_API_KEY}" \
"${DTRACK_URL}/api/v1/oidc/group" | jq -r ".[] | select(.name==\"${AZURE_GROUP_ID}\") | .uuid")
# 5. Create OIDC Group-to-Team mapping
# IMPORTANT: Use UUID strings directly, NOT objects
curl -X PUT "${DTRACK_URL}/api/v1/oidc/mapping" \
-H "X-Api-Key: ${DTRACK_API_KEY}" \
-H "Content-Type: application/json" \
-d "{\"team\": \"${TEAM_UUID}\", \"group\": \"${OIDC_GROUP_UUID}\"}"
# 6. Assign permissions to Team
curl -X POST "${DTRACK_URL}/api/v1/permission/BOM_UPLOAD/team/${TEAM_UUID}" \
-H "X-Api-Key: ${DTRACK_API_KEY}"IMPORTANT: The /api/v1/oidc/mapping endpoint expects UUID strings, not objects.Using {"team": {"uuid": "..."}, "group": {"uuid": "..."}} will return HTTP 400.Verifying OIDC Group Mappings
Use these commands to verify that OIDC groups are properly mapped to teams:
# Set environment variables
export DTRACK_URL="https://dtrack.example.com"
export DTRACK_API_KEY="your-api-key"
# List all teams with their OIDC group mappings
/usr/bin/curl -s "${DTRACK_URL}/api/v1/team" \
-H "X-Api-Key: ${DTRACK_API_KEY}" | \
/usr/bin/jq '[.[] | select(.mappedOidcGroups | length > 0) | {
team: .name,
oidcGroups: [.mappedOidcGroups[] | .group.name]
}]'
# List all OIDC groups registered in D-Track
/usr/bin/curl -s "${DTRACK_URL}/api/v1/oidc/group" \
-H "X-Api-Key: ${DTRACK_API_KEY}" | \
/usr/bin/jq '.[] | {uuid: .uuid, name: .name}'
# Check specific team's OIDC mappings
TEAM_NAME="Administrators"
/usr/bin/curl -s "${DTRACK_URL}/api/v1/team" \
-H "X-Api-Key: ${DTRACK_API_KEY}" | \
/usr/bin/jq --arg team "$TEAM_NAME" '.[] | select(.name==$team) | {
name: .name,
uuid: .uuid,
oidcGroups: [.mappedOidcGroups[]? | {name: .group.name, uuid: .group.uuid}]
}'Example Output (cafehyna-dev cluster):
[
{"team": "Administrators", "oidcGroups": ["31d6daa5-5cc2-4e5f-9bf5-75ee8e09198c"]},
{"team": "Auditors", "oidcGroups": ["39d0ddb2-8a0d-47c9-bc55-1993aadf7fe8"]},
{"team": "Developers", "oidcGroups": ["7144c503-7934-40fe-99e2-f45f67cb383e"]},
{"team": "Moderators", "oidcGroups": ["d34a2ea3-34c9-4744-a031-4e4a55d72746"]},
{"team": "ReadOnly", "oidcGroups": ["7109c419-028e-4b7b-8246-66517d512c3c"]}
]Note: Use full paths (/usr/bin/curl,/usr/bin/jq) to avoid shell alias issues in automated environments.
Method 2: Manual UI Configuration
1. Create Teams:
- Go to Administration > Access Management > Teams
- Create teams: Administrators, Moderators, Auditors, Developers
- Assign permissions to each team
2. Map Groups:
- Go to Administration > Access Management > OpenID Connect Groups
- Click "Create Group"
- Enter Azure AD Group Object ID (e.g.,
31d6daa5-5cc2-4e5f-9bf5-75ee8e09198c) - Select Team to map to
- Save
Retrieving Azure AD Group Object IDs
# List all Azure AD groups with DependencyTrack in name
az ad group list --filter "startswith(displayName, 'G-Usuarios-DependencyTrack')" \
--query "[].{name:displayName, id:id}" -o table
# Get specific group Object ID
az ad group show --group "G-Usuarios-DependencyTrack-Admin" --query id -o tsvValidating Group Claims in ID Token
1. Decode JWT Token (use jwt.io):
- Login to Dependency-Track
- Capture network request to
/api/v1/oidc/callback - Decode the
id_tokento verifygroupsclaim contains expected UUIDs
2. Azure AD Token Configuration Checklist:
- App Registration has
groupMembershipClaims: SecurityGroup - Optional Claims include
groupsin ID Token - API Permissions:
GroupMember.Read.All(delegated) - Admin consent granted
Environment Variables Reference
| Variable | Description | Example |
|---|---|---|
ALPINE_OIDC_ENABLED | Enable OIDC | true |
ALPINE_OIDC_ISSUER | Azure AD issuer URL | https://login.microsoftonline.com/{tenant}/v2.0 |
ALPINE_OIDC_CLIENT_ID | App Registration Client ID | From Key Vault |
ALPINE_OIDC_CLIENT_SECRET | App Registration Secret | From Key Vault |
ALPINE_OIDC_USERNAME_CLAIM | Claim for username | preferred_username |
ALPINE_OIDC_TEAMS_CLAIM | Claim for groups | groups |
ALPINE_OIDC_USER_PROVISIONING | Auto-create users | true |
ALPINE_OIDC_TEAM_SYNCHRONIZATION | Auto-sync team membership | true |
---
3. CI/CD Integration
Workflow: BOM Upload Pipeline
┌─────────────┐ ┌─────────────┐ ┌──────────────────┐
│ Build │────▶│ Generate │────▶│ Upload to │
│ Project │ │ CycloneDX │ │ Dependency-Track │
└─────────────┘ │ SBOM │ └──────────────────┘
└─────────────┘ │
▼
┌─────────────┐ ┌──────────────────┐
│ Break │◀────│ Evaluate │
│ Build? │ │ Policies │
└─────────────┘ └──────────────────┘Jenkins Pipeline
pipeline {
agent any
environment {
DTRACK_URL = 'https://dtrack.example.com'
DTRACK_API_KEY = credentials('dependency-track-api-key')
}
stages {
stage('Build') {
steps {
sh 'mvn clean package'
}
}
stage('Generate SBOM') {
steps {
sh 'mvn org.cyclonedx:cyclonedx-maven-plugin:makeAggregateBom'
}
}
stage('Upload SBOM') {
steps {
dependencyTrackPublisher(
artifact: 'target/bom.xml',
projectId: env.PROJECT_UUID,
synchronous: true,
failedTotalCritical: 0,
failedTotalHigh: 5
)
}
}
}
}GitHub Actions
name: SBOM Security Scan
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
sbom-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Generate SBOM
uses: CycloneDX/gh-node-module-generatebom@v1
with:
output: ./bom.json
- name: Upload to Dependency-Track
uses: DependencyTrack/gh-upload-sbom@v3
with:
serverHostname: ${{ secrets.DTRACK_URL }}
apiKey: ${{ secrets.DTRACK_API_KEY }}
projectName: ${{ github.repository }}
projectVersion: ${{ github.ref_name }}
bomFilename: ./bom.json
autoCreate: trueOfficial GitHub Action Options (`DependencyTrack/gh-upload-sbom@v3`):
| Parameter | Required | Default | Description |
|---|---|---|---|
serverHostname | Yes | - | Dependency-Track server URL |
apiKey | Yes | - | API key with BOM_UPLOAD permission |
projectName | Yes | - | Name of the project |
projectVersion | Yes | - | Version of the project |
bomFilename | Yes | - | Path to CycloneDX SBOM file |
autoCreate | No | false | Auto-create project if not exists |
parentName | No | - | Parent project name |
parentVersion | No | - | Parent project version |
projectUuid | No | - | Use UUID instead of name/version |
GitLab CI
stages:
- build
- security
generate-sbom:
stage: build
image: cyclonedx/cyclonedx-cli
script:
- cyclonedx-py -r requirements.txt -o bom.json
artifacts:
paths:
- bom.json
upload-sbom:
stage: security
image: curlimages/curl
script:
- |
curl -X PUT "${DTRACK_URL}/api/v1/bom" \
-H "X-Api-Key: ${DTRACK_API_KEY}" \
-H "Content-Type: application/json" \
-d @- << EOF
{
"projectName": "${CI_PROJECT_NAME}",
"projectVersion": "${CI_COMMIT_REF_NAME}",
"autoCreate": true,
"bom": "$(base64 -w0 bom.json)"
}
EOFAzure DevOps Pipeline
trigger:
- main
pool:
vmImage: 'ubuntu-latest'
variables:
- group: dependency-track-credentials
steps:
- task: Maven@4
inputs:
mavenPomFile: 'pom.xml'
goals: 'package org.cyclonedx:cyclonedx-maven-plugin:makeAggregateBom'
- task: Bash@3
displayName: 'Upload SBOM to Dependency-Track'
inputs:
targetType: 'inline'
script: |
BOM_CONTENT=$(base64 -w0 target/bom.xml)
curl -X PUT "$(DTRACK_URL)/api/v1/bom" \
-H "X-Api-Key: $(DTRACK_API_KEY)" \
-H "Content-Type: application/json" \
-d "{\"projectName\":\"$(Build.Repository.Name)\",\"projectVersion\":\"$(Build.SourceBranchName)\",\"autoCreate\":true,\"bom\":\"${BOM_CONTENT}\"}"---
4. REST API Usage
Authentication
Generate API keys in Administration > Access Management > Teams > [Team] > API Keys.
# All API calls require the X-Api-Key header
curl -H "X-Api-Key: YOUR_API_KEY" \
https://dtrack.example.com/api/v1/projectOpenAPI Specification
Access at:
- JSON:
http://localhost:8081/api/openapi.json - YAML:
http://localhost:8081/api/openapi.yaml
Note: Use port 8081 (API server), not 8080 (frontend).
Common API Operations
# List all projects
curl -s -H "X-Api-Key: ${API_KEY}" \
"${DTRACK_URL}/api/v1/project" | jq
# Get project by name and version
curl -s -H "X-Api-Key: ${API_KEY}" \
"${DTRACK_URL}/api/v1/project/lookup?name=myapp&version=1.0.0" | jq
# Upload SBOM (Base64 encoded)
curl -X PUT "${DTRACK_URL}/api/v1/bom" \
-H "X-Api-Key: ${API_KEY}" \
-H "Content-Type: application/json" \
-d "{
\"projectName\": \"my-application\",
\"projectVersion\": \"1.0.0\",
\"autoCreate\": true,
\"bom\": \"$(base64 -w0 bom.json)\"
}"
# Upload SBOM (multipart - no encoding needed)
curl -X POST "${DTRACK_URL}/api/v1/bom" \
-H "X-Api-Key: ${API_KEY}" \
-F "projectName=my-application" \
-F "projectVersion=1.0.0" \
-F "autoCreate=true" \
-F "bom=@bom.json"
# Get vulnerabilities for a project
curl -s -H "X-Api-Key: ${API_KEY}" \
"${DTRACK_URL}/api/v1/vulnerability/project/${PROJECT_UUID}" | jq
# Get policy violations
curl -s -H "X-Api-Key: ${API_KEY}" \
"${DTRACK_URL}/api/v1/violation/project/${PROJECT_UUID}" | jq
# Get component dependencies
curl -s -H "X-Api-Key: ${API_KEY}" \
"${DTRACK_URL}/api/v1/component/project/${PROJECT_UUID}" | jq
# Create project
curl -X PUT "${DTRACK_URL}/api/v1/project" \
-H "X-Api-Key: ${API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"name": "new-project",
"version": "1.0.0",
"description": "Project description",
"tags": [{"name": "production"}]
}'---
5. Policy Configuration
Policy Types
| Type | Purpose | Example Conditions |
|---|---|---|
| License | Control allowed/forbidden licenses | Apache-2.0 allowed, GPL-3.0 forbidden |
| Security | Vulnerability severity thresholds | No Critical, max 5 High |
| Operational | Component allowlists/denylists | Block log4j < 2.17.0 |
Creating Policies via API
# Create a security policy
curl -X PUT "${DTRACK_URL}/api/v1/policy" \
-H "X-Api-Key: ${API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"name": "No Critical Vulnerabilities",
"operator": "ANY",
"violationState": "FAIL",
"policyConditions": [
{
"subject": "SEVERITY",
"operator": "IS",
"value": "CRITICAL"
}
]
}'
# Create a license policy (denylist approach)
curl -X PUT "${DTRACK_URL}/api/v1/policy" \
-H "X-Api-Key: ${API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"name": "No Copyleft Licenses",
"operator": "ANY",
"violationState": "WARN",
"policyConditions": [
{
"subject": "LICENSE_GROUP",
"operator": "IS",
"value": "Copyleft"
}
]
}'
# Create operational policy (block specific component)
curl -X PUT "${DTRACK_URL}/api/v1/policy" \
-H "X-Api-Key: ${API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"name": "Block Vulnerable Log4j",
"operator": "ALL",
"violationState": "FAIL",
"policyConditions": [
{
"subject": "COORDINATES",
"operator": "MATCHES",
"value": "{\"group\":\"org.apache.logging.log4j\",\"name\":\"log4j-core\",\"version\":\"<2.17.0\"}"
}
]
}'Policy Condition Subjects
AGE- Component ageCOORDINATES- Group/Name/VersionCPE- Common Platform EnumerationCWE- Common Weakness EnumerationEPSS- Exploit Prediction Scoring SystemLICENSE- Specific licenseLICENSE_GROUP- License categoryPACKAGE_URL- Package URL (PURL)SEVERITY- Vulnerability severitySWID_TAGID- Software ID tagVERSION- Component versionCOMPONENT_HASH- MD5, SHA, Blake hashes
---
6. Notifications & Integrations
Webhook Configuration
{
"notification": {
"level": "INFORMATIONAL",
"scope": "PORTFOLIO",
"group": "NEW_VULNERABILITY",
"timestamp": "2024-01-15T10:30:00Z",
"subject": {
"component": {
"name": "log4j-core",
"version": "2.14.1"
},
"vulnerability": {
"vulnId": "CVE-2021-44228",
"severity": "CRITICAL"
},
"affectedProjects": [...]
}
}
}Notification Groups
NEW_VULNERABILITY- New vulnerability identifiedNEW_VULNERABLE_DEPENDENCY- New vulnerable component addedANALYSIS_DECISION_CHANGE- Audit decision modifiedPOLICY_VIOLATION- Policy violation detectedBOM_CONSUMED- SBOM processedBOM_PROCESSED- SBOM analysis completeVEX_CONSUMED- VEX document processed
Integration Targets
- Slack/Teams: Direct webhook support
- Email: SMTP configuration
- Jira: Native integration
- DefectDojo: Vulnerability aggregation
- Fortify SSC: AppSec platform
- ThreadFix: Vulnerability management
- Kenna Security: Risk prioritization
---
7. SBOM Generation Tools
By Language/Ecosystem
| Language | Tool | Command |
|---|---|---|
| Java/Maven | cyclonedx-maven-plugin | mvn org.cyclonedx:cyclonedx-maven-plugin:makeAggregateBom |
| Java/Gradle | cyclonedx-gradle-plugin | gradle cyclonedxBom |
| Node.js | @cyclonedx/cyclonedx-npm | npx @cyclonedx/cyclonedx-npm --output-file bom.json |
| Python | cyclonedx-py | cyclonedx-py -r requirements.txt -o bom.json |
| Go | cyclonedx-gomod | cyclonedx-gomod mod -json -output bom.json |
| .NET | CycloneDX | dotnet CycloneDX <project> -o bom.json |
| Rust | cargo-cyclonedx | cargo cyclonedx -f json |
| Container | Syft | syft <image> -o cyclonedx-json > bom.json |
Universal Tools
# Syft - multi-ecosystem SBOM generator
syft packages dir:. -o cyclonedx-json > bom.json
# Trivy - with vulnerability scanning
trivy fs --format cyclonedx --output bom.json .
# cdxgen - comprehensive generator
cdxgen -o bom.json---
8. Best Practices
Deployment
1. Use external PostgreSQL for production (not embedded H2) 2. Allocate sufficient RAM (minimum 4.5GB for API server) 3. Configure persistent storage for /data directory 4. Enable TLS with proper certificates 5. Set up regular backups of database and configuration
Operations
1. Generate SBOMs in CI/CD - automate at build time 2. Enable OSS Index Analyzer - required for PURL-based scanning 3. Mirror NVD locally - improves performance 4. Use synchronous uploads in pipelines for immediate feedback 5. Implement policy gates - fail builds on violations
Security
1. Rotate API keys regularly 2. Use team-scoped keys with minimal permissions 3. Enable audit logging for compliance 4. Configure OIDC/LDAP for enterprise authentication 5. Contractually require SBOMs from vendors
Performance
1. Schedule heavy operations during off-hours 2. Use pagination for large API responses 3. Implement caching for frequently accessed data 4. Monitor analyzer queue depth 5. Scale horizontally with multiple API server replicas
---
9. Troubleshooting
Common Issues
| Issue | Cause | Solution |
|---|---|---|
| No vulnerabilities found | OSS Index not enabled | Enable in Admin > Analyzers, get API token |
| Analyzer not running | 6-hour schedule | Re-upload SBOM or wait for scheduled run |
| Container crashes | Insufficient RAM | Allocate minimum 4.5GB to API server |
| 413 Request Entity Too Large | Nginx body size limit | Add nginx.ingress.kubernetes.io/proxy-body-size: "100m" |
| PKIX path building error | Self-signed certificates | Configure internal CA in settings |
| LDAP sync delays | Async job queue | Wait or manually create accounts |
| Service stops after 1-2 weeks | OS temp cleanup | Set -Djava.io.tmpdir=/path/to/tmpdir |
Debug Commands
# Check API server logs
docker logs -f dependency-track-apiserver
# Verify database connectivity
docker exec -it dependency-track-apiserver \
curl -s http://localhost:8080/api/version
# Check analyzer status
curl -s -H "X-Api-Key: ${API_KEY}" \
"${DTRACK_URL}/api/v1/configProperty?groupName=analyzer"
# Monitor mirror status
curl -s -H "X-Api-Key: ${API_KEY}" \
"${DTRACK_URL}/api/v1/mirror/nvd"
# Health check
curl -s "${DTRACK_URL}/api/version"Performance Tuning
# JVM settings for high-load environments
environment:
JAVA_OPTIONS: >-
-Xms8g
-Xmx16g
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:+ParallelRefProcEnabled---
10. Reference Files
Additional templates and examples are in the references/ directory:
Deployment:
references/deployment/docker-compose-production.yaml- Docker Compose for productionreferences/deployment/helm-values.yaml- Helm values for v4.x (Chart v0.40.0)references/deployment/helm-values-hyades.yaml- Helm values for Hyades v5.x (Chart v0.10.0)references/deployment/kubernetes-manifests/- Raw Kubernetes manifests
CI/CD Integration:
references/cicd/jenkinsfile- Jenkins Pipeline examplereferences/cicd/github-action.yaml- GitHub Actions workflowreferences/cicd/gitlab-ci.yaml- GitLab CI/CD pipelinereferences/cicd/azure-pipeline.yaml- Azure DevOps Pipeline
API & Scripts:
references/api/python-client.py- Python client library examplereferences/api/bash-scripts/- Shell scripts for common operations
Policies:
references/policies/security-policies.json- Security policy templatesreferences/policies/license-policies.json- License policy templatesreferences/policies/operational-policies.json- Operational policy templates
Troubleshooting:
references/troubleshooting.md- Common issues and solutions
---
Related Skills
defectdojo-skill- Vulnerability management integrationargocd-skill- GitOps deployment of Dependency-Trackquality-guardian- Security testing integration
---
Gotchas
- BOM upload without `projectVersion` overwrites the previous BOM: Uploading to the same project name without a distinct version replaces the inventory instead of versioning it. Always pass
projectVersion(commit SHA or semver) and considerparentName/parentVersionfor monorepos. - OSS Index analyzer runs on a 6-hour schedule, not on upload: A fresh SBOM may show zero vulnerabilities for hours because the analyzer hasn't swept it yet. Re-upload the BOM or wait — don't conclude "clean" from the immediate UI state.
- `/api/v1/oidc/mapping` expects UUID strings, not objects: Sending
{"team": {"uuid": "..."}, "group": {"uuid": "..."}}returns 400. Use{"team": "<uuid>", "group": "<uuid>"}. OIDC groups only appear in the UI after a user from that group logs in once. - First-time startup mirrors NVD for 10-30+ minutes: During this window the API works but vulnerability matching is incomplete. Do not benchmark, do not ship pipeline integration until logs show NVD mirror complete — early scans look falsely clean.
- API server needs 4.5GB RAM minimum: Below that the JVM OOMs mid-mirror leaving a half-initialized database. Recovery often requires wiping
/dataand restarting. Set HelmapiServer.resources.requests.memoryaccordingly before scaling. - NGINX Ingress default body size rejects large SBOMs as 413: Monorepo or container BOMs frequently exceed 1MB. Add
nginx.ingress.kubernetes.io/proxy-body-size: "100m"to the ingress annotations.
#!/usr/bin/env bash
#
# manage-projects.sh - Dependency-Track project management utilities
#
# Usage:
# ./manage-projects.sh list
# ./manage-projects.sh get -n "project-name" -v "1.0.0"
# ./manage-projects.sh create -n "project-name" -v "1.0.0" -d "Description"
# ./manage-projects.sh delete -u "project-uuid"
# ./manage-projects.sh export -n "project-name" -v "1.0.0" -o sbom.json
#
# Environment variables:
# DTRACK_URL - Dependency-Track base URL
# DTRACK_API_KEY - API key for authentication
set -euo pipefail
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
usage() {
cat <<EOF
Usage: $0 <command> [options]
Commands:
list List all projects
get Get project details
create Create a new project
delete Delete a project
export Export project SBOM
vulnerabilities List project vulnerabilities
components List project components
Options:
-n, --name Project name
-v, --version Project version
-u, --uuid Project UUID
-d, --description Project description (for create)
-t, --tags Comma-separated tags (for create)
-o, --output Output file (for export)
-f, --format Output format: json, xml (for export, default: json)
--json Output as JSON
-h, --help Show this help
Environment:
DTRACK_URL Dependency-Track base URL
DTRACK_API_KEY API key for authentication
EOF
exit 1
}
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1" >&2; }
check_env() {
if [[ -z "${DTRACK_URL:-}" ]]; then
log_error "DTRACK_URL environment variable not set"
exit 1
fi
if [[ -z "${DTRACK_API_KEY:-}" ]]; then
log_error "DTRACK_API_KEY environment variable not set"
exit 1
fi
}
api_get() {
curl -s -H "X-Api-Key: ${DTRACK_API_KEY}" "${DTRACK_URL}/api/v1$1"
}
api_put() {
curl -s -X PUT -H "X-Api-Key: ${DTRACK_API_KEY}" \
-H "Content-Type: application/json" \
"${DTRACK_URL}/api/v1$1" -d "$2"
}
api_delete() {
curl -s -X DELETE -H "X-Api-Key: ${DTRACK_API_KEY}" "${DTRACK_URL}/api/v1$1"
}
cmd_list() {
local json_output="false"
while [[ $# -gt 0 ]]; do
case $1 in
--json) json_output="true"; shift ;;
*) shift ;;
esac
done
PROJECTS=$(api_get "/project?pageSize=1000")
if [[ "$json_output" == "true" ]]; then
echo "$PROJECTS" | jq .
else
echo ""
echo "Projects:"
echo "=========================================="
echo "$PROJECTS" | jq -r '.[] | "\(.name):\(.version) (\(.uuid))"' | sort
echo ""
TOTAL=$(echo "$PROJECTS" | jq 'length')
echo "Total: $TOTAL projects"
fi
}
cmd_get() {
local project_name="" project_version="" project_uuid="" json_output="false"
while [[ $# -gt 0 ]]; do
case $1 in
-n|--name) project_name="$2"; shift 2 ;;
-v|--version) project_version="$2"; shift 2 ;;
-u|--uuid) project_uuid="$2"; shift 2 ;;
--json) json_output="true"; shift ;;
*) shift ;;
esac
done
if [[ -n "$project_uuid" ]]; then
PROJECT=$(api_get "/project/${project_uuid}")
elif [[ -n "$project_name" && -n "$project_version" ]]; then
PROJECT=$(api_get "/project/lookup?name=${project_name}&version=${project_version}")
else
log_error "Either --uuid or --name and --version required"
exit 1
fi
if [[ "$json_output" == "true" ]]; then
echo "$PROJECT" | jq .
else
echo ""
echo "Project Details:"
echo "=========================================="
echo "Name: $(echo "$PROJECT" | jq -r '.name')"
echo "Version: $(echo "$PROJECT" | jq -r '.version')"
echo "UUID: $(echo "$PROJECT" | jq -r '.uuid')"
echo "Description: $(echo "$PROJECT" | jq -r '.description // "N/A"')"
echo "Active: $(echo "$PROJECT" | jq -r '.active')"
echo "Tags: $(echo "$PROJECT" | jq -r '[.tags[]?.name] | join(", ") // "None"')"
echo "Last BOM: $(echo "$PROJECT" | jq -r '.lastBomImportFormat // "N/A"')"
echo "Created: $(echo "$PROJECT" | jq -r '.created // "N/A"')"
fi
}
cmd_create() {
local project_name="" project_version="" description="" tags=""
while [[ $# -gt 0 ]]; do
case $1 in
-n|--name) project_name="$2"; shift 2 ;;
-v|--version) project_version="$2"; shift 2 ;;
-d|--description) description="$2"; shift 2 ;;
-t|--tags) tags="$2"; shift 2 ;;
*) shift ;;
esac
done
if [[ -z "$project_name" || -z "$project_version" ]]; then
log_error "--name and --version required"
exit 1
fi
# Build tags JSON array
local tags_json="[]"
if [[ -n "$tags" ]]; then
tags_json=$(echo "$tags" | tr ',' '\n' | jq -R . | jq -s 'map({name: .})')
fi
local payload
payload=$(jq -n \
--arg name "$project_name" \
--arg version "$project_version" \
--arg description "$description" \
--argjson tags "$tags_json" \
'{name: $name, version: $version, description: $description, tags: $tags, active: true}')
log_info "Creating project: ${project_name}:${project_version}"
RESULT=$(api_put "/project" "$payload")
uuid=$(echo "$RESULT" | jq -r '.uuid')
if [[ "$uuid" != "null" && -n "$uuid" ]]; then
log_info "Project created successfully!"
echo "UUID: $uuid"
else
log_error "Failed to create project"
echo "$RESULT"
exit 1
fi
}
cmd_delete() {
local project_uuid=""
while [[ $# -gt 0 ]]; do
case $1 in
-u|--uuid) project_uuid="$2"; shift 2 ;;
*) shift ;;
esac
done
if [[ -z "$project_uuid" ]]; then
log_error "--uuid required"
exit 1
fi
log_warn "Deleting project: $project_uuid"
read -p "Are you sure? (y/N) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
api_delete "/project/${project_uuid}"
log_info "Project deleted"
else
log_info "Cancelled"
fi
}
cmd_export() {
local project_name="" project_version="" output_file="" format="json"
while [[ $# -gt 0 ]]; do
case $1 in
-n|--name) project_name="$2"; shift 2 ;;
-v|--version) project_version="$2"; shift 2 ;;
-o|--output) output_file="$2"; shift 2 ;;
-f|--format) format="$2"; shift 2 ;;
*) shift ;;
esac
done
if [[ -z "$project_name" || -z "$project_version" ]]; then
log_error "--name and --version required"
exit 1
fi
# Get project UUID
PROJECT=$(api_get "/project/lookup?name=${project_name}&version=${project_version}")
project_uuid=$(echo "$PROJECT" | jq -r '.uuid')
if [[ "$project_uuid" == "null" || -z "$project_uuid" ]]; then
log_error "Project not found"
exit 1
fi
log_info "Exporting SBOM for ${project_name}:${project_version}"
SBOM=$(curl -s -H "X-Api-Key: ${DTRACK_API_KEY}" \
"${DTRACK_URL}/api/v1/bom/cyclonedx/project/${project_uuid}?format=${format}")
if [[ -n "$output_file" ]]; then
echo "$SBOM" > "$output_file"
log_info "SBOM exported to: $output_file"
else
echo "$SBOM"
fi
}
cmd_vulnerabilities() {
local project_name="" project_version="" json_output="false"
while [[ $# -gt 0 ]]; do
case $1 in
-n|--name) project_name="$2"; shift 2 ;;
-v|--version) project_version="$2"; shift 2 ;;
--json) json_output="true"; shift ;;
*) shift ;;
esac
done
PROJECT=$(api_get "/project/lookup?name=${project_name}&version=${project_version}")
project_uuid=$(echo "$PROJECT" | jq -r '.uuid')
VULNS=$(api_get "/vulnerability/project/${project_uuid}")
if [[ "$json_output" == "true" ]]; then
echo "$VULNS" | jq .
else
echo ""
echo "Vulnerabilities for ${project_name}:${project_version}:"
echo "=========================================="
echo "$VULNS" | jq -r '.[] | "\(.vulnerability.severity): \(.vulnerability.vulnId) - \(.component.name):\(.component.version)"' | sort
echo ""
echo "Summary:"
echo " Critical: $(echo "$VULNS" | jq '[.[] | select(.vulnerability.severity == "CRITICAL")] | length')"
echo " High: $(echo "$VULNS" | jq '[.[] | select(.vulnerability.severity == "HIGH")] | length')"
echo " Medium: $(echo "$VULNS" | jq '[.[] | select(.vulnerability.severity == "MEDIUM")] | length')"
echo " Low: $(echo "$VULNS" | jq '[.[] | select(.vulnerability.severity == "LOW")] | length')"
fi
}
cmd_components() {
local project_name="" project_version="" json_output="false"
while [[ $# -gt 0 ]]; do
case $1 in
-n|--name) project_name="$2"; shift 2 ;;
-v|--version) project_version="$2"; shift 2 ;;
--json) json_output="true"; shift ;;
*) shift ;;
esac
done
PROJECT=$(api_get "/project/lookup?name=${project_name}&version=${project_version}")
project_uuid=$(echo "$PROJECT" | jq -r '.uuid')
COMPONENTS=$(api_get "/component/project/${project_uuid}?pageSize=1000")
if [[ "$json_output" == "true" ]]; then
echo "$COMPONENTS" | jq .
else
echo ""
echo "Components for ${project_name}:${project_version}:"
echo "=========================================="
echo "$COMPONENTS" | jq -r '.[] | "\(.name):\(.version) [\(.purl // "no-purl")]"' | sort
echo ""
TOTAL=$(echo "$COMPONENTS" | jq 'length')
echo "Total: $TOTAL components"
fi
}
# Main
check_env
COMMAND="${1:-}"
shift || true
case "$COMMAND" in
list)
cmd_list "$@"
;;
get)
cmd_get "$@"
;;
create)
cmd_create "$@"
;;
delete)
cmd_delete "$@"
;;
export)
cmd_export "$@"
;;
vulnerabilities|vulns)
cmd_vulnerabilities "$@"
;;
components)
cmd_components "$@"
;;
-h|--help|"")
usage
;;
*)
log_error "Unknown command: $COMMAND"
usage
;;
esac
#!/usr/bin/env bash
#
# security-gate.sh - Security gate check for CI/CD pipelines
#
# Usage:
# ./security-gate.sh -n "project-name" -v "1.0.0"
# ./security-gate.sh -n "project-name" -v "1.0.0" --max-critical 0 --max-high 5
#
# Environment variables:
# DTRACK_URL - Dependency-Track base URL
# DTRACK_API_KEY - API key for authentication
#
# Options:
# -n, --name Project name (required)
# -v, --version Project version (required)
# --max-critical Maximum allowed critical vulnerabilities (default: 0)
# --max-high Maximum allowed high vulnerabilities (default: 0)
# --max-medium Maximum allowed medium vulnerabilities (default: unlimited)
# --max-low Maximum allowed low vulnerabilities (default: unlimited)
# --max-policy-fail Maximum allowed FAIL policy violations (default: 0)
# --fail-on-warn Fail on policy WARN violations
# -o, --output Output format: text, json, github (default: text)
# -h, --help Show this help message
set -euo pipefail
# Default thresholds
MAX_CRITICAL=0
MAX_HIGH=0
MAX_MEDIUM=999999
MAX_LOW=999999
MAX_POLICY_FAIL=0
FAIL_ON_WARN="false"
OUTPUT_FORMAT="text"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
usage() {
grep '^#' "$0" | grep -v '#!/' | cut -c3-
exit 1
}
log_info() {
[[ "$OUTPUT_FORMAT" == "text" ]] && echo -e "${GREEN}[INFO]${NC} $1"
}
log_warn() {
[[ "$OUTPUT_FORMAT" == "text" ]] && echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
[[ "$OUTPUT_FORMAT" == "text" ]] && echo -e "${RED}[ERROR]${NC} $1" >&2
}
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
-n|--name)
PROJECT_NAME="$2"
shift 2
;;
-v|--version)
PROJECT_VERSION="$2"
shift 2
;;
--max-critical)
MAX_CRITICAL="$2"
shift 2
;;
--max-high)
MAX_HIGH="$2"
shift 2
;;
--max-medium)
MAX_MEDIUM="$2"
shift 2
;;
--max-low)
MAX_LOW="$2"
shift 2
;;
--max-policy-fail)
MAX_POLICY_FAIL="$2"
shift 2
;;
--fail-on-warn)
FAIL_ON_WARN="true"
shift
;;
-o|--output)
OUTPUT_FORMAT="$2"
shift 2
;;
-h|--help)
usage
;;
*)
log_error "Unknown option: $1"
usage
;;
esac
done
# Validate environment
if [[ -z "${DTRACK_URL:-}" ]]; then
log_error "DTRACK_URL environment variable not set"
exit 1
fi
if [[ -z "${DTRACK_API_KEY:-}" ]]; then
log_error "DTRACK_API_KEY environment variable not set"
exit 1
fi
if [[ -z "${PROJECT_NAME:-}" || -z "${PROJECT_VERSION:-}" ]]; then
log_error "Project name (-n) and version (-v) are required"
usage
fi
# Get project UUID
log_info "Looking up project: ${PROJECT_NAME}:${PROJECT_VERSION}"
PROJECT=$(curl -s -H "X-Api-Key: ${DTRACK_API_KEY}" \
"${DTRACK_URL}/api/v1/project/lookup?name=${PROJECT_NAME}&version=${PROJECT_VERSION}")
PROJECT_UUID=$(echo "$PROJECT" | jq -r '.uuid // empty')
if [[ -z "$PROJECT_UUID" ]]; then
log_error "Project not found: ${PROJECT_NAME}:${PROJECT_VERSION}"
exit 1
fi
log_info "Project UUID: $PROJECT_UUID"
# Get vulnerability metrics
METRICS=$(curl -s -H "X-Api-Key: ${DTRACK_API_KEY}" \
"${DTRACK_URL}/api/v1/metrics/project/${PROJECT_UUID}/current")
CRITICAL=$(echo "$METRICS" | jq -r '.critical // 0')
HIGH=$(echo "$METRICS" | jq -r '.high // 0')
MEDIUM=$(echo "$METRICS" | jq -r '.medium // 0')
LOW=$(echo "$METRICS" | jq -r '.low // 0')
UNASSIGNED=$(echo "$METRICS" | jq -r '.unassigned // 0')
# Get policy violations
VIOLATIONS=$(curl -s -H "X-Api-Key: ${DTRACK_API_KEY}" \
"${DTRACK_URL}/api/v1/violation/project/${PROJECT_UUID}")
POLICY_FAIL=$(echo "$VIOLATIONS" | jq '[.[] | select(.policyViolation.violationState == "FAIL")] | length')
POLICY_WARN=$(echo "$VIOLATIONS" | jq '[.[] | select(.policyViolation.violationState == "WARN")] | length')
POLICY_INFO=$(echo "$VIOLATIONS" | jq '[.[] | select(.policyViolation.violationState == "INFO")] | length')
# Determine result
RESULT="PASSED"
FAILURES=()
if [[ $CRITICAL -gt $MAX_CRITICAL ]]; then
FAILURES+=("Critical: $CRITICAL > $MAX_CRITICAL")
RESULT="FAILED"
fi
if [[ $HIGH -gt $MAX_HIGH ]]; then
FAILURES+=("High: $HIGH > $MAX_HIGH")
RESULT="FAILED"
fi
if [[ $MEDIUM -gt $MAX_MEDIUM ]]; then
FAILURES+=("Medium: $MEDIUM > $MAX_MEDIUM")
RESULT="FAILED"
fi
if [[ $LOW -gt $MAX_LOW ]]; then
FAILURES+=("Low: $LOW > $MAX_LOW")
RESULT="FAILED"
fi
if [[ $POLICY_FAIL -gt $MAX_POLICY_FAIL ]]; then
FAILURES+=("Policy FAIL: $POLICY_FAIL > $MAX_POLICY_FAIL")
RESULT="FAILED"
fi
if [[ "$FAIL_ON_WARN" == "true" && $POLICY_WARN -gt 0 ]]; then
FAILURES+=("Policy WARN: $POLICY_WARN (--fail-on-warn enabled)")
RESULT="FAILED"
fi
# Output results
case "$OUTPUT_FORMAT" in
json)
cat <<EOF
{
"result": "$RESULT",
"project": {
"name": "$PROJECT_NAME",
"version": "$PROJECT_VERSION",
"uuid": "$PROJECT_UUID"
},
"vulnerabilities": {
"critical": $CRITICAL,
"high": $HIGH,
"medium": $MEDIUM,
"low": $LOW,
"unassigned": $UNASSIGNED
},
"thresholds": {
"critical": $MAX_CRITICAL,
"high": $MAX_HIGH,
"medium": $MAX_MEDIUM,
"low": $MAX_LOW
},
"policyViolations": {
"fail": $POLICY_FAIL,
"warn": $POLICY_WARN,
"info": $POLICY_INFO
},
"failures": $(printf '%s\n' "${FAILURES[@]:-}" | jq -R . | jq -s .)
}
EOF
;;
github)
# GitHub Actions output format
echo "result=$RESULT" >> "${GITHUB_OUTPUT:-/dev/stdout}"
echo "critical=$CRITICAL" >> "${GITHUB_OUTPUT:-/dev/stdout}"
echo "high=$HIGH" >> "${GITHUB_OUTPUT:-/dev/stdout}"
echo "medium=$MEDIUM" >> "${GITHUB_OUTPUT:-/dev/stdout}"
echo "low=$LOW" >> "${GITHUB_OUTPUT:-/dev/stdout}"
echo "policy_fail=$POLICY_FAIL" >> "${GITHUB_OUTPUT:-/dev/stdout}"
echo "policy_warn=$POLICY_WARN" >> "${GITHUB_OUTPUT:-/dev/stdout}"
# Summary
{
echo "## Security Gate: $RESULT"
echo ""
echo "| Severity | Count | Threshold |"
echo "|----------|-------|-----------|"
echo "| Critical | $CRITICAL | $MAX_CRITICAL |"
echo "| High | $HIGH | $MAX_HIGH |"
echo "| Medium | $MEDIUM | $MAX_MEDIUM |"
echo "| Low | $LOW | $MAX_LOW |"
echo ""
echo "### Policy Violations"
echo "- FAIL: $POLICY_FAIL"
echo "- WARN: $POLICY_WARN"
echo "- INFO: $POLICY_INFO"
} >> "${GITHUB_STEP_SUMMARY:-/dev/stdout}"
;;
text|*)
echo ""
echo "========================================"
echo "Security Gate: $RESULT"
echo "========================================"
echo "Project: ${PROJECT_NAME}:${PROJECT_VERSION}"
echo ""
echo "Vulnerabilities:"
echo " Critical: $CRITICAL (max: $MAX_CRITICAL)"
echo " High: $HIGH (max: $MAX_HIGH)"
echo " Medium: $MEDIUM (max: $MAX_MEDIUM)"
echo " Low: $LOW (max: $MAX_LOW)"
echo " Unassigned: $UNASSIGNED"
echo ""
echo "Policy Violations:"
echo " FAIL: $POLICY_FAIL (max: $MAX_POLICY_FAIL)"
echo " WARN: $POLICY_WARN"
echo " INFO: $POLICY_INFO"
echo ""
if [[ ${#FAILURES[@]} -gt 0 ]]; then
echo "Failures:"
for failure in "${FAILURES[@]}"; do
echo " - $failure"
done
fi
echo "========================================"
;;
esac
# Exit with appropriate code
if [[ "$RESULT" == "PASSED" ]]; then
exit 0
else
exit 1
fi
#!/usr/bin/env bash
#
# upload-sbom.sh - Upload CycloneDX SBOM to Dependency-Track
#
# Usage:
# ./upload-sbom.sh -f bom.json -n "project-name" -v "1.0.0"
# ./upload-sbom.sh -f bom.json -u "project-uuid"
#
# Environment variables:
# DTRACK_URL - Dependency-Track base URL
# DTRACK_API_KEY - API key for authentication
#
# Options:
# -f, --file BOM file path (required)
# -n, --name Project name (required if no UUID)
# -v, --version Project version (required if no UUID)
# -u, --uuid Project UUID (alternative to name/version)
# -a, --auto Auto-create project if not exists (default: true)
# -w, --wait Wait for processing to complete
# -t, --timeout Timeout in seconds for waiting (default: 300)
# -h, --help Show this help message
set -euo pipefail
# Default values
AUTO_CREATE="true"
WAIT="false"
TIMEOUT=300
POLL_INTERVAL=5
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
usage() {
grep '^#' "$0" | grep -v '#!/' | cut -c3-
exit 1
}
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1" >&2
}
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
-f|--file)
BOM_FILE="$2"
shift 2
;;
-n|--name)
PROJECT_NAME="$2"
shift 2
;;
-v|--version)
PROJECT_VERSION="$2"
shift 2
;;
-u|--uuid)
PROJECT_UUID="$2"
shift 2
;;
-a|--auto)
AUTO_CREATE="$2"
shift 2
;;
-w|--wait)
WAIT="true"
shift
;;
-t|--timeout)
TIMEOUT="$2"
shift 2
;;
-h|--help)
usage
;;
*)
log_error "Unknown option: $1"
usage
;;
esac
done
# Validate environment
if [[ -z "${DTRACK_URL:-}" ]]; then
log_error "DTRACK_URL environment variable not set"
exit 1
fi
if [[ -z "${DTRACK_API_KEY:-}" ]]; then
log_error "DTRACK_API_KEY environment variable not set"
exit 1
fi
# Validate arguments
if [[ -z "${BOM_FILE:-}" ]]; then
log_error "BOM file is required (-f/--file)"
usage
fi
if [[ ! -f "$BOM_FILE" ]]; then
log_error "BOM file not found: $BOM_FILE"
exit 1
fi
if [[ -z "${PROJECT_UUID:-}" ]] && [[ -z "${PROJECT_NAME:-}" || -z "${PROJECT_VERSION:-}" ]]; then
log_error "Either project UUID (-u) or project name (-n) and version (-v) are required"
usage
fi
# Base64 encode the BOM
log_info "Encoding BOM file: $BOM_FILE"
BOM_CONTENT=$(base64 -w0 "$BOM_FILE")
# Build JSON payload
if [[ -n "${PROJECT_UUID:-}" ]]; then
PAYLOAD=$(cat <<EOF
{
"project": "${PROJECT_UUID}",
"autoCreate": ${AUTO_CREATE},
"bom": "${BOM_CONTENT}"
}
EOF
)
else
PAYLOAD=$(cat <<EOF
{
"projectName": "${PROJECT_NAME}",
"projectVersion": "${PROJECT_VERSION}",
"autoCreate": ${AUTO_CREATE},
"bom": "${BOM_CONTENT}"
}
EOF
)
fi
# Upload SBOM
log_info "Uploading SBOM to Dependency-Track..."
RESPONSE=$(curl -s -w "\n%{http_code}" -X PUT "${DTRACK_URL}/api/v1/bom" \
-H "X-Api-Key: ${DTRACK_API_KEY}" \
-H "Content-Type: application/json" \
-d "${PAYLOAD}")
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
BODY=$(echo "$RESPONSE" | head -n-1)
if [[ "$HTTP_CODE" -ne 200 ]]; then
log_error "Failed to upload SBOM. HTTP $HTTP_CODE"
echo "$BODY"
exit 1
fi
TOKEN=$(echo "$BODY" | jq -r '.token')
log_info "SBOM uploaded successfully. Token: $TOKEN"
# Wait for processing if requested
if [[ "$WAIT" == "true" ]]; then
log_info "Waiting for BOM processing to complete (timeout: ${TIMEOUT}s)..."
START_TIME=$(date +%s)
while true; do
ELAPSED=$(($(date +%s) - START_TIME))
if [[ $ELAPSED -ge $TIMEOUT ]]; then
log_error "Timeout waiting for BOM processing"
exit 1
fi
STATUS=$(curl -s -H "X-Api-Key: ${DTRACK_API_KEY}" \
"${DTRACK_URL}/api/v1/bom/token/${TOKEN}")
PROCESSING=$(echo "$STATUS" | jq -r '.processing')
if [[ "$PROCESSING" == "false" ]]; then
log_info "BOM processing complete!"
break
fi
echo -n "."
sleep $POLL_INTERVAL
done
fi
# Output the token for use in other scripts
echo "$TOKEN"
#!/usr/bin/env python3
"""
Dependency-Track Python Client
A comprehensive Python client for interacting with the Dependency-Track REST API.
Supports all common operations: projects, SBOMs, vulnerabilities, policies.
Requirements:
pip install requests python-dotenv
Usage:
from python_client import DependencyTrackClient
client = DependencyTrackClient(
base_url="https://dtrack.example.com",
api_key="your-api-key"
)
# List all projects
projects = client.get_projects()
# Upload SBOM
client.upload_sbom("bom.json", project_name="my-app", project_version="1.0.0")
"""
import os
import json
import base64
import time
from pathlib import Path
from typing import Optional, List, Dict, Any
from dataclasses import dataclass
from datetime import datetime
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
@dataclass
class VulnerabilitySummary:
"""Summary of vulnerabilities for a project."""
critical: int = 0
high: int = 0
medium: int = 0
low: int = 0
unassigned: int = 0
@property
def total(self) -> int:
return self.critical + self.high + self.medium + self.low + self.unassigned
def exceeds_threshold(
self,
max_critical: int = 0,
max_high: int = 0,
max_medium: int = 0,
max_low: int = 0
) -> bool:
"""Check if vulnerabilities exceed specified thresholds."""
return (
self.critical > max_critical or
self.high > max_high or
self.medium > max_medium or
self.low > max_low
)
@dataclass
class PolicyViolationSummary:
"""Summary of policy violations for a project."""
fail: int = 0
warn: int = 0
info: int = 0
@property
def total(self) -> int:
return self.fail + self.warn + self.info
class DependencyTrackClient:
"""Client for Dependency-Track REST API."""
def __init__(
self,
base_url: Optional[str] = None,
api_key: Optional[str] = None,
timeout: int = 30,
verify_ssl: bool = True
):
"""
Initialize the Dependency-Track client.
Args:
base_url: Base URL of Dependency-Track instance (or DTRACK_URL env var)
api_key: API key for authentication (or DTRACK_API_KEY env var)
timeout: Request timeout in seconds
verify_ssl: Whether to verify SSL certificates
"""
self.base_url = (base_url or os.getenv("DTRACK_URL", "")).rstrip("/")
self.api_key = api_key or os.getenv("DTRACK_API_KEY", "")
if not self.base_url:
raise ValueError("base_url or DTRACK_URL environment variable required")
if not self.api_key:
raise ValueError("api_key or DTRACK_API_KEY environment variable required")
self.timeout = timeout
self.verify_ssl = verify_ssl
# Configure session with retries
self.session = requests.Session()
retry_strategy = Retry(
total=3,
backoff_factor=1,
status_forcelist=[429, 500, 502, 503, 504]
)
adapter = HTTPAdapter(max_retries=retry_strategy)
self.session.mount("http://", adapter)
self.session.mount("https://", adapter)
self.session.headers.update({
"X-Api-Key": self.api_key,
"Content-Type": "application/json",
"Accept": "application/json"
})
def _request(
self,
method: str,
endpoint: str,
**kwargs
) -> requests.Response:
"""Make an API request."""
url = f"{self.base_url}/api/v1{endpoint}"
response = self.session.request(
method,
url,
timeout=self.timeout,
verify=self.verify_ssl,
**kwargs
)
response.raise_for_status()
return response
def _get(self, endpoint: str, **kwargs) -> Any:
"""Make a GET request and return JSON."""
return self._request("GET", endpoint, **kwargs).json()
def _put(self, endpoint: str, **kwargs) -> Any:
"""Make a PUT request and return JSON."""
return self._request("PUT", endpoint, **kwargs).json()
def _post(self, endpoint: str, **kwargs) -> Any:
"""Make a POST request and return JSON."""
return self._request("POST", endpoint, **kwargs).json()
def _delete(self, endpoint: str, **kwargs) -> None:
"""Make a DELETE request."""
self._request("DELETE", endpoint, **kwargs)
# =========================================================================
# Version / Health
# =========================================================================
def get_version(self) -> Dict[str, str]:
"""Get Dependency-Track version information."""
return self._get("/version")
def health_check(self) -> bool:
"""Check if Dependency-Track is healthy."""
try:
self.get_version()
return True
except Exception:
return False
# =========================================================================
# Projects
# =========================================================================
def get_projects(
self,
name: Optional[str] = None,
exclude_inactive: bool = False,
only_root: bool = False,
page_size: int = 100,
page_number: int = 1
) -> List[Dict[str, Any]]:
"""
Get all projects or filter by name.
Args:
name: Filter projects by name
exclude_inactive: Exclude inactive projects
only_root: Only return root projects (no children)
page_size: Number of results per page
page_number: Page number (1-indexed)
"""
params = {
"pageSize": page_size,
"pageNumber": page_number,
"excludeInactive": exclude_inactive,
"onlyRoot": only_root
}
if name:
params["name"] = name
return self._get("/project", params=params)
def get_project(self, uuid: str) -> Dict[str, Any]:
"""Get a project by UUID."""
return self._get(f"/project/{uuid}")
def get_project_by_name_version(
self,
name: str,
version: str
) -> Optional[Dict[str, Any]]:
"""Look up a project by name and version."""
try:
return self._get("/project/lookup", params={"name": name, "version": version})
except requests.HTTPError as e:
if e.response.status_code == 404:
return None
raise
def create_project(
self,
name: str,
version: str,
description: Optional[str] = None,
tags: Optional[List[str]] = None,
parent_uuid: Optional[str] = None,
active: bool = True
) -> Dict[str, Any]:
"""
Create a new project.
Args:
name: Project name
version: Project version
description: Project description
tags: List of tag names
parent_uuid: UUID of parent project
active: Whether project is active
"""
data = {
"name": name,
"version": version,
"active": active
}
if description:
data["description"] = description
if tags:
data["tags"] = [{"name": tag} for tag in tags]
if parent_uuid:
data["parent"] = {"uuid": parent_uuid}
return self._put("/project", json=data)
def delete_project(self, uuid: str) -> None:
"""Delete a project by UUID."""
self._delete(f"/project/{uuid}")
def get_project_children(self, uuid: str) -> List[Dict[str, Any]]:
"""Get child projects of a parent project."""
return self._get(f"/project/{uuid}/children")
# =========================================================================
# SBOM / BOM
# =========================================================================
def upload_sbom(
self,
bom_path: str,
project_uuid: Optional[str] = None,
project_name: Optional[str] = None,
project_version: Optional[str] = None,
auto_create: bool = True,
parent_uuid: Optional[str] = None
) -> Dict[str, Any]:
"""
Upload a CycloneDX SBOM to Dependency-Track.
Args:
bom_path: Path to BOM file (JSON or XML)
project_uuid: UUID of existing project (if known)
project_name: Project name (required if no UUID)
project_version: Project version (required if no UUID)
auto_create: Create project if it doesn't exist
parent_uuid: UUID of parent project for auto-created projects
Returns:
Upload response containing token for tracking
"""
bom_path = Path(bom_path)
if not bom_path.exists():
raise FileNotFoundError(f"BOM file not found: {bom_path}")
with open(bom_path, "rb") as f:
bom_content = base64.b64encode(f.read()).decode("utf-8")
data = {
"bom": bom_content,
"autoCreate": auto_create
}
if project_uuid:
data["project"] = project_uuid
else:
if not project_name or not project_version:
raise ValueError("project_name and project_version required when project_uuid not provided")
data["projectName"] = project_name
data["projectVersion"] = project_version
if parent_uuid:
data["parentUUID"] = parent_uuid
return self._put("/bom", json=data)
def upload_sbom_multipart(
self,
bom_path: str,
project_name: str,
project_version: str,
auto_create: bool = True
) -> Dict[str, Any]:
"""
Upload SBOM using multipart form data (no base64 encoding needed).
Useful for very large SBOMs.
"""
bom_path = Path(bom_path)
if not bom_path.exists():
raise FileNotFoundError(f"BOM file not found: {bom_path}")
# Remove Content-Type header for multipart
headers = {"X-Api-Key": self.api_key}
with open(bom_path, "rb") as f:
files = {"bom": (bom_path.name, f)}
data = {
"projectName": project_name,
"projectVersion": project_version,
"autoCreate": str(auto_create).lower()
}
response = self.session.post(
f"{self.base_url}/api/v1/bom",
headers=headers,
files=files,
data=data,
timeout=self.timeout,
verify=self.verify_ssl
)
response.raise_for_status()
return response.json()
def is_bom_processing_complete(self, token: str) -> bool:
"""Check if BOM processing is complete."""
result = self._get(f"/bom/token/{token}")
return result.get("processing", True) is False
def wait_for_bom_processing(
self,
token: str,
timeout_seconds: int = 300,
poll_interval: int = 5
) -> bool:
"""
Wait for BOM processing to complete.
Args:
token: Upload token from upload_sbom response
timeout_seconds: Maximum time to wait
poll_interval: Seconds between status checks
Returns:
True if processing completed, False if timeout
"""
start_time = time.time()
while time.time() - start_time < timeout_seconds:
if self.is_bom_processing_complete(token):
return True
time.sleep(poll_interval)
return False
def export_sbom(
self,
project_uuid: str,
format: str = "json",
variant: str = "inventory"
) -> str:
"""
Export a project's SBOM.
Args:
project_uuid: Project UUID
format: Export format (json or xml)
variant: BOM variant (inventory, withVulnerabilities, vdr, vex)
Returns:
SBOM content as string
"""
params = {"format": format, "variant": variant}
response = self._request("GET", f"/bom/cyclonedx/project/{project_uuid}", params=params)
return response.text
# =========================================================================
# Vulnerabilities
# =========================================================================
def get_project_vulnerabilities(
self,
project_uuid: str,
suppressed: bool = False
) -> List[Dict[str, Any]]:
"""Get vulnerabilities for a project."""
params = {"suppressed": suppressed}
return self._get(f"/vulnerability/project/{project_uuid}", params=params)
def get_vulnerability(
self,
source: str,
vuln_id: str
) -> Dict[str, Any]:
"""Get vulnerability details by source and ID."""
return self._get(f"/vulnerability/source/{source}/vuln/{vuln_id}")
def get_project_metrics(self, project_uuid: str) -> Dict[str, Any]:
"""Get current vulnerability metrics for a project."""
return self._get(f"/metrics/project/{project_uuid}/current")
def get_vulnerability_summary(self, project_uuid: str) -> VulnerabilitySummary:
"""Get vulnerability summary for a project."""
metrics = self.get_project_metrics(project_uuid)
return VulnerabilitySummary(
critical=metrics.get("critical", 0),
high=metrics.get("high", 0),
medium=metrics.get("medium", 0),
low=metrics.get("low", 0),
unassigned=metrics.get("unassigned", 0)
)
# =========================================================================
# Components
# =========================================================================
def get_project_components(
self,
project_uuid: str,
page_size: int = 100,
page_number: int = 1
) -> List[Dict[str, Any]]:
"""Get components for a project."""
params = {"pageSize": page_size, "pageNumber": page_number}
return self._get(f"/component/project/{project_uuid}", params=params)
def get_component(self, uuid: str) -> Dict[str, Any]:
"""Get component details by UUID."""
return self._get(f"/component/{uuid}")
# =========================================================================
# Policies
# =========================================================================
def get_policies(self) -> List[Dict[str, Any]]:
"""Get all policies."""
return self._get("/policy")
def get_policy(self, uuid: str) -> Dict[str, Any]:
"""Get a policy by UUID."""
return self._get(f"/policy/{uuid}")
def create_policy(
self,
name: str,
operator: str = "ANY",
violation_state: str = "FAIL",
conditions: Optional[List[Dict[str, str]]] = None
) -> Dict[str, Any]:
"""
Create a new policy.
Args:
name: Policy name
operator: ALL or ANY
violation_state: FAIL, WARN, or INFO
conditions: List of policy conditions
"""
data = {
"name": name,
"operator": operator,
"violationState": violation_state
}
if conditions:
data["policyConditions"] = conditions
return self._put("/policy", json=data)
def delete_policy(self, uuid: str) -> None:
"""Delete a policy by UUID."""
self._delete(f"/policy/{uuid}")
# =========================================================================
# Policy Violations
# =========================================================================
def get_project_policy_violations(
self,
project_uuid: str,
suppressed: bool = False
) -> List[Dict[str, Any]]:
"""Get policy violations for a project."""
params = {"suppressed": suppressed}
return self._get(f"/violation/project/{project_uuid}", params=params)
def get_policy_violation_summary(
self,
project_uuid: str
) -> PolicyViolationSummary:
"""Get policy violation summary for a project."""
violations = self.get_project_policy_violations(project_uuid)
summary = PolicyViolationSummary()
for v in violations:
state = v.get("policyViolation", {}).get("violationState", "")
if state == "FAIL":
summary.fail += 1
elif state == "WARN":
summary.warn += 1
elif state == "INFO":
summary.info += 1
return summary
# =========================================================================
# Findings (Analysis/Audit)
# =========================================================================
def get_project_findings(
self,
project_uuid: str,
suppressed: bool = False
) -> List[Dict[str, Any]]:
"""Get findings (vulnerabilities with audit info) for a project."""
params = {"suppressed": suppressed}
return self._get(f"/finding/project/{project_uuid}", params=params)
def update_analysis(
self,
project_uuid: str,
component_uuid: str,
vulnerability_uuid: str,
state: str,
comment: Optional[str] = None,
suppressed: bool = False
) -> Dict[str, Any]:
"""
Update vulnerability analysis/audit state.
Args:
project_uuid: Project UUID
component_uuid: Component UUID
vulnerability_uuid: Vulnerability UUID
state: Analysis state (NOT_SET, EXPLOITABLE, IN_TRIAGE, FALSE_POSITIVE,
NOT_AFFECTED, RESOLVED)
comment: Optional analysis comment
suppressed: Whether to suppress the finding
"""
data = {
"project": project_uuid,
"component": component_uuid,
"vulnerability": vulnerability_uuid,
"analysisState": state,
"suppressed": suppressed
}
if comment:
data["comment"] = comment
return self._put("/analysis", json=data)
# =========================================================================
# License
# =========================================================================
def get_licenses(self) -> List[Dict[str, Any]]:
"""Get all licenses."""
return self._get("/license")
def get_license_groups(self) -> List[Dict[str, Any]]:
"""Get all license groups."""
return self._get("/licenseGroup")
# =========================================================================
# CLI Interface
# =========================================================================
def main():
"""CLI interface for common operations."""
import argparse
parser = argparse.ArgumentParser(description="Dependency-Track CLI")
parser.add_argument("--url", help="Dependency-Track URL", default=os.getenv("DTRACK_URL"))
parser.add_argument("--api-key", help="API Key", default=os.getenv("DTRACK_API_KEY"))
subparsers = parser.add_subparsers(dest="command", help="Commands")
# Upload command
upload_parser = subparsers.add_parser("upload", help="Upload SBOM")
upload_parser.add_argument("bom", help="Path to BOM file")
upload_parser.add_argument("--project", help="Project name")
upload_parser.add_argument("--version", help="Project version")
upload_parser.add_argument("--wait", action="store_true", help="Wait for processing")
# List projects command
subparsers.add_parser("projects", help="List projects")
# Get metrics command
metrics_parser = subparsers.add_parser("metrics", help="Get project metrics")
metrics_parser.add_argument("project", help="Project name")
metrics_parser.add_argument("version", help="Project version")
# Security gate command
gate_parser = subparsers.add_parser("gate", help="Security gate check")
gate_parser.add_argument("project", help="Project name")
gate_parser.add_argument("version", help="Project version")
gate_parser.add_argument("--max-critical", type=int, default=0)
gate_parser.add_argument("--max-high", type=int, default=0)
args = parser.parse_args()
if not args.url or not args.api_key:
parser.error("--url and --api-key required (or set DTRACK_URL and DTRACK_API_KEY)")
client = DependencyTrackClient(base_url=args.url, api_key=args.api_key)
if args.command == "upload":
result = client.upload_sbom(
args.bom,
project_name=args.project,
project_version=args.version
)
print(f"Upload initiated. Token: {result.get('token')}")
if args.wait:
print("Waiting for processing...")
if client.wait_for_bom_processing(result["token"]):
print("Processing complete!")
else:
print("Processing timeout!")
exit(1)
elif args.command == "projects":
projects = client.get_projects()
for p in projects:
print(f"{p['name']}:{p['version']} ({p['uuid']})")
elif args.command == "metrics":
project = client.get_project_by_name_version(args.project, args.version)
if not project:
print(f"Project not found: {args.project}:{args.version}")
exit(1)
summary = client.get_vulnerability_summary(project["uuid"])
print(f"Vulnerabilities for {args.project}:{args.version}")
print(f" Critical: {summary.critical}")
print(f" High: {summary.high}")
print(f" Medium: {summary.medium}")
print(f" Low: {summary.low}")
print(f" Total: {summary.total}")
elif args.command == "gate":
project = client.get_project_by_name_version(args.project, args.version)
if not project:
print(f"Project not found: {args.project}:{args.version}")
exit(1)
summary = client.get_vulnerability_summary(project["uuid"])
if summary.exceeds_threshold(
max_critical=args.max_critical,
max_high=args.max_high
):
print(f"FAILED: Vulnerabilities exceed threshold")
print(f" Critical: {summary.critical} (max: {args.max_critical})")
print(f" High: {summary.high} (max: {args.max_high})")
exit(1)
else:
print("PASSED: Security gate check")
if __name__ == "__main__":
main()
Azure AD OIDC Validation Guide
Comprehensive validation guide for Azure AD OIDC integration with Dependency-Track.
Pre-Deployment Checklist
Azure AD App Registration
- [ ] App created with
AzureADMyOrgsign-in audience - [ ] Redirect URI:
https://<domain>/static/oidc-callback.html - [ ]
groupMembershipClaims: SecurityGroupconfigured - [ ] Optional claims include
groups,email,preferred_username - [ ] API permissions: User.Read, GroupMember.Read.All, email, openid, profile
- [ ] Admin consent granted
- [ ] Client secret stored in Key Vault
Azure AD Groups
- [ ] Groups created with naming convention:
G-Usuarios-DependencyTrack-<Role> - [ ] Users added to appropriate groups
- [ ] Group Object IDs documented
Dependency-Track Configuration
- [ ] OIDC environment variables set
- [ ] Teams created matching group names
- [ ] Group-to-Team mappings configured
- [ ] Permissions assigned to Teams
---
Validation Commands
1. Verify App Registration Configuration
CLIENT_ID="your-client-id"
az ad app show --id $CLIENT_ID --query '{
displayName:displayName,
groupMembershipClaims:groupMembershipClaims,
web:{redirectUris:web.redirectUris},
optionalClaims:optionalClaims
}' -o yamlExpected output:
displayName: DependencyTrack
groupMembershipClaims: SecurityGroup
web:
redirectUris:
- https://dtrack.example.com/static/oidc-callback.html
optionalClaims:
idToken:
- name: groups
...2. Verify Azure AD Groups Exist
az ad group list --filter "startswith(displayName, 'G-Usuarios-DependencyTrack')" \
--query "[].{name:displayName, id:id}" -o tableExpected output:
Name Id
-------------------------------- ------------------------------------
G-Usuarios-DependencyTrack-Admin 31d6daa5-5cc2-4e5f-9bf5-75ee8e09198c
G-Usuarios-DependencyTrack-Mod d34a2ea3-34c9-4744-a031-4e4a55d72746
G-Usuarios-DependencyTrack-Audit 39d0ddb2-8a0d-47c9-bc55-1993aadf7fe8
G-Usuarios-DependencyTrack-Dev 7144c503-7934-40fe-99e2-f45f67cb383e
G-Usuarios-DependencyTrack-Read 7109c419-028e-4b7b-8246-66517d512c3c3. Verify User Group Membership
USER_UPN="user@example.com"
# Get user's security group memberships
az ad user get-member-objects --id $USER_UPN --security-enabled-only true
# Filter for DependencyTrack groups
az ad user get-member-objects --id $USER_UPN --security-enabled-only true \
| jq -r '.[] | select(. | test("31d6daa5|d34a2ea3|39d0ddb2|7144c503|7109c419"))'4. Verify Dependency-Track Teams via API
export DTRACK_URL="https://dtrack.dev.cafehyna.com.br"
export API_KEY="your-api-key"
# List all teams with their OIDC group mappings
curl -s -H "X-Api-Key: ${API_KEY}" "${DTRACK_URL}/api/v1/team" | jq '[.[] | {
name: .name,
uuid: .uuid,
oidcGroups: [.mappedOidcGroups[]?.group.uuid // empty]
}]'Expected output:
[
{
"name": "Administrators",
"uuid": "abc123...",
"oidcGroups": ["31d6daa5-5cc2-4e5f-9bf5-75ee8e09198c"]
},
...
]5. Verify OIDC Groups in Dependency-Track
# List all OIDC groups recognized by Dependency-Track
curl -s -H "X-Api-Key: ${API_KEY}" "${DTRACK_URL}/api/v1/oidc/group" | jq '.'Note: Groups only appear here after a user from that group has authenticated.
6. Check User's Current Teams
# After user logs in via OIDC
curl -s -H "X-Api-Key: ${API_KEY}" "${DTRACK_URL}/api/v1/user/self" | jq '.teams'7. Verify Team Permissions
# Get specific team's permissions
TEAM_NAME="Administrators"
curl -s -H "X-Api-Key: ${API_KEY}" "${DTRACK_URL}/api/v1/team" \
| jq '.[] | select(.name=="'$TEAM_NAME'") | {name, permissions}'---
Expected Results Summary
| Check | Expected Result |
|---|---|
| App Registration groups claim | SecurityGroup |
| Azure AD groups | 4-5 groups with naming convention |
| User group membership | Returns matching group Object IDs |
| D-Track teams | 4-5 teams with mappedOidcGroups populated |
| OIDC groups | Shows groups after user login |
| User teams | User belongs to expected teams based on Azure AD groups |
---
Troubleshooting
Groups not appearing after user login
1. Check OIDC logs:
kubectl logs -n dependency-track deployment/dependency-track-apiserver \
| grep -i "oidc\|group\|claim"2. Decode the ID token:
- Open browser DevTools > Network
- Login to Dependency-Track
- Find request to
/api/v1/oidc/callback - Copy the
id_tokenfrom the request - Decode at https://jwt.io
- Verify
groupsclaim contains expected UUIDs
3. Verify App Registration manifest:
az ad app show --id $CLIENT_ID --query 'optionalClaims.idToken[?name==`groups`]'User not assigned to correct team
1. Check team synchronization setting:
kubectl get pods -n dependency-track -o jsonpath='{.items[*].spec.containers[*].env}' \
| jq '.[] | select(.name | contains("TEAM"))'2. Manually trigger team assignment:
- User logs out
- Clear browser cache/cookies
- User logs in again
3. Verify group-to-team mapping exists:
curl -s -H "X-Api-Key: ${API_KEY}" "${DTRACK_URL}/api/v1/team" \
| jq '.[] | select(.mappedOidcGroups | length > 0)'---
Creating Missing Group Mappings via API
If groups need to be manually mapped:
# Configuration
TEAM_NAME="Administrators"
AZURE_GROUP_ID="31d6daa5-5cc2-4e5f-9bf5-75ee8e09198c"
# 1. Get or create the team
TEAM_UUID=$(curl -s -H "X-Api-Key: ${API_KEY}" \
"${DTRACK_URL}/api/v1/team" | jq -r '.[] | select(.name=="'$TEAM_NAME'") | .uuid')
# If team doesn't exist, create it
if [ -z "$TEAM_UUID" ] || [ "$TEAM_UUID" = "null" ]; then
TEAM_UUID=$(curl -s -X PUT "${DTRACK_URL}/api/v1/team" \
-H "X-Api-Key: ${API_KEY}" \
-H "Content-Type: application/json" \
-d '{"name": "'$TEAM_NAME'"}' | jq -r '.uuid')
echo "Created team: $TEAM_UUID"
fi
# 2. Create OIDC group (required before mapping)
# Use Azure AD Group Object ID as both uuid and name
curl -X PUT "${DTRACK_URL}/api/v1/oidc/group" \
-H "X-Api-Key: ${API_KEY}" \
-H "Content-Type: application/json" \
-d "{\"uuid\": \"${AZURE_GROUP_ID}\", \"name\": \"${AZURE_GROUP_ID}\"}"
# 3. Get the OIDC group UUID (D-Track generates internal UUID)
OIDC_GROUP_UUID=$(curl -s -H "X-Api-Key: ${API_KEY}" \
"${DTRACK_URL}/api/v1/oidc/group" | jq -r ".[] | select(.name==\"${AZURE_GROUP_ID}\") | .uuid")
echo "OIDC Group UUID: $OIDC_GROUP_UUID"
# 4. Create OIDC group-to-team mapping
# IMPORTANT: Use UUID strings directly, NOT objects
curl -X PUT "${DTRACK_URL}/api/v1/oidc/mapping" \
-H "X-Api-Key: ${API_KEY}" \
-H "Content-Type: application/json" \
-d "{\"team\": \"${TEAM_UUID}\", \"group\": \"${OIDC_GROUP_UUID}\"}"
# 5. Assign permissions (example: full admin)
for PERMISSION in ACCESS_MANAGEMENT BOM_UPLOAD POLICY_MANAGEMENT \
POLICY_VIOLATION_ANALYSIS PROJECT_CREATION_UPLOAD PORTFOLIO_MANAGEMENT \
SYSTEM_CONFIGURATION VIEW_PORTFOLIO VIEW_VULNERABILITY; do
curl -X POST "${DTRACK_URL}/api/v1/permission/${PERMISSION}/team/${TEAM_UUID}" \
-H "X-Api-Key: ${API_KEY}"
done
# 6. Verify mapping was created
curl -s -H "X-Api-Key: ${API_KEY}" "${DTRACK_URL}/api/v1/team" \
| jq ".[] | select(.name==\"${TEAM_NAME}\") | {name, mappedOidcGroups}"IMPORTANT: The /api/v1/oidc/mapping endpoint expects UUID strings, not objects.Using {"team": {"uuid": "..."}, "group": {"uuid": "..."}} returns HTTP 400 Bad Request.---
Reference: Standard Group Mappings
| Azure AD Group | Object ID | Dependency-Track Team | Role |
|---|---|---|---|
| G-Usuarios-DependencyTrack-Admin | 31d6daa5-5cc2-4e5f-9bf5-75ee8e09198c | Administrators | Full access |
| G-Usuarios-DependencyTrack-Moderator | d34a2ea3-34c9-4744-a031-4e4a55d72746 | Moderators | Policy & vulnerability management |
| G-Usuarios-DependencyTrack-Auditor | 39d0ddb2-8a0d-47c9-bc55-1993aadf7fe8 | Auditors | Read-only with audit capabilities |
| G-Usuarios-DependencyTrack-Developer | 7144c503-7934-40fe-99e2-f45f67cb383e | Developers | BOM upload, view portfolio |
| G-Usuarios-DependencyTrack-ReadOnly | 7109c419-028e-4b7b-8246-66517d512c3c | ReadOnly | View only |
# Azure DevOps Pipeline - Dependency-Track Integration
# Complete CI/CD pipeline with SBOM generation and security gates
trigger:
branches:
include:
- main
- develop
paths:
exclude:
- '*.md'
- 'docs/**'
pr:
branches:
include:
- main
drafts: false
pool:
vmImage: 'ubuntu-latest'
variables:
- group: dependency-track-credentials # Contains DTRACK_URL and DTRACK_API_KEY
- name: projectName
value: '$(Build.Repository.Name)'
- name: projectVersion
value: '$(Build.SourceBranchName)'
stages:
# ============================================
# Stage 1: Build
# ============================================
- stage: Build
displayName: 'Build Application'
jobs:
- job: BuildJob
displayName: 'Build'
steps:
- checkout: self
fetchDepth: 0
# Node.js build
- task: NodeTool@0
displayName: 'Install Node.js'
inputs:
versionSpec: '20.x'
- task: Npm@1
displayName: 'npm install'
inputs:
command: 'ci'
- task: Npm@1
displayName: 'npm build'
inputs:
command: 'custom'
customCommand: 'run build'
# Publish build artifacts
- task: PublishPipelineArtifact@1
displayName: 'Publish Build Artifacts'
inputs:
targetPath: '$(Build.SourcesDirectory)/dist'
artifact: 'build'
# ============================================
# Stage 2: Security Scan
# ============================================
- stage: Security
displayName: 'Security Analysis'
dependsOn: Build
jobs:
- job: GenerateSBOM
displayName: 'Generate SBOM'
steps:
- checkout: self
- task: NodeTool@0
displayName: 'Install Node.js'
inputs:
versionSpec: '20.x'
- task: Npm@1
displayName: 'npm install'
inputs:
command: 'ci'
# Generate SBOM using CycloneDX
- task: Bash@3
displayName: 'Generate CycloneDX SBOM'
inputs:
targetType: 'inline'
script: |
npx @cyclonedx/cyclonedx-npm --output-file $(Build.ArtifactStagingDirectory)/bom.json
echo "SBOM generated successfully"
cat $(Build.ArtifactStagingDirectory)/bom.json | jq '.metadata'
# Alternative: Use Syft for container scanning
# - task: Bash@3
# displayName: 'Generate SBOM with Syft'
# inputs:
# targetType: 'inline'
# script: |
# curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
# syft packages dir:. -o cyclonedx-json > $(Build.ArtifactStagingDirectory)/bom.json
- task: PublishPipelineArtifact@1
displayName: 'Publish SBOM'
inputs:
targetPath: '$(Build.ArtifactStagingDirectory)/bom.json'
artifact: 'sbom'
- job: UploadSBOM
displayName: 'Upload SBOM to Dependency-Track'
dependsOn: GenerateSBOM
steps:
- task: DownloadPipelineArtifact@2
displayName: 'Download SBOM'
inputs:
artifact: 'sbom'
path: '$(Pipeline.Workspace)/sbom'
- task: Bash@3
displayName: 'Upload SBOM to Dependency-Track'
inputs:
targetType: 'inline'
script: |
set -e
BOM_FILE="$(Pipeline.Workspace)/sbom/bom.json"
# Base64 encode the SBOM
BOM_CONTENT=$(base64 -w0 "$BOM_FILE")
# Upload to Dependency-Track
RESPONSE=$(curl -s -w "\n%{http_code}" -X PUT "$(DTRACK_URL)/api/v1/bom" \
-H "X-Api-Key: $(DTRACK_API_KEY)" \
-H "Content-Type: application/json" \
-d "{
\"projectName\": \"$(projectName)\",
\"projectVersion\": \"$(projectVersion)\",
\"autoCreate\": true,
\"bom\": \"${BOM_CONTENT}\"
}")
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
BODY=$(echo "$RESPONSE" | head -n-1)
if [ "$HTTP_CODE" -ne 200 ]; then
echo "##[error]Failed to upload SBOM. HTTP $HTTP_CODE"
echo "$BODY"
exit 1
fi
echo "##[section]SBOM uploaded successfully"
TOKEN=$(echo "$BODY" | jq -r '.token')
echo "##vso[task.setvariable variable=uploadToken;isOutput=true]$TOKEN"
name: upload
- job: SecurityGate
displayName: 'Security Gate'
dependsOn: UploadSBOM
steps:
- task: Bash@3
displayName: 'Wait for Analysis'
inputs:
targetType: 'inline'
script: |
echo "Waiting for Dependency-Track to process SBOM..."
sleep 45
- task: Bash@3
displayName: 'Check Vulnerabilities'
inputs:
targetType: 'inline'
script: |
set -e
# Get project UUID
PROJECT=$(curl -s -H "X-Api-Key: $(DTRACK_API_KEY)" \
"$(DTRACK_URL)/api/v1/project/lookup?name=$(projectName)&version=$(projectVersion)")
PROJECT_UUID=$(echo $PROJECT | jq -r '.uuid')
if [ "$PROJECT_UUID" = "null" ] || [ -z "$PROJECT_UUID" ]; then
echo "##[error]Project not found in Dependency-Track"
exit 1
fi
echo "##[section]Project UUID: $PROJECT_UUID"
# Get metrics
METRICS=$(curl -s -H "X-Api-Key: $(DTRACK_API_KEY)" \
"$(DTRACK_URL)/api/v1/metrics/project/${PROJECT_UUID}/current")
CRITICAL=$(echo $METRICS | jq -r '.critical // 0')
HIGH=$(echo $METRICS | jq -r '.high // 0')
MEDIUM=$(echo $METRICS | jq -r '.medium // 0')
LOW=$(echo $METRICS | jq -r '.low // 0')
echo "##[section]Vulnerability Summary"
echo "Critical: $CRITICAL"
echo "High: $HIGH"
echo "Medium: $MEDIUM"
echo "Low: $LOW"
# Set output variables for later stages
echo "##vso[task.setvariable variable=criticalCount;isOutput=true]$CRITICAL"
echo "##vso[task.setvariable variable=highCount;isOutput=true]$HIGH"
# Fail on critical vulnerabilities
if [ "$CRITICAL" -gt 0 ]; then
echo "##[error]Critical vulnerabilities found: $CRITICAL"
echo "##vso[task.logissue type=error]Security gate failed - critical vulnerabilities detected"
exit 1
fi
# Warn on high vulnerabilities (configurable)
if [ "$HIGH" -gt 5 ]; then
echo "##[warning]High vulnerability count ($HIGH) exceeds threshold"
echo "##vso[task.logissue type=warning]High vulnerabilities exceed threshold"
fi
echo "##[section]Security gate passed!"
name: gate
- task: Bash@3
displayName: 'Check Policy Violations'
inputs:
targetType: 'inline'
script: |
set -e
PROJECT=$(curl -s -H "X-Api-Key: $(DTRACK_API_KEY)" \
"$(DTRACK_URL)/api/v1/project/lookup?name=$(projectName)&version=$(projectVersion)")
PROJECT_UUID=$(echo $PROJECT | jq -r '.uuid')
# Get policy violations
VIOLATIONS=$(curl -s -H "X-Api-Key: $(DTRACK_API_KEY)" \
"$(DTRACK_URL)/api/v1/violation/project/${PROJECT_UUID}")
FAIL_COUNT=$(echo $VIOLATIONS | jq '[.[] | select(.policyViolation.violationState == "FAIL")] | length')
WARN_COUNT=$(echo $VIOLATIONS | jq '[.[] | select(.policyViolation.violationState == "WARN")] | length')
echo "##[section]Policy Violations"
echo "FAIL: $FAIL_COUNT"
echo "WARN: $WARN_COUNT"
if [ "$FAIL_COUNT" -gt 0 ]; then
echo "##[error]Policy violations found!"
echo $VIOLATIONS | jq -r '.[] | select(.policyViolation.violationState == "FAIL") | "- \(.component.name):\(.component.version) - \(.policyViolation.policy.name)"'
exit 1
fi
# ============================================
# Stage 3: Test
# ============================================
- stage: Test
displayName: 'Run Tests'
dependsOn: Security
jobs:
- job: UnitTests
displayName: 'Unit Tests'
steps:
- checkout: self
- task: NodeTool@0
inputs:
versionSpec: '20.x'
- task: Npm@1
inputs:
command: 'ci'
- task: Npm@1
displayName: 'Run Tests'
inputs:
command: 'custom'
customCommand: 'test -- --coverage'
- task: PublishCodeCoverageResults@2
displayName: 'Publish Coverage'
inputs:
summaryFileLocation: '$(Build.SourcesDirectory)/coverage/cobertura-coverage.xml'
# ============================================
# Stage 4: Deploy
# ============================================
- stage: Deploy
displayName: 'Deploy to Production'
dependsOn: Test
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
jobs:
- deployment: Production
displayName: 'Deploy to Production'
environment: 'production'
strategy:
runOnce:
deploy:
steps:
- task: DownloadPipelineArtifact@2
inputs:
artifact: 'build'
path: '$(Pipeline.Workspace)/build'
- task: Bash@3
displayName: 'Deploy'
inputs:
targetType: 'inline'
script: |
echo "Deploying to production..."
# Add deployment commands
# GitHub Actions - Dependency-Track SBOM Upload
# Triggers on push to main and pull requests
# Uses official DependencyTrack GitHub Action
name: SBOM Security Scan
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
workflow_dispatch:
env:
DTRACK_URL: ${{ secrets.DTRACK_URL }}
DTRACK_API_KEY: ${{ secrets.DTRACK_API_KEY }}
jobs:
# Job 1: Build and Generate SBOM
generate-sbom:
name: Generate SBOM
runs-on: ubuntu-latest
outputs:
project-version: ${{ steps.version.outputs.version }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Determine version
id: version
run: |
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
echo "version=PR-${{ github.event.pull_request.number }}" >> $GITHUB_OUTPUT
else
echo "version=${{ github.ref_name }}" >> $GITHUB_OUTPUT
fi
# Node.js Example
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Generate SBOM (npm)
run: npx @cyclonedx/cyclonedx-npm --output-file bom.json
# Java/Maven Example (uncomment if needed)
# - name: Setup Java
# uses: actions/setup-java@v4
# with:
# distribution: 'temurin'
# java-version: '21'
# cache: 'maven'
#
# - name: Build and Generate SBOM (Maven)
# run: |
# mvn clean package -DskipTests
# mvn org.cyclonedx:cyclonedx-maven-plugin:makeAggregateBom -DoutputFormat=json
# Python Example (uncomment if needed)
# - name: Setup Python
# uses: actions/setup-python@v5
# with:
# python-version: '3.12'
#
# - name: Generate SBOM (Python)
# run: |
# pip install cyclonedx-bom
# cyclonedx-py -r requirements.txt -o bom.json
# Go Example (uncomment if needed)
# - name: Setup Go
# uses: actions/setup-go@v5
# with:
# go-version: '1.22'
#
# - name: Generate SBOM (Go)
# run: |
# go install github.com/CycloneDX/cyclonedx-gomod/cmd/cyclonedx-gomod@latest
# cyclonedx-gomod mod -json -output bom.json
# Universal SBOM with Syft
# - name: Generate SBOM (Syft)
# uses: anchore/sbom-action@v0
# with:
# format: cyclonedx-json
# output-file: bom.json
- name: Upload SBOM artifact
uses: actions/upload-artifact@v4
with:
name: sbom
path: bom.json
retention-days: 30
# Job 2: Upload to Dependency-Track
upload-sbom:
name: Upload SBOM to Dependency-Track
needs: generate-sbom
runs-on: ubuntu-latest
steps:
- name: Download SBOM artifact
uses: actions/download-artifact@v4
with:
name: sbom
- name: Upload SBOM to Dependency-Track
uses: DependencyTrack/gh-upload-sbom@v3
with:
serverhostname: ${{ secrets.DTRACK_URL }}
apikey: ${{ secrets.DTRACK_API_KEY }}
projectname: ${{ github.repository }}
projectversion: ${{ needs.generate-sbom.outputs.project-version }}
bomfilename: bom.json
autocreate: true
# Parent project (optional)
# parentName: 'parent-project'
# parentVersion: 'main'
# Job 3: Security Gate (wait for analysis and check results)
security-gate:
name: Security Gate
needs: [generate-sbom, upload-sbom]
runs-on: ubuntu-latest
steps:
- name: Wait for Dependency-Track analysis
run: sleep 30 # Allow time for async processing
- name: Get project metrics
id: metrics
run: |
# Get project UUID
PROJECT=$(curl -s -H "X-Api-Key: ${{ secrets.DTRACK_API_KEY }}" \
"${{ secrets.DTRACK_URL }}/api/v1/project/lookup?name=${{ github.repository }}&version=${{ needs.generate-sbom.outputs.project-version }}")
PROJECT_UUID=$(echo $PROJECT | jq -r '.uuid')
echo "Project UUID: $PROJECT_UUID"
# Get current metrics
METRICS=$(curl -s -H "X-Api-Key: ${{ secrets.DTRACK_API_KEY }}" \
"${{ secrets.DTRACK_URL }}/api/v1/metrics/project/${PROJECT_UUID}/current")
echo "Metrics: $METRICS"
CRITICAL=$(echo $METRICS | jq -r '.critical // 0')
HIGH=$(echo $METRICS | jq -r '.high // 0')
MEDIUM=$(echo $METRICS | jq -r '.medium // 0')
LOW=$(echo $METRICS | jq -r '.low // 0')
echo "critical=$CRITICAL" >> $GITHUB_OUTPUT
echo "high=$HIGH" >> $GITHUB_OUTPUT
echo "medium=$MEDIUM" >> $GITHUB_OUTPUT
echo "low=$LOW" >> $GITHUB_OUTPUT
- name: Display vulnerability summary
run: |
echo "## Vulnerability Summary" >> $GITHUB_STEP_SUMMARY
echo "| Severity | Count |" >> $GITHUB_STEP_SUMMARY
echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| Critical | ${{ steps.metrics.outputs.critical }} |" >> $GITHUB_STEP_SUMMARY
echo "| High | ${{ steps.metrics.outputs.high }} |" >> $GITHUB_STEP_SUMMARY
echo "| Medium | ${{ steps.metrics.outputs.medium }} |" >> $GITHUB_STEP_SUMMARY
echo "| Low | ${{ steps.metrics.outputs.low }} |" >> $GITHUB_STEP_SUMMARY
- name: Fail on critical vulnerabilities
if: steps.metrics.outputs.critical != '0'
run: |
echo "::error::Critical vulnerabilities found: ${{ steps.metrics.outputs.critical }}"
exit 1
- name: Warn on high vulnerabilities
if: steps.metrics.outputs.high > 5
run: |
echo "::warning::High vulnerabilities exceed threshold: ${{ steps.metrics.outputs.high }}"
# Job 4: Check policy violations
policy-check:
name: Policy Compliance Check
needs: [generate-sbom, upload-sbom]
runs-on: ubuntu-latest
steps:
- name: Wait for policy evaluation
run: sleep 30
- name: Check policy violations
run: |
# Get project UUID
PROJECT=$(curl -s -H "X-Api-Key: ${{ secrets.DTRACK_API_KEY }}" \
"${{ secrets.DTRACK_URL }}/api/v1/project/lookup?name=${{ github.repository }}&version=${{ needs.generate-sbom.outputs.project-version }}")
PROJECT_UUID=$(echo $PROJECT | jq -r '.uuid')
# Get policy violations
VIOLATIONS=$(curl -s -H "X-Api-Key: ${{ secrets.DTRACK_API_KEY }}" \
"${{ secrets.DTRACK_URL }}/api/v1/violation/project/${PROJECT_UUID}")
FAIL_COUNT=$(echo $VIOLATIONS | jq '[.[] | select(.type == "FAIL")] | length')
WARN_COUNT=$(echo $VIOLATIONS | jq '[.[] | select(.type == "WARN")] | length')
INFO_COUNT=$(echo $VIOLATIONS | jq '[.[] | select(.type == "INFO")] | length')
echo "## Policy Violations" >> $GITHUB_STEP_SUMMARY
echo "| Type | Count |" >> $GITHUB_STEP_SUMMARY
echo "|------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| Fail | $FAIL_COUNT |" >> $GITHUB_STEP_SUMMARY
echo "| Warn | $WARN_COUNT |" >> $GITHUB_STEP_SUMMARY
echo "| Info | $INFO_COUNT |" >> $GITHUB_STEP_SUMMARY
if [ "$FAIL_COUNT" -gt 0 ]; then
echo "::error::Policy violations with FAIL state: $FAIL_COUNT"
exit 1
fi
# GitLab CI - Dependency-Track Integration
# Full pipeline with SBOM generation, upload, and security gates
stages:
- build
- sbom
- security
- test
- deploy
variables:
DTRACK_URL: ${DTRACK_URL}
# Store API key in GitLab CI/CD variables (masked)
# DTRACK_API_KEY: defined in CI/CD settings
# Project configuration
PROJECT_NAME: ${CI_PROJECT_PATH}
PROJECT_VERSION: ${CI_COMMIT_REF_NAME}
# Cache dependencies
cache:
paths:
- node_modules/
- .m2/repository/
- .gradle/
# ============================================
# Build Stage
# ============================================
build:
stage: build
image: node:20-alpine
script:
- npm ci
- npm run build
artifacts:
paths:
- dist/
- node_modules/
expire_in: 1 hour
# Maven build alternative
build:maven:
stage: build
image: maven:3.9-eclipse-temurin-21
script:
- mvn clean package -DskipTests -B
artifacts:
paths:
- target/
expire_in: 1 hour
only:
changes:
- pom.xml
- src/**/*
# ============================================
# SBOM Generation Stage
# ============================================
generate-sbom:npm:
stage: sbom
image: node:20-alpine
needs: ["build"]
script:
- npx @cyclonedx/cyclonedx-npm --output-file bom.json
artifacts:
paths:
- bom.json
reports:
cyclonedx: bom.json
expire_in: 30 days
generate-sbom:maven:
stage: sbom
image: maven:3.9-eclipse-temurin-21
needs: ["build:maven"]
script:
- mvn org.cyclonedx:cyclonedx-maven-plugin:makeAggregateBom -DoutputFormat=json -DoutputName=bom -B
- mv target/bom.json .
artifacts:
paths:
- bom.json
reports:
cyclonedx: bom.json
expire_in: 30 days
only:
changes:
- pom.xml
- src/**/*
# Universal SBOM with Syft
generate-sbom:syft:
stage: sbom
image: anchore/syft:latest
script:
- syft packages dir:. -o cyclonedx-json > bom.json
artifacts:
paths:
- bom.json
reports:
cyclonedx: bom.json
expire_in: 30 days
when: manual
# ============================================
# Security Stage - Upload and Analyze
# ============================================
upload-sbom:
stage: security
image: curlimages/curl:latest
needs: ["generate-sbom:npm"]
script:
- |
# Upload SBOM to Dependency-Track
RESPONSE=$(curl -s -w "\n%{http_code}" -X PUT "${DTRACK_URL}/api/v1/bom" \
-H "X-Api-Key: ${DTRACK_API_KEY}" \
-H "Content-Type: application/json" \
-d "{
\"projectName\": \"${PROJECT_NAME}\",
\"projectVersion\": \"${PROJECT_VERSION}\",
\"autoCreate\": true,
\"bom\": \"$(base64 -w0 bom.json)\"
}")
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
BODY=$(echo "$RESPONSE" | head -n-1)
if [ "$HTTP_CODE" -ne 200 ]; then
echo "Failed to upload SBOM. HTTP $HTTP_CODE"
echo "$BODY"
exit 1
fi
echo "SBOM uploaded successfully"
echo "$BODY" | jq .
security-gate:
stage: security
image: curlimages/curl:latest
needs: ["upload-sbom"]
script:
- |
# Wait for async processing
sleep 30
# Get project UUID
PROJECT=$(curl -s -H "X-Api-Key: ${DTRACK_API_KEY}" \
"${DTRACK_URL}/api/v1/project/lookup?name=${PROJECT_NAME}&version=${PROJECT_VERSION}")
PROJECT_UUID=$(echo $PROJECT | jq -r '.uuid')
if [ "$PROJECT_UUID" = "null" ]; then
echo "Project not found"
exit 1
fi
# Get metrics
METRICS=$(curl -s -H "X-Api-Key: ${DTRACK_API_KEY}" \
"${DTRACK_URL}/api/v1/metrics/project/${PROJECT_UUID}/current")
CRITICAL=$(echo $METRICS | jq -r '.critical // 0')
HIGH=$(echo $METRICS | jq -r '.high // 0')
MEDIUM=$(echo $METRICS | jq -r '.medium // 0')
LOW=$(echo $METRICS | jq -r '.low // 0')
echo "======================================"
echo "Vulnerability Summary"
echo "======================================"
echo "Critical: $CRITICAL"
echo "High: $HIGH"
echo "Medium: $MEDIUM"
echo "Low: $LOW"
echo "======================================"
# Fail on critical vulnerabilities
if [ "$CRITICAL" -gt 0 ]; then
echo "FAILED: Critical vulnerabilities found!"
exit 1
fi
# Warn on high vulnerabilities (configurable threshold)
if [ "$HIGH" -gt 5 ]; then
echo "WARNING: High vulnerability count exceeds threshold"
fi
echo "Security gate passed!"
allow_failure: false
policy-check:
stage: security
image: curlimages/curl:latest
needs: ["upload-sbom"]
script:
- |
sleep 30
PROJECT=$(curl -s -H "X-Api-Key: ${DTRACK_API_KEY}" \
"${DTRACK_URL}/api/v1/project/lookup?name=${PROJECT_NAME}&version=${PROJECT_VERSION}")
PROJECT_UUID=$(echo $PROJECT | jq -r '.uuid')
# Get policy violations
VIOLATIONS=$(curl -s -H "X-Api-Key: ${DTRACK_API_KEY}" \
"${DTRACK_URL}/api/v1/violation/project/${PROJECT_UUID}")
FAIL_COUNT=$(echo $VIOLATIONS | jq '[.[] | select(.policyViolation.violationState == "FAIL")] | length')
echo "Policy violations (FAIL): $FAIL_COUNT"
if [ "$FAIL_COUNT" -gt 0 ]; then
echo "Policy violations found:"
echo $VIOLATIONS | jq -r '.[] | select(.policyViolation.violationState == "FAIL") | "\(.component.name):\(.component.version) - \(.policyViolation.policy.name)"'
exit 1
fi
allow_failure: false
# ============================================
# Test Stage
# ============================================
test:
stage: test
image: node:20-alpine
needs: ["security-gate"]
script:
- npm test
coverage: '/All files[^|]*\|[^|]*\s+([\d\.]+)/'
# ============================================
# Deploy Stage
# ============================================
deploy:production:
stage: deploy
image: alpine:latest
needs: ["test", "policy-check"]
script:
- echo "Deploying to production..."
# Add deployment commands
environment:
name: production
url: https://app.example.com
only:
- main
when: manual
// Jenkinsfile - Dependency-Track Integration
// Requires: Dependency-Track Jenkins Plugin
// https://plugins.jenkins.io/dependency-track/
pipeline {
agent any
environment {
// Dependency-Track configuration
DTRACK_URL = 'https://dtrack.example.com'
DTRACK_API_KEY = credentials('dependency-track-api-key')
// Project identifiers (can use PROJECT_UUID or name/version)
PROJECT_NAME = "${env.JOB_NAME}"
PROJECT_VERSION = "${env.GIT_BRANCH ?: 'main'}"
}
options {
buildDiscarder(logRotator(numToKeepStr: '10'))
timeout(time: 30, unit: 'MINUTES')
disableConcurrentBuilds()
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Build') {
steps {
script {
// Maven example
sh 'mvn clean package -DskipTests'
// Or Gradle
// sh './gradlew build -x test'
// Or npm
// sh 'npm ci && npm run build'
}
}
}
stage('Generate SBOM') {
steps {
script {
// Maven CycloneDX plugin
sh '''
mvn org.cyclonedx:cyclonedx-maven-plugin:makeAggregateBom \
-DoutputFormat=json \
-DoutputName=bom
'''
// Alternative: Use Syft for universal SBOM generation
// sh 'syft packages dir:. -o cyclonedx-json > target/bom.json'
// Alternative: npm
// sh 'npx @cyclonedx/cyclonedx-npm --output-file target/bom.json'
}
}
}
stage('Upload SBOM to Dependency-Track') {
steps {
// Using Dependency-Track Jenkins Plugin
dependencyTrackPublisher(
artifact: 'target/bom.json',
projectName: env.PROJECT_NAME,
projectVersion: env.PROJECT_VERSION,
synchronous: true, // Wait for processing
autoCreateProjects: true,
dependencyTrackUrl: env.DTRACK_URL,
dependencyTrackApiKey: env.DTRACK_API_KEY,
// Policy violation thresholds (fail build if exceeded)
failedTotalCritical: 0,
failedTotalHigh: 5,
failedTotalMedium: 20,
failedTotalLow: 50,
failedTotalUnassigned: 10,
// New vulnerabilities thresholds
failedNewCritical: 0,
failedNewHigh: 0,
// Policy violation thresholds
failedTotalPolicyViolationsFail: 0,
failedTotalPolicyViolationsWarn: 5,
failedTotalPolicyViolationsInfo: 10
)
}
}
stage('Security Gate') {
steps {
script {
// Additional manual check via API if needed
def response = httpRequest(
httpMode: 'GET',
url: "${DTRACK_URL}/api/v1/metrics/project/${env.PROJECT_UUID}/current",
customHeaders: [[name: 'X-Api-Key', value: env.DTRACK_API_KEY]],
validResponseCodes: '200'
)
def metrics = readJSON text: response.content
echo "Vulnerabilities - Critical: ${metrics.critical}, High: ${metrics.high}, Medium: ${metrics.medium}, Low: ${metrics.low}"
if (metrics.critical > 0) {
error("Critical vulnerabilities found! Failing build.")
}
}
}
}
stage('Run Tests') {
steps {
sh 'mvn test'
}
}
stage('Deploy') {
when {
branch 'main'
}
steps {
echo 'Deploying to production...'
// Your deployment steps
}
}
}
post {
always {
// Archive SBOM as artifact
archiveArtifacts artifacts: 'target/bom.json', fingerprint: true
// Clean workspace
cleanWs()
}
failure {
// Notify on failure
emailext(
subject: "Pipeline Failed: ${currentBuild.fullDisplayName}",
body: "Security scan failed. Check Dependency-Track for details: ${DTRACK_URL}",
recipientProviders: [developers(), requestor()]
)
}
}
}
// Alternative: Declarative Pipeline with Shared Library
// @Library('my-shared-library') _
//
// dependencyTrackScan(
// projectName: 'my-project',
// bomPath: 'target/bom.json',
// failOnCritical: true
// )
# Dependency-Track Production Docker Compose
# Features: PostgreSQL, external volumes, proper resource limits, health checks
services:
# PostgreSQL Database
postgres:
image: postgres:16-alpine
container_name: dtrack-postgres
restart: unless-stopped
environment:
POSTGRES_DB: dtrack
POSTGRES_USER: dtrack
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-changeme}
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U dtrack -d dtrack"]
interval: 10s
timeout: 5s
retries: 5
networks:
- dtrack-net
# API Server (Backend)
apiserver:
image: dependencytrack/apiserver:latest
container_name: dtrack-apiserver
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
environment:
# Database Configuration
ALPINE_DATABASE_MODE: external
ALPINE_DATABASE_URL: jdbc:postgresql://postgres:5432/dtrack
ALPINE_DATABASE_DRIVER: org.postgresql.Driver
ALPINE_DATABASE_USERNAME: dtrack
ALPINE_DATABASE_PASSWORD: ${POSTGRES_PASSWORD:-changeme}
# JVM Settings (16GB heap for production)
JAVA_OPTIONS: >-
-Xms4g
-Xmx16g
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:+ParallelRefProcEnabled
-Djava.io.tmpdir=/data/tmp
# Authentication (uncomment as needed)
# ALPINE_LDAP_ENABLED: "true"
# ALPINE_LDAP_SERVER_URL: ldap://ldap.example.com:389
# ALPINE_LDAP_BASEDN: dc=example,dc=com
# ALPINE_OIDC_ENABLED: "true"
# ALPINE_OIDC_ISSUER: https://auth.example.com
# ALPINE_OIDC_CLIENT_ID: dependency-track
# Metrics & Monitoring
ALPINE_METRICS_ENABLED: "true"
# Logging
LOGGING_LEVEL: INFO
# System Requirement Check (disable if needed)
SYSTEM_REQUIREMENT_CHECK_ENABLED: "true"
# Secret Key (generate with: openssl rand -base64 32)
ALPINE_SECRET_KEY_PATH: /data/.secret.key
volumes:
- dtrack-data:/data
ports:
- "8081:8080"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/api/version"]
interval: 30s
timeout: 10s
retries: 3
start_period: 120s
deploy:
resources:
limits:
memory: 18G
cpus: '4'
reservations:
memory: 8G
cpus: '2'
networks:
- dtrack-net
# Frontend (UI)
frontend:
image: dependencytrack/frontend:latest
container_name: dtrack-frontend
restart: unless-stopped
depends_on:
apiserver:
condition: service_healthy
environment:
API_BASE_URL: http://apiserver:8080
# For OIDC, configure the following:
# OIDC_ISSUER: https://auth.example.com
# OIDC_CLIENT_ID: dependency-track-frontend
# OIDC_SCOPE: openid profile email
# OIDC_FLOW: code
ports:
- "8080:8080"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080"]
interval: 30s
timeout: 10s
retries: 3
deploy:
resources:
limits:
memory: 1G
cpus: '2'
reservations:
memory: 512M
cpus: '1'
networks:
- dtrack-net
volumes:
postgres-data:
driver: local
dtrack-data:
driver: local
networks:
dtrack-net:
driver: bridge
# Usage:
# 1. Create .env file with POSTGRES_PASSWORD
# 2. docker compose -f docker-compose-production.yaml up -d
# 3. Access UI at http://localhost:8080
# 4. Default credentials: admin / admin (change immediately!)
# Dependency-Track Hyades Helm Values
# Chart: dependency-track/hyades (v0.10.0)
# App Version: 0.6.0-SNAPSHOT
# Repository: https://dependencytrack.github.io/helm-charts
#
# WARNING: Hyades is NOT generally available yet. Breaking changes may occur
# without prior notice. Use only in test environments.
# GA Roadmap: https://github.com/DependencyTrack/hyades/issues/860
---
# Common settings shared across all components
common:
nameOverride: ""
fullnameOverride: ""
image:
registry: ghcr.io
pullSecrets: []
# External PostgreSQL database (REQUIRED)
database:
jdbcUrl: "jdbc:postgresql://postgres:5432/dtrack"
username: "dtrack"
password: "" # Use secret reference instead
# Apache Kafka configuration (REQUIRED)
kafka:
bootstrapServers: "kafka:9092"
topicPrefix: "" # Optional prefix for all Kafka topics
secretKey:
# Generate a secret key upon deployment
createSecret: true
# Or use an existing secret
# existingSecretName: "hyades-secret-key"
serviceAccount:
create: true
annotations: {}
name: ""
automount: false
# API Server Configuration
apiServer:
enabled: true
# Horizontal Pod Autoscaling
autoScaling:
enabled: true
annotations: {}
minReplicas: 1
maxReplicas: 3
targetCPUUtilizationPercentage: 70
targetMemoryUtilizationPercentage: 70
# Used when autoscaling is disabled
replicaCount: 1
deployment:
strategy:
type: RollingUpdate
annotations: {}
image:
registry: "" # Uses common.image.registry if empty
repository: dependencytrack/hyades-apiserver
tag: snapshot
pullPolicy: Always
command: []
args: []
resources:
requests:
cpu: "2"
memory: 2Gi
limits:
cpu: "4"
memory: 2Gi
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
runAsNonRoot: true
readOnlyRootFilesystem: true
seccompProfile:
type: RuntimeDefault
extraEnv: []
extraEnvFrom: []
probes:
liveness:
failureThreshold: 3
initialDelaySeconds: 10
periodSeconds: 15
successThreshold: 1
timeoutSeconds: 5
readiness:
failureThreshold: 3
initialDelaySeconds: 10
periodSeconds: 15
successThreshold: 1
timeoutSeconds: 5
service:
type: ClusterIP
nodePort: ~
port: 8080
annotations: {}
serviceMonitor:
enabled: true
namespace: monitoring
scrapeInternal: 15s
scrapeTimeout: 30s
# Grace period for pod termination
terminationGracePeriodSeconds: 60
initContainers: []
extraContainers: []
additionalVolumes: []
additionalVolumeMounts: []
tolerations: []
nodeSelector: {}
# Database Initializer Job
initializer:
# Enable for initial setup and migrations
enabled: true
# Deploy as Helm hook (post-install, post-upgrade)
# Set to true if using --wait flag with helm install/upgrade
noHelmHook: false
annotations: {}
image:
registry: ""
repository: dependencytrack/hyades-apiserver
tag: snapshot
pullPolicy: Always
command: []
args: []
resources:
requests:
cpu: 150m
memory: 256Mi
limits:
cpu: 500m
memory: 256Mi
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
runAsNonRoot: true
readOnlyRootFilesystem: true
seccompProfile:
type: RuntimeDefault
extraEnv: []
extraEnvFrom: []
tolerations: []
nodeSelector: {}
waiter:
image:
registry: "docker.io"
repository: bitnami/kubectl
tag: latest
pullPolicy: Always
createRole: true
# Frontend Configuration
frontend:
enabled: true
autoScaling:
enabled: false
annotations: {}
minReplicas: 1
maxReplicas: 3
targetCPUUtilizationPercentage: 70
targetMemoryUtilizationPercentage: 70
replicaCount: 2
deployment:
strategy:
type: RollingUpdate
annotations: {}
image:
registry: ""
repository: dependencytrack/hyades-frontend
tag: snapshot
pullPolicy: Always
command: []
args: []
resources:
requests:
cpu: 150m
memory: 64Mi
limits:
cpu: 500m
memory: 128Mi
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
runAsNonRoot: true
readOnlyRootFilesystem: false # Required for frontend
seccompProfile:
type: RuntimeDefault
extraEnv: []
extraEnvFrom: []
probes:
liveness:
failureThreshold: 3
initialDelaySeconds: 5
periodSeconds: 15
successThreshold: 1
timeoutSeconds: 5
readiness:
failureThreshold: 3
initialDelaySeconds: 5
periodSeconds: 15
successThreshold: 1
timeoutSeconds: 5
service:
type: ClusterIP
nodePort: ~
port: 8080
annotations: {}
apiBaseUrl: ""
initContainers: []
extraContainers: []
additionalVolumes: []
additionalVolumeMounts: []
tolerations: []
nodeSelector: {}
# Notification Publisher Service
notificationPublisher:
enabled: true
autoScaling:
enabled: false
annotations: {}
minReplicas: 1
maxReplicas: 3
targetCPUUtilizationPercentage: 70
targetMemoryUtilizationPercentage: 70
replicaCount: 1
deployment:
# Recreate recommended - avoids Kafka consumer rebalances
strategy:
type: Recreate
annotations: {}
image:
registry: ""
repository: dependencytrack/hyades-notification-publisher
tag: snapshot-native
pullPolicy: Always
command: []
args: []
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: "2"
memory: 2Gi
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
runAsNonRoot: true
readOnlyRootFilesystem: true
seccompProfile:
type: RuntimeDefault
extraEnv: []
extraEnvFrom: []
probes:
liveness:
failureThreshold: 3
initialDelaySeconds: 10
periodSeconds: 15
successThreshold: 1
timeoutSeconds: 5
readiness:
failureThreshold: 3
initialDelaySeconds: 10
periodSeconds: 15
successThreshold: 1
timeoutSeconds: 5
initContainers: []
extraContainers: []
additionalVolumes: []
additionalVolumeMounts: []
tolerations: []
nodeSelector: {}
# Repository Metadata Analyzer Service
repoMetaAnalyzer:
enabled: true
autoScaling:
enabled: false
annotations: {}
minReplicas: 1
maxReplicas: 3
targetCPUUtilizationPercentage: 70
targetMemoryUtilizationPercentage: 70
replicaCount: 1
deployment:
# Recreate recommended - avoids Kafka consumer rebalances
strategy:
type: Recreate
annotations: {}
image:
registry: ""
repository: dependencytrack/hyades-repository-meta-analyzer
tag: snapshot-native
pullPolicy: Always
command: []
args: []
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: "2"
memory: 2Gi
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
runAsNonRoot: true
readOnlyRootFilesystem: true
seccompProfile:
type: RuntimeDefault
extraEnv: []
extraEnvFrom: []
probes:
liveness:
failureThreshold: 3
initialDelaySeconds: 10
periodSeconds: 15
successThreshold: 1
timeoutSeconds: 5
readiness:
failureThreshold: 3
initialDelaySeconds: 10
periodSeconds: 15
successThreshold: 1
timeoutSeconds: 5
initContainers: []
extraContainers: []
additionalVolumes: []
additionalVolumeMounts: []
tolerations: []
nodeSelector: {}
# Vulnerability Analyzer Service
vulnAnalyzer:
enabled: true
# Enable StatefulSet when using RocksDB for state storage
useStatefulSet: false
autoScaling:
enabled: false
annotations: {}
minReplicas: 1
maxReplicas: 3
targetCPUUtilizationPercentage: 70
targetMemoryUtilizationPercentage: 70
replicaCount: 1
deployment:
# Recreate recommended - avoids Kafka consumer rebalances
strategy:
type: Recreate
annotations: {}
image:
registry: ""
repository: dependencytrack/hyades-vulnerability-analyzer
tag: snapshot-native
pullPolicy: Always
command: []
args: []
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: "2"
memory: 2Gi
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
runAsNonRoot: true
readOnlyRootFilesystem: true
seccompProfile:
type: RuntimeDefault
# Persistent volume for RocksDB state (requires useStatefulSet: true)
persistentVolume:
enabled: false
className: ""
size: 2Gi
extraEnv: []
# Enable RocksDB for persistent state:
# extraEnv:
# - name: STATE_STORE_TYPE
# value: "rocks_db"
extraEnvFrom: []
probes:
liveness:
failureThreshold: 3
initialDelaySeconds: 10
periodSeconds: 15
successThreshold: 1
timeoutSeconds: 5
readiness:
failureThreshold: 3
initialDelaySeconds: 10
periodSeconds: 15
successThreshold: 1
timeoutSeconds: 5
service:
annotations: {}
initContainers: []
extraContainers: []
additionalVolumes: []
additionalVolumeMounts: []
tolerations: []
nodeSelector: {}
# Ingress Configuration
ingress:
enabled: true
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "100m"
nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
cert-manager.io/cluster-issuer: letsencrypt-prod
hostname: "dtrack.example.com"
ingressClassName: nginx
tls:
- secretName: dtrack-tls
hosts:
- dtrack.example.com
# Extra Kubernetes objects
extraObjects: []