Skip to content

mreiche/owasp-dependency-track-azure-devops

Repository files navigation

PyPI version

OWASP Dependency Track / Azure DevOps Sync

Synchronizes OWASP Dependency Track Findings with Azure DevOps WorkItems.

Installation

pip install owasp-dependency-track-azure-devops

Usage

The following command will log possible change operations, when the environment variables are configured:

owasp-dtrack-azure-devops

Use the following flag to perform these changes:

owasp-dtrack-azure-devops --apply

As Container runtime:

podman|docker \
 run --rm \
 -eAZURE_ORG_URL="https://dev.azure.com/organisation" \
 -eAZURE_PROJECT="my-project" \
 -eAZURE_API_KEY="abc" \
 -eAZURE_WORK_ITEM_DEFAULT_AREA_PATH="My\Path" \
 -eOWASP_DTRACK_URL="http://192.168.1.100:8081" \
 -eOWASP_DTRACK_VERIFY_SSL="false" \
 -eOWASP_DTRACK_API_KEY="xyz" \
 ghcr.io/mreiche/owasp-dependency-track-azure-devops:latest --apply

Environment variables

These environment variables are available for configuration:

AZURE_ORG_URL="https://dev.azure.com/organisation"  # Azure organisation URL
AZURE_PROJECT=""                                    # Azure project name
AZURE_API_KEY=""                                    # Azure API key to use (PAT also works)
AZURE_WORK_ITEM_DEFAULT_AREA_PATH="My\Path"         # The default area path for new work items (recommended)
OWASP_DTRACK_URL="http://localhost:8081"            # Base-URL to OWASP Dependency Track
OWASP_DTRACK_VERIFY_SSL="False"                     # Do not verify SSL
OWASP_DTRACK_API_KEY=""                             # Your OWASP Dependency Track API Key
HTTPS_PROXY=""                                      # URL for HTTP(S) proxy (optional)
LOG_LEVEL="info"                                    # Logging verbosity (optional)
HTTPX_LOG_LEVEL="warning"                           # Log level of the httpx framework (optional)

You can also pass these variables from a file:

owasp-dtrack-azure-devops --env path/to/your/file.env

How it works

sequenceDiagram
    Sync->>+OWASP DT: Get unsuppressed findings of activate projects
    OWASP DT->>-Sync: Findings[]
    loop for every Finding
        Sync->>+CustomMapper: process_finding()
        CustomMapper-->>-Sync: Filtered Finding
        Sync->>+OWASP DT: get_project()
        OWASP DT->>-Sync: Project
        Sync->>Sync: read WorkItem reference from Analysis
        alt WorkItem reference present
            Sync->>+AzureDevops: get_work_item()
            AzureDevops->>-Sync: WorkItem
        else
            Sync->>CustomMapper: new_work_item()
            opt
                CustomMapper-->CustomMapper: Set Area
                CustomMapper-->CustomMapper: Render description markup
            end
            CustomMapper->>Sync: Updated WorkItem
            Sync->>+AzureDevops: create_work_item()
            AzureDevops->>-Sync: WorkItem created
        end

        opt WorkItem is newer than Analysis
            Sync->>CustomMapper: map_work_item_to_analysis()
            opt WorkItem has been closed
                CustomMapper-->CustomMapper: Suppress Finding
            end
            CustomMapper->>Sync: Updated Analysis
            Sync->>+OWASP DT: update_finding()
            OWASP DT->>-Sync: Finding updated
        end
        opt Analysis is newer than WorkItem
            Sync->>CustomMapper: map_analysis_to_work_item()
            opt Finding has been suppressed
                CustomMapper-->CustomMapper: Close WorkItem
            end
            CustomMapper->>Sync: Updated WorkItem
            Sync->>+AzureDevops: update_work_item()
            AzureDevops->>-Sync: WorkItem updated
        end
    end
Loading

Templating

The WorkItem description is being rendered by the provided template. You can pass your own template using

owasp-dtrack-azure-devops --template path/to/your/template.jinja2

Custom filtering and mapping

You can filter findings and apply changes on the work items using custom mappers:

def process_finding(finding):
    return finding.component.project_name == "My_Project"

def new_work_item(work_item_adapter):
    work_item_adapter.title = "New Finding"

    if work_item_adapter.finding.component.project_name == "Other project":
        work_item_adapter.area = "Path\\To\\My\\Custom\\Area"
        
def map_analysis_to_work_item(analysis_adapter, work_item_adapter):
    # Call this method if you want to re-render the ticket description from template
    work_item_adapter.render_description()

# Remove mappers you don't need
# def map_work_item_to_analysis(work_item_adapter, analysis_adapter):
#     pass

and pass this mapper using:

owasp-dtrack-azure-devops --mapper path/to/your/mapper.py

In Container runtime, keep in mind that you have to mount the mapper location as volume first.

podman|docker \
 run --rm -v"$(pwd):$(pwd)"
 ...
 ghcr.io/mreiche/owasp-dependency-track-azure-devops:latest --mapper "$(pwd)/path/to/your/mapper.py"

More OWASP Dependency Track utils

This library is part of a wider OWASP Dependency Track tool chain:

About

Synchronizes OWASP Dependency Track Findings with Azure DevOps WorkItems

Resources

License

Stars

Watchers

Forks

Packages