26 Commits

Author SHA1 Message Date
7ddb3c153e form for upload, fix
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2025-07-15 17:24:34 +02:00
677e34f1f3 form for upload 2025-07-15 17:16:14 +02:00
2a2a1316e1 tune error messages, fix 2025-07-15 16:31:56 +02:00
f2d6178304 tune error messages 2025-07-15 16:28:01 +02:00
7236c35ef9 fix paths, fix 2025-07-15 16:01:09 +02:00
b2cf3fe4c7 fix paths 2025-07-15 15:59:21 +02:00
a56119379a add forgotten module 2025-07-15 15:48:46 +02:00
bd368822aa fix paths in api 2025-07-15 15:45:45 +02:00
1cb9451c47 fix shell in entrypoint script of server 2025-07-15 15:40:14 +02:00
5eedb7c523 fix image name confusion, fix 2025-07-15 15:36:32 +02:00
294f30eb38 fix image name confusion
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2025-07-15 15:32:53 +02:00
5af202469c fix start script, add port 2025-07-15 15:26:10 +02:00
81bd403069 fix start script 2025-07-15 15:20:26 +02:00
93222237ee adjust ci rules, fix 2025-07-15 15:18:46 +02:00
d5bda1c2d4 adjust ci rules 2025-07-15 15:16:54 +02:00
b430afcfef add deploy stage, fix 3 2025-07-15 15:10:24 +02:00
3ce0b0a4cf add deploy stage, fix 2 2025-07-15 15:09:50 +02:00
c88a74daa3 add deploy stage, fix 2025-07-15 15:09:05 +02:00
10d14d87fb add deploy stage
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2025-07-15 15:07:53 +02:00
58795aca81 rename dockerfiles, fix 2 2025-07-15 14:45:32 +02:00
13271a6d5e rename dockerfiles, fix 2025-07-15 14:44:18 +02:00
5a9493fe32 rename dockerfiles
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2025-07-15 14:42:53 +02:00
708b99852f add second dockerfile, add ci snippet
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2025-07-15 14:40:40 +02:00
e15973db53 add second dockerfile 2025-07-15 14:40:06 +02:00
b2db5b35ad prepare second dockerfile
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2025-07-15 14:33:07 +02:00
b21bd408f7 there is still an error
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2025-07-14 23:13:30 +02:00
9 changed files with 338 additions and 79 deletions

View File

@@ -1,10 +1,11 @@
stages: stages:
- generate-api-clients - generate-api-clients
- build - build
- deploy
variables: variables:
REGISTRY: devnexus.krohne.com:18079/repository/docker-krohne REGISTRY: devnexus.krohne.com:18079/repository/docker-krohne
IMAGE_NAME: $REGISTRY/$CI_PROJECT_NAME IMAGE_NAME_PREFIX: $REGISTRY/$CI_PROJECT_NAME
DTRACK_API_URL: https://dtrack-api-rd.krohne.com DTRACK_API_URL: https://dtrack-api-rd.krohne.com
DEFECTDOJO_API_URL: https://defectdojo-rd.krohne.com DEFECTDOJO_API_URL: https://defectdojo-rd.krohne.com
KROHNE_CA_URL: https://devwiki.krohnegroup.com/lib/exe/fetch.php?media=krohne-ca.crt KROHNE_CA_URL: https://devwiki.krohnegroup.com/lib/exe/fetch.php?media=krohne-ca.crt
@@ -68,7 +69,7 @@ generate-defectdojo-api:
-o defectdojo-client \ -o defectdojo-client \
--package-name defectdojo_api --package-name defectdojo_api
dockerize: .dockerize:
stage: build stage: build
image: devnexus.krohne.com:18079/repository/docker-krohne/krohnedockerbash:0.5 image: devnexus.krohne.com:18079/repository/docker-krohne/krohnedockerbash:0.5
tags: tags:
@@ -76,51 +77,82 @@ dockerize:
- docker - docker
- bash - bash
rules: rules:
- if: '$CI_COMMIT_TAG' - if: '$CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "production_deployment"'
script: script:
- IMAGE_NAME=$IMAGE_NAME_PREFIX"-"$IMAGE_NAME_SUFFIX
- docker build --build-arg ADDITIONAL_CA_URL="$KROHNE_CA_URL" - docker build --build-arg ADDITIONAL_CA_URL="$KROHNE_CA_URL"
--build-arg ADDITIONAL_CA_CHECKSUM=$KROHNE_CA_CHECKSUM --build-arg ADDITIONAL_CA_CHECKSUM=$KROHNE_CA_CHECKSUM
--tag $IMAGE_NAME:latest --tag $IMAGE_NAME:latest-$CI_COMMIT_BRANCH
--tag $IMAGE_NAME:$CI_COMMIT_SHA --tag $IMAGE_NAME:$CI_COMMIT_SHA
--tag $IMAGE_NAME:$CI_COMMIT_TAG -f $DOCKERFILE
. .
- docker login -u $NEXUS_USER -p $NEXUS_PASSWORD $REGISTRY - docker login -u $NEXUS_USER -p $NEXUS_PASSWORD $REGISTRY
- docker push $IMAGE_NAME:latest - docker push $IMAGE_NAME:latest-$CI_COMMIT_BRANCH
- docker push $IMAGE_NAME:$CI_COMMIT_SHA - docker push $IMAGE_NAME:$CI_COMMIT_SHA
- docker push $IMAGE_NAME:$CI_COMMIT_TAG
build-windows-binary: dockerize-cli:
stage: build extends: .dockerize
tags: variables:
- windows IMAGE_NAME_SUFFIX: cli
- pwsh DOCKERFILE: Dockerfile-cli
- python3.13
rules: dockerize-server:
- if: '$CI_COMMIT_TAG' extends: .dockerize
artifacts: variables:
paths: IMAGE_NAME_SUFFIX: server
- sbom-dt-dd.exe DOCKERFILE: Dockerfile-server
.deploy:
stage: deploy
image: devnexus.krohne.com:18079/repository/docker-krohne/krohnedockerbash:0.5
variables:
GIT_STRATEGY: none
SERVICE: sbom-dd-dt-integrator
script: script:
- IMAGE_NAME=$IMAGE_NAME_PREFIX"-server"
- VERSION=$CI_COMMIT_SHA
- CONTAINER_NAME=$SERVICE"-"$INSTANCE_SPECIFIER
- SERVICE_VOLUME=$SERVICE"-"$INSTANCE_SPECIFIER"-data"
- docker volume inspect $SERVICE_VOLUME || docker volume create $SERVICE_VOLUME
- docker stop $CONTAINER_NAME || echo "$CONTAINER_NAME not running, anyway okay"
- docker rm $CONTAINER_NAME || echo "$CONTAINER_NAME not running, anyway okay"
- docker login -u $NEXUS_USER -p $NEXUS_PASSWORD $REGISTRY
- docker pull $IMAGE_NAME:$VERSION
- | - |
cd src cat - > /start-scripts/${CONTAINER_NAME}.sh << EOT
mv ..\dependencytrack-client . docker run \
mv ..\defectdojo-client . -d \
& 'C:\Program Files\Python313\python.exe' -m venv venv --restart always \
.\venv\Scripts\pip.exe install --upgrade pip -p 4701:8000 \
.\venv\Scripts\pip.exe install -r requirements.txt --name $CONTAINER_NAME \
.\venv\Scripts\pip.exe install -r dependencytrack-client\requirements.txt -e DTRACK_API_URL=$DTRACK_API_URL \
.\venv\Scripts\pip.exe install -r defectdojo-client\requirements.txt -e DTRACK_TOKEN=$DTRACK_TOKEN \
.\venv\Scripts\pip.exe install pyinstaller -e DEFECTDOJO_URL=$DEFECTDOJO_URL \
.\venv\Scripts\pyinstaller.exe --onefile ` -e DEFECTDOJO_TOKEN=$DEFECTDOJO_TOKEN \
--add-data "dependencytrack-client;dependencytrack-client" ` $IMAGE_NAME:$VERSION
--add-data "defectdojo-client;defectdojo-client" ` EOT
--hidden-import pydantic ` - chmod 755 /start-scripts/${CONTAINER_NAME}.sh
--hidden-import dateutil.parser ` - /start-scripts/${CONTAINER_NAME}.sh
--hidden-import urllib3 `
--hidden-import regex `
--collect-data cyclonedx `
--collect-data license_experssion `
sbom-dt-dd.py
mv dist\sbom-dt-dd.exe ..
deploy-test:
extends: .deploy
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
tags:
- test-deployment-de01rdtst01
variables:
INSTANCE_SPECIFIER: test
environment:
name: test
deploy-dev:
extends: .deploy
rules:
- if: '$CI_COMMIT_BRANCH == "production_deployment"'
tags:
- for-common-services-prod-deployment-only
variables:
INSTANCE_SPECIFIER: prod
environment:
name: prod

View File

@@ -34,7 +34,7 @@ COPY src/requirements.txt .
COPY src/sbom_dt_dd.py . COPY src/sbom_dt_dd.py .
COPY src/sbom_dt_dd_cli.py . COPY src/sbom_dt_dd_cli.py .
COPY src/converter.py . COPY src/converter.py .
COPY src/entrypoint.sh . COPY src/entrypoint-cli.sh .
COPY dependencytrack-client/ ./dependencytrack-client COPY dependencytrack-client/ ./dependencytrack-client
COPY defectdojo-client/ ./defectdojo-client COPY defectdojo-client/ ./defectdojo-client
@@ -45,7 +45,7 @@ RUN \
pip install -r dependencytrack-client/requirements.txt &&\ pip install -r dependencytrack-client/requirements.txt &&\
pip install -r defectdojo-client/requirements.txt pip install -r defectdojo-client/requirements.txt
ENTRYPOINT [ "./entrypoint.sh" ] ENTRYPOINT [ "./entrypoint-cli.sh" ]

52
Dockerfile-server Normal file
View File

@@ -0,0 +1,52 @@
FROM python:3.12.10-alpine3.22
ENV DTRACK_API_URL=""
ENV DTRACK_TOKEN=""
ENV DEFECTDOJO_URL=""
ENV DEFECTDOJO_TOKEN=""
ARG APP_DIR=/opt/app
ARG ADDITIONAL_CA_URL="x"
ARG ADDITIONAL_CA_CHECKSUM="y"
RUN \
set -e &&\
adduser -s /bin/sh -D user &&\
mkdir -p $APP_DIR &&\
chown user:user $APP_DIR &&\
echo $ADDITIONAL_CA_URL &&\
echo $ADDITIONAL_CA_CHECKSUM &&\
if [ "$ADDITIONAL_CA_URL" != "x" ]; then \
cd /usr/local/share/ca-certificates; \
wget --no-check-certificate -O custom-ca.crt $ADDITIONAL_CA_URL; \
echo "$ADDITIONAL_CA_CHECKSUM custom-ca.crt" | md5sum -c; \
/usr/sbin/update-ca-certificates; \
echo "custom ca added"; \
else \
echo "no additional ca"; \
fi
USER user
WORKDIR $APP_DIR
COPY src/requirements.txt .
COPY src/sbom_dt_dd.py .
COPY src/sbom_dt_dd_api.py .
COPY src/converter.py .
COPY src/entrypoint-server.sh .
COPY dependencytrack-client/ ./dependencytrack-client
COPY defectdojo-client/ ./defectdojo-client
RUN \
python -m venv .venv &&\
. ./.venv/bin/activate &&\
pip install -r requirements.txt &&\
pip install -r dependencytrack-client/requirements.txt &&\
pip install -r defectdojo-client/requirements.txt
EXPOSE 8000
ENTRYPOINT [ "./entrypoint-server.sh" ]

9
src/entrypoint-server.sh Executable file
View File

@@ -0,0 +1,9 @@
#!/bin/sh
source /opt/app/.venv/bin/activate
PYTHONPATH="$PYTHONPATH:/opt/app/dependencytrack-client"
PYTHONPATH="$PYTHONPATH:/opt/app/defectdojo-client"
export PYTHONPATH
gunicorn sbom_dt_dd_api:app -k uvicorn.workers.UvicornWorker -w 4 -b 0.0.0.0:8000

View File

@@ -5,3 +5,4 @@ cyclonedx-python-lib==10.4.1
fastapi==0.116.1 fastapi==0.116.1
gunicorn==23.0.0 gunicorn==23.0.0
uvicorn==0.35.0 uvicorn==0.35.0
python-multipart==0.0.20

View File

@@ -17,12 +17,13 @@ import dependencytrack_api
from dependencytrack_api.rest import ApiException as DependencyTrackApiException from dependencytrack_api.rest import ApiException as DependencyTrackApiException
class ApiException(Exception): class ApiException(Exception):
def __init__(self, status, reason, body, data, headers): def __init__(self, cause):
self.status = status self.cause = cause
self.reason = reason self.status = cause.status
self.body = body self.reason = cause.reason
self.data = data self.body = cause.body
self.headers = None self.data = cause.data
self.headers = cause.headers
class ApiCallExecutor: class ApiCallExecutor:
def __init__(self, verbose): def __init__(self, verbose):
@@ -50,7 +51,7 @@ class DefectDojoApiClient(defectdojo_api.ApiClient, ApiCallExecutor):
try: try:
return self.innerExecuteApiCall(ApiClass, EndpointMethod, RequestClass, requestParams, additionalParams) return self.innerExecuteApiCall(ApiClass, EndpointMethod, RequestClass, requestParams, additionalParams)
except defectdojo_api.exceptions.ApiException as e: except defectdojo_api.exceptions.ApiException as e:
raise ApiException(e.status, e.reason, e.body, e.data, e.headers) raise ApiException(e)
class DependencyTrackApiClient(dependencytrack_api.ApiClient, ApiCallExecutor): class DependencyTrackApiClient(dependencytrack_api.ApiClient, ApiCallExecutor):
def __init__(self, config, verbose): def __init__(self, config, verbose):
@@ -61,7 +62,7 @@ class DependencyTrackApiClient(dependencytrack_api.ApiClient, ApiCallExecutor):
try: try:
return self.innerExecuteApiCall(ApiClass, EndpointMethod, RequestClass, requestParams, additionalParams) return self.innerExecuteApiCall(ApiClass, EndpointMethod, RequestClass, requestParams, additionalParams)
except dependencytrack_api.exceptions.ApiException as e: except dependencytrack_api.exceptions.ApiException as e:
raise ApiException(e.status, e.reason, e.body, e.data, e.headers) raise ApiException(e)

View File

@@ -1,14 +1,18 @@
import os import os
import json
import yaml
from loguru import logger from loguru import logger
from fastapi import FastAPI, UploadFile, File, Form, HTTPException from fastapi import FastAPI, UploadFile, File, Form, HTTPException, Request
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse, HTMLResponse
from fastapi.templating import Jinja2Templates
from converter import minimalSbomFormatConverter from converter import minimalSbomFormatConverter
from sbom_dt_dd import generateSBOM, loadToDTrackAndDefectDojo, ApiException from sbom_dt_dd import generateSBOM, loadToDTrackAndDefectDojo, ApiException
app = FastAPI( app = FastAPI(
title="SBOM DTrack DefectDojo Synchronization API", title="SBOM DTrack DefectDojo Synchronization API",
version="0.0.1", version="0.0.1",
description="" description="",
root_path="/sbom-integrator/v1"
) )
config = {} config = {}
@@ -24,31 +28,103 @@ except KeyError as e:
app.state.config = config app.state.config = config
@app.get("/hello") @app.get("/upload-form", response_class=HTMLResponse)
async def say_hello(name: str): async def upload_form(request: Request):
""" """
Returns a friendly greeting. Route serving an HTML page with the upload form
---
parameters:
- name: name
in: query
required: true
schema:
type: string
responses:
200:
description: Successful Response
content:
application/json:
schema:
type: object
properties:
message:
type: string
""" """
return JSONResponse(content={"message": f"Hello, {name}!"}) # BY AWARE OF THE HARDCODED ROOT_PATH BELOW
html_content = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Upload Minimal SBOM</title>
</head>
<body>
<h1>Upload Minimal SBOM</h1>
<form id="sbomForm">
<label for="file">Select SBOM file:</label><br>
<input type="file" id="file" name="file" required><br><br>
@app.post("/uploadMinimalSBOM/") <label for="reimport">Reimport:</label>
<select name="reimport" id="reimport">
<option value="true">true</option>
<option value="false" selected>false</option>
</select><br><br>
<button type="submit">Upload SBOM</button>
</form>
<div id="result"></div>
<script>
document.getElementById("sbomForm").addEventListener("submit", async function(event) {
event.preventDefault();
let form = document.getElementById("sbomForm");
let formData = new FormData(form);
try {
let response = await fetch("/sbom-integrator/v1/upload-minimal-sbom/", {
method: "POST",
body: formData
});
let resultDiv = document.getElementById("result");
if (response.ok) {
let data = await response.json();
resultDiv.innerHTML = "<p style='color:green;'>Upload successful</p>";
} else {
let errorData = await response.json();
let detail = errorData.detail;
// Dynamisch HTML generieren
let html = "<p style='color:red;'>Upload failed:</p><ul>";
for (const [key, value] of Object.entries(detail)) {
html += "<li style='color:red'><strong>" + key + ":</strong> " + formatValue(value) + "</li>";
}
html += "</ul>";
resultDiv.innerHTML = html;
}
} catch (error) {
console.log(error);
document.getElementById("result").innerHTML = "<p style='color:red;'>Error: " + error + "</p>";
}
});
// Hilfsfunktion für verschachtelte Objekte
function formatValue(value) {
if (typeof value === 'object' && value !== null) {
return "<pre>" + escapeHtml(JSON.stringify(value, null, 2)) + "</pre>";
} else {
return escapeHtml(value);
}
}
function escapeHtml(unsafe) {
if (unsafe === null || unsafe === undefined) {
return '';
}
return String(unsafe)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
}
</script>
</body>
</html>
"""
return HTMLResponse(content=html_content)
@app.post("/upload-minimal-sbom/")
async def uploadMinimalSBOM( async def uploadMinimalSBOM(
file: UploadFile = File(...), file: UploadFile = File(...),
reimport: bool = Form(...) reimport: bool = Form(...)
@@ -56,17 +132,108 @@ async def uploadMinimalSBOM(
""" """
Endpoint to upload a minimal SBOM definition Endpoint to upload a minimal SBOM definition
""" """
sbom = await file.read()
try: try:
sbom = await file.read()
logger.info("Start converting from minimal format into cyclonedx") logger.info("Start converting from minimal format into cyclonedx")
(sbom, projectName, projectVersion, projectClassifier, projectDescription) = minimalSbomFormatConverter(sbom) (sbom, projectName, projectVersion, projectClassifier, projectDescription) = minimalSbomFormatConverter(sbom)
logger.info("Converted") logger.info("Converted")
loadToDTrackAndDefectDojo(app.state.config, projectName, projectVersion, projectClassifier, projectDescription, 1, sbom, reimport) loadToDTrackAndDefectDojo(app.state.config, projectName, projectVersion, projectClassifier, projectDescription, 1, sbom, reimport)
logger.info("Done.") logger.info("Done.")
except yaml.scanner.ScannerError as e:
logger.warning(f"uploadMinimalSBOM, yaml ScannerError: {e.context=}, {e.context_mark=}, {e.problem=}, {e.problem_mark=}, {e.note=}")
raise HTTPException(
status_code=400,
detail={
"error": "yaml ScannerError",
"context": e.context,
"context_mark": str(e.context_mark),
"problem": e.problem,
"problem_mark": str(e.problem_mark),
"note": e.note
}
)
except ApiException as e: except ApiException as e:
raise HTTPException(status_code=e.status, detail=f"{e.reason=}, {e.body=}, {e.data=}") logger.warning(f"uploadMinimalSBOM, ApiException: {type(e.cause)=}, {e.status=}, {e.reason=}, {e.body=}")
raise HTTPException(
status_code=e.status,
detail={
"type": str(type(e.cause)),
"reason": e.reason,
"body": e.body,
"data": e.data
}
)
except Exception as e:
logger.warning(f"uploadMinimalSBOM, Exception: {type(e)=}, {str(e)=}")
raise HTTPException(
status_code=500,
detail={
"error": "Exception occurred",
"type": str(type(e)),
"message": str(e)
}
)
return JSONResponse(content={
"message": "Upload successful!"
})
@app.post("/upload-sbom/")
async def uploadSBOM(
file: UploadFile = File(...),
projectName: str = Form(...),
projectVersion: str = Form(...),
projectClassifier: str = Form(...),
projectDescription: str = Form(...),
reimport: bool = Form(...)
):
"""
Endpoint to upload a CycloneDX SBOM
"""
sbom = await file.read()
try:
sbomJson = json.loads(sbom)
sbom = json.dumps(sbomJson)
loadToDTrackAndDefectDojo(app.state.config, projectName, projectVersion, projectClassifier, projectDescription, 1, str(sbom), reimport)
logger.info("Done.")
except json.decoder.JSONDecodeError as e:
logger.warning(f"uploadSBOM, JSONDecodeError: {e.msg=}")
raise HTTPException(
status_code=400,
detail={
"error": "JSON decoding error",
"msg": e.msg,
"doc": e.doc,
"pos": e.pos,
"lineno": e.lineno,
"colno": e.colno
}
)
except ApiException as e:
logger.warning(f"uploadSBOM, ApiException: {type(e.cause)=}, {e.status=}, {e.reason=}, {e.body=}")
raise HTTPException(
status_code=e.status,
detail={
"type": str(type(e.cause)),
"reason": e.reason,
"body": e.body,
"data": e.data
}
)
except Exception as e:
logger.warning(f"uploadSBOM, Exception: {type(e)=}, {str(e)=}")
raise HTTPException(
status_code=500,
detail={
"error": "Exception occurred",
"type": str(type(e)),
"message": str(e)
}
)
return JSONResponse(content={ return JSONResponse(content={
"message": "Upload successful!" "message": "Upload successful!"

View File

@@ -1,3 +0,0 @@
#!/bin/bash
./.venv/bin/gunicorn sbom_dt_dd_api:app -k uvicorn.workers.UvicornWorker -w 4 -b 0.0.0.0:8000