OCCTET D4.1 – Ready-to-Use Installation Source on GitLab

D4.1 – Ready-to-Use Installation of the OCCTET Toolkit #

Reference installation of the OCCTET toolkit as a containerised cloud service, with its documentation

1. Document information #

ItemValue
Project titleOpen Source Compliance Comprehensive tools and resources designed to simplify and streamline the CRA compliance process for SME, allowing them to tackle the complexities of OSS compliance
Project acronymOCCTET
Grant agreement101190474
ProgrammeDigital Europe Programme (DIGITAL), call DIGITAL-ECCC-2024-DEPLOY-CYBER-06
InstrumentDIGITAL JU SME Support Action
Granting authorityEuropean Cybersecurity Industrial, Technology and Research Competence Centre (ECCC)
Project start and duration1 November 2024, 24 months
DeliverableD4.1 – Ready to use installation
Deliverable typeDEM – Demonstrator, pilot, prototype
Work package / taskWP4 – Validation – Use Cases / T4.3
Dissemination levelPU – Public
Due date (DoA)31 October 2026 (month 24)
Submission date31 October 2026 (planned)
Version1.0 (draft 7)
StatusDraft – not yet approved
Date15 October 2026
Lead beneficiaryDO – Double Open
Main author(s)Martin von Willebrand, Sebastian Schuberth, … (list to be completed)
Contributing partner(s)DO, BS, ECL
ReviewerECL – Eclipse Foundation Europe
LicenceCC BY 4.0 (Attribution 4.0 International)
Sourcehttps://gitlab.eclipse.org/eclipsefdn/open-source-services/occtet-eu/reference-installation/reference-installation-documentation

Version history

VersionDateIssued byStatusDescription
0.12026-10-06DODraftFirst draft
0.22026-10-15ECLDraftRevised after internal review: deployment artefacts, traceable demonstration, SME feedback, corrected statistics, backup and continuity status, operational readiness matrix

Legal notice. This deliverable has been produced within the OCCTET project (Grant Agreement No. 101190474) funded under the Digital Europe Programme. Co-funded by the European Union. Views and opinions expressed are however those of the author(s) only and do not necessarily reflect those of the European Union or the European Cybersecurity Industrial, Technology and Research Competence Centre. Neither the European Union nor the granting authority can be held responsible for them.

Co-funded by the European Union and the European Cybersecurity Competence Centre (ECCC)

2. Executive summary #

Deliverable D4.1 describes the reference implementation of the OCCTET toolkit and provides deployment and operational documentation written in English and maintained in Markdown. Built entirely from components released under open-source licences, the reference implementation is publicly available on GitLab for anyone to deploy.

The reference instance runs as a containerised service on cloud infrastructure, in line with Task T4.3. Ongoing work focuses on assessing operational scalability and establishing a path towards a fully operational software-as-a-service offering.

Scope of the reference instance.

Four services run on OVHcloud Managed Kubernetes in Strasbourg, France:

For vulnerability data, ORT Server and the Curator query 2 external services: OSV and the public VulnerableCode service, https://public.vulnerablecode.io or the OCCTET instance: https://vulnerablecode.occtet.eu, part of the federated software metadata platform developed in WP3 (D3.1, D3.4).

Installation

Every component is installed with Helm. Upstream, these are the Eclipse Apoapsis chart for ORT Server, the Bitsea chart for the Curator and the CloudPirates chart for Keycloak. VulnerableCode is installed with an OCCTET specific helm chart. The OCCTET-specific deployment material is kept in 8 repositories of the Eclipse Foundation GitLab group occtet-eu/reference-installation. One of them, occtet-helm-chart, installs the whole platform, one Helm release per component. GitLab CI also deploys the ORT Server stack, the Curator and Keycloak on every merge.

Validation evidence

ClaimEvidence
Analysis at scale - Eclipse Foundation portfolioOf the 4,125 repositories registered for the Eclipse Foundation projects and the Eclipse Research Labs, 3,649 were analysed at least once and 2,980 have at least one completed analysis
SMEs can use the toolkit (Milestone 8)10 SMEs engaged; 8 of them tested ORT Server on 19 of their products, above the target of 15 projects or solutions. Seven tested on Double Open’s own hosted ORT Server instance for early access. Qualitative feedback from three SMEs (section 14.3)
Iconic open source projects (Milestone 9)Run data confirmed for 4 of the 7 candidate projects named in the Grant Agreement: Adoptium, Eclipse IDE, Eclipse Mosquitto and Jetty (section 14.4)
The toolkit applies to the wider open source ecosystem96 widely used projects in 10 language ecosystems analysed on the OCCTET instance: 10,043 packages identified (section 14.5)
Vulnerability data is collected7,184 distinct vulnerability identifiers stored by ORT Server(section 14.6, Consolidated results of the instance)

Operations and continuity

Following the project’s closure on 31 October 2026, the Eclipse Foundation will continue hosting the platform until early 2027.

The software will remain available beyond the lifetime of the hosted reference instance. ORT Server is developed within Eclipse Apoapsis, an Eclipse Foundation project. The other components are also publicly available on GitHub under open-source licences, allowing continued use, community contributions and independent deployments (Section 15.1, Hosting and continuity).

3. Table of contents #

4. Introduction #

4.1. Purpose and scope #

This document describes the reference installation delivered by task T4.3. It is written for three audiences:

Annexes A to C hold the glossary, the detailed results and definitions of the statistics, and the container images; Annex D holds the configuration reference.

The document describes the installation as configured in its deployment repositories and measurements on the instance (statistics, storage use, costs) date from 30 October 2026. Section 14 states the sources of the validation figures, and Annex B gives the definitions of the statistics of the instance and the script that extracts them.

4.2. Grant Agreement reference #

D4.1 corresponds to a deliverable card of Work Package 4 in Annex 1 of the Grant Agreement. Its official description is: “Reference implementation available to partners with a documented process in English using markdown”. It is a public demonstrator, due at month 24 (31 October 2026) and led by Double Open. It evidences the following commitments:

4.3. Relationship to WP3 deliverables and to D4.2 #

D4.1 builds on the toolkit deliverables of Work Package 3:

D4.2 – Tools Comparison Report describes the testing framework used to validate the tools and compares the OCCTET tools with other solutions. It contains the full analysis of the SME feedback summarised in section 14.3.

4.4. Expected results of task T4.3 #

Expected result of T4.3How it is addressedSections
Deploy the WP3 tools as a ready-to-use reference solution, in the form of a cloud service using containersProduction instance on OVHcloud Managed Kubernetes; all components containerised and installed with Helm; deployment material in version-controlled repositories4.5, 5, 6, 11
Integrate the components into a toolchain that allows end-to-end testsShared identity realm; ORT Server analysis are automatically exported to the Curator, or the Curator starts ORT Server runs and imports their results; vulnerability services connected6.9, 14.1
Demonstrate that the solution is usable by SMEsSign-in, web interfaces access, data isolation per organisation; testing of ORT Server and the Curator by SMEs on their own products5.3, 13, 14.3
Show the feasibility of a service at scale and the path to a fully operational SaaSAnalysis campaign on the Eclipse Foundation portfolio; benchmark of 96 open source projects; infrastructure cost model14.2, 14.5, 15

Section 14.8 (Assessment against the Grant Agreement commitments) assesses each of these results.

4.5. Delivered artefacts #

The deployment repositories belong to the group occtet-eu/reference-installation of the Eclipse Foundation GitLab, abbreviated `` below.

ArtefactLocationContentLicence
Running instancehttps://ort-server.occtet.eu, https://curator.occtet.eu, https://keycloack.occtet.eu, https://vulnerablecode.occtet.euORT Server, Occtet Curator, Keycloak, VulnerableCode–
This documentreference-installation-documentationD4.1 in Markdown, also published as a web page by GitLab Pages (Hugo site in site/)CC BY 4.0
Installation of the platformocctet-helm-chartMakefile that installs each component as its own Helm release, in production or on a local minikube cluster (section 12); chart occtet-entrypoint (ClusterIssuers or a self-signed CA, TLS Ingresses, nginx reverse proxies); values of ingress-nginx and cert-manager, with their development overrides; the chart repositories of ORT Server, the Curator and VulnerableCode, referenced as git submodules at a pinned commit; RenovateEPL-2.0
Keycloak deploymentocctet-keycloakWrapper chart occtet-keycloak, values, Makefile, CI deployment, Renovate; development realm occtet, imported at startup on a local clusterEPL-2.0
Keycloak themeocctet-keycloak-themeKeycloakify theme sources, Storybook, CI that publishes the theme packageMIT
ORT Server deploymentocctet-ort-server-helm-chartUmbrella chart prod-stack, values, Makefile, CI deployment, Renovate, footerEPL-2.0
ORT Server configurationocctet-ort-server-configORT Server parameter validation script, evaluator rules, licence classifications, copyright garbage listApache-2.0
Occtet Curator deploymentocctet-curator-helm-chartUmbrella chart occtet-curator-stack, which depends on the Bitsea chart occtet-curator 0.1.0; OCCTET values and their development overrides, Makefile with the secret script, CI deployment, RenovateEPL-2.0
VulnerableCode deploymentocctet-vulnerablecode-helm-chartChart vulnerablecode (web UI and REST API, RQ scheduler and workers, PostgreSQL, Redis), OCCTET values and branding, Makefile, examplesEPL-2.0
Portfolio and access scriptsocctet-eclipse-projectssync-eclipse-projects.sh, sync-eclipse-research-projects.sh, bulk-run.sh, bulk-run-stop.sh; grant-users-access.sh and the schedule CI pipeline that runs it; documentationEPL-2.0
Statistics and dashboardsocctet-statsstatistics.sh, which extracts the statistics of section 14 and Annex B; chart occtet-stats: the Grafana dashboards of ORT Server and the CronJob that precomputes their vulnerabilities, with the PostgreSQL roles, the datasource, a Makefile and tests on Docker (section 9.4); documentationEPL-2.0
Upstream chartshttps://github.com/eclipse-apoapsis/helm-charts, https://github.com/Bitsea/Occtet-Curator-Helm-Chart, https://github.com/CloudPirates-io/helm-chartsORT Server, Occtet Curator and Keycloak charts, and the PostgreSQL, RabbitMQ and Redis chartsSee repositories

All these repositories are public. The OCCTET deployment material is licensed under the Eclipse Public License 2.0, except the Keycloak theme, which keeps the MIT licence of the Keycloakify starter it derives from, and the ORT Server configuration, which keeps the Apache 2.0 licence of the ORT Server example configuration. Annex C lists the container images of the installation and their versions.

4.6. Components and scope #

ComponentRoleStatus for this installation
ORT ServerServer edition of the OSS Review Toolkit. Analyses repositories with a pipeline of workers: analyzer, advisor, scanner, evaluator, reporter. Offers a REST API, a React-based web UI and a CLI (osc). Re-running an analysis, by hand, through the API or from a scheduled CI pipeline, picks up vulnerabilities published since the previous run.Deployed on this instance
Occtet CuratorImports, curates and assesses SBOMs: inventory items, licences, copyrights, vulnerabilities, VEX statements, SPDX and CycloneDX exports. Starts ORT Server runs and imports their results. Its AI-assisted services use a local LLM to filter false copyright findings and to identify licences, without sending data to external AI services.Deployed on this instance
Keycloak with the OCCTET themeSingle sign-on for ORT Server and the Curator (realm occtet)Deployed on this instance
ScanCodeLicence and copyright scannerRun by the ORT Server scanner worker inside the cluster (section 21.1)
VulnerableCodeVulnerability database (AboutCode). Aggregates advisories from many sources and serves them by package URL, through a web UI and a REST API.Used in two ways (section 21.3). The public service https://public.vulnerablecode.io is queried by ORT Server and the Curator, with an API key. A local instance runs on this cluster, installed with the OCCTET VulnerableCode chart. Available at https://vulnerablecode.occtet.eu (sections 6.8 and 11.9).
OSVOpen-source vulnerability databaseConsumed as an external service: https://api.osv.dev
Federated software metadata platform (WP3: D3.1, D3.4)VulnerableCode and the wider AboutCode/FederatedCode ecosystem: package origin, licence metadata, vulnerabilities and SBOMs, identified by package URLUsed as a data source through the public VulnerableCode service: the toolchain does not require installing it. It is based on open source projects and can be operated independently; the local VulnerableCode instance above runs its vulnerability database on this cluster.

5. Reference instance and access #

5.1. Public endpoints #

ServiceURLPurpose
ORT Server – web UIhttps://ort-server.occtet.euManage organisations, products and repositories; start runs; browse results; download reports
ORT Server – REST APIhttps://ort-server.occtet.eu/api/v1Programmatic access (reference: https://eclipse-apoapsis.github.io/ort-server/api/ort-server-api)
ORT Server – token pagehttps://ort-server.occtet.eu/profile/tokenLong-lived token for the CLI and scripts
Occtet Curatorhttps://curator.occtet.euRedirects to the application, served under /boc
Keycloak – realm occtethttps://keycloack.occtet.eu/realms/occtetOpenID Connect issuer; discovery document at /.well-known/openid-configuration
Keycloak – account consolehttps://keycloack.occtet.eu/realms/occtet/accountSelf-service management of profile, password and linked accounts
VulnerableCode – OCCTET instancehttps://vulnerablecode.occtet.euWeb UI and REST API (/api/v3/) of the local vulnerability database, without account (section 6.8, Vulnerability database: VulnerableCode)

5.2. Component versions #

Versions of the reference state, as configured in the deployment repositories on 15 October 2026:

ComponentVersion
Kubernetes (OVHcloud MKS)1.33
ORT Server (core, UI, orchestrator, workers, maintenance tasks)0.95.0, installed with Helm chart ort-server 0.24.3
Keycloak26.8.0, installed with the CloudPirates keycloak chart 0.21.46
Occtet Curator (frontend and microservices)latest images published by Bitsea (Annex C); Bitsea Helm chart occtet-curator 0.1.0 in the umbrella chart occtet-curator-stack 0.1.0, since 20 October 2026 (before, a copy of the chart, version 0.0.1)
VulnerableCode40.1.0, installed with Helm chart vulnerablecode 0.0.1
PostgreSQL18.6 (ORT Server and VulnerableCode), 18.0 (Keycloak), 17 with pgvector (Curator)
RabbitMQ4.3.6
Redis8.10.2 (VulnerableCode)
NATS JetStream, Ollamalatest images (Annex C); LLM model qwen3:8b
Loki, Promtail, Grafana2.6.1, 3.5.1, 10.3.3 (chart loki-stack 2.10.3)
ingress-nginxController 1.13.2 (chart 4.13.2)
cert-manager1.18.2

5.3. Access modalities #

Requesting access. Organisations request access by e-mail to [email protected], as described in the toolkit’s getting-started guide (https://occtet.eu/toolkit/getting-started/). The request gives the organisation, the tools to try, the technologies of the project and whether the repository is public or private. It never contains credentials.

ORT Server and the Curator share the Keycloak realm occtet, so one account gives access to both. Access is granted in three steps.

  1. First sign-in. The user opens either tool and is redirected to the OCCTET login page. There they either create an account, using their e-mail address as user name and choosing a password, or select Sign in with GitHub.
  2. Automatic access. Every hour, a scheduled job gives every account of the realm read access to the public organisations of ORT Server, and the realm role curator, which opens the Curator (section 8.3.1, Granting permissions). A new user therefore reads the results of the public organisations within the hour after the first sign-in, without waiting for an administrator.
  3. Permissions by profile. An administrator grants the other permissions according to the user’s profile.

The public organisations of ORT Server hold the results of open source projects only:

OrganisationContent
FOSS ValidationOne product per iconic project named in the Grant Agreement (section 14.4, Iconic open source projects)
Renowned FOSS projectsThe benchmark of widely used projects, one product per language ecosystem (section 14.5, General open source ecosystem benchmark)
eclipse-projectsThe Eclipse Foundation projects (section 14.2, Scenario B – Analysis campaign on the Eclipse Foundation portfolio)
eclipse-research-labsThe Eclipse Research Labs projects (same section)

The organisations of SMEs and partners are not public: only the users an administrator adds to them can see them. Permissions by profile:

ProfileORT ServerOcctet CuratorKubernetes cluster
Onboarded SME or open-source projectReader on the public organisations (automatic); role (reader, writer or admin) on its own organisation, products or repositoriescurator role (automatic); member of its organisation, added by a Curator admin; sees only its organisation’s dataNo access
Consortium partner working on a given serviceRights matching the service: roles on organisations, or superuser for those maintaining the platformadmin or curator role, as neededA ServiceAccount restricted to the namespace of the service the partner works on, for example the Curator team on namespace curator
Platform operatorsSuperuser, granted under Administration → User Management → UsersadminCluster administration

The OCCTET VulnerableCode instance, https://vulnerablecode.occtet.eu, is not connected to the realm: anyone can query it without an account (section 6.8, Vulnerability database: VulnerableCode). Partners can also query the public VulnerableCode service, with an API key of their own (section 5.1, Public endpoints).

5.4. Programmatic access: API, CLI and CI #

Interactive token. The page https://ort-server.occtet.eu/profile/token uses the public client ort-server-cli to issue an offline refresh token for the osc CLI and for scripts. Exchange it for an access token, then call the API:

REFRESH_TOKEN='<token copied from /profile/token>'

ACCESS_TOKEN=$(curl -s -X POST \
  https://keycloack.occtet.eu/realms/occtet/protocol/openid-connect/token \
  -d grant_type=refresh_token \
  -d client_id=ort-server-cli \
  -d refresh_token="$REFRESH_TOKEN" | jq -r .access_token)

curl -H "Authorization: Bearer $ACCESS_TOKEN" \
  https://ort-server.occtet.eu/api/v1/organizations

The API answers 401 when the token lacks the ort-server audience or has the wrong issuer. It answers 403 when the token is valid but the user has no permission on the resource.

CI pipelines. The confidential client ort-ci generates a user token. Projects store that token in their CI and use it to trigger analyses through the API.

CLI discovery. GET https://ort-server.occtet.eu/api/v1/auth/oidc-config/cli returns the token endpoint and the client ID the CLI must use.

6. Architecture #

6.1. Overview #

flowchart TB
    users(["Users, partners and SMEs<br/>browsers, osc CLI, CI pipelines"])
    lb["OVHcloud Public Cloud Load Balancer, size S<br/>with Floating IP and Gateway"]
    ext["External services<br/>public VulnerableCode, OSV, advisory sources,<br/>Git hosting, container registries, Let's Encrypt"]

    users -->|"HTTPS"| lb

    subgraph mks["OVHcloud Managed Kubernetes Service - SBG5 Strasbourg - 6 nodes"]
        subgraph nsing["namespace ingress-nginx"]
            ic["ingress-nginx controller<br/>TLS termination"]
        end
        subgraph nscm["namespace cert-manager"]
            cm["cert-manager<br/>Let's Encrypt ClusterIssuer"]
        end
        subgraph nsdef["namespace default - entry point"]
            ing["Ingress per host name"]
            pxo["nginx proxy<br/>ORT Server"]
            pxc["nginx proxy<br/>Curator"]
            pxk["nginx proxy<br/>Keycloak"]
            pxv["nginx proxy<br/>VulnerableCode"]
        end
        subgraph nsort["namespace ort-server"]
            oui["ORT Server UI"]
            ocore["ORT Server core<br/>REST API"]
            oorch["Orchestrator"]
            owk["Workers as Kubernetes Jobs<br/>config, analyzer, advisor,<br/>scanner with ScanCode, evaluator, reporter"]
            omq[["RabbitMQ"]]
            odb[("PostgreSQL")]
            oobs["Loki, Promtail, Grafana"]
        end
        subgraph nscur["namespace curator"]
            cfe["Curator frontend /boc"]
            cms["13 microservices"]
            cnats[["NATS JetStream"]]
            cdb[("PostgreSQL + pgvector")]
            cai["Ollama LLM"]
            cnfs["NFS provisioner<br/>shared project data"]
        end
        subgraph nskc["namespace keycloak"]
            kc["Keycloak<br/>realm occtet, OCCTET theme"]
            kdb[("PostgreSQL")]
        end
        subgraph nsvc["namespace vulnerablecode"]
            vweb["VulnerableCode web<br/>UI and REST API"]
            vwk["RQ scheduler and workers<br/>importer and improver pipelines"]
            vredis[["Redis"]]
            vdb[("PostgreSQL")]
        end
    end

    lb --> ic --> ing
    cm -.->|"certificates"| ing
    ing --> pxo & pxc & pxk & pxv
    pxo -->|"/"| oui
    pxo -->|"/api"| ocore
    pxc --> cfe
    pxk --> kc
    pxv --> vweb
    ocore --> omq
    oorch --> omq
    oorch --> owk
    ocore --> odb
    owk --> odb
    ocore -.->|"run logs"| oobs
    cfe --> cnats
    cms --> cnats
    cfe --> cdb
    cms --> cdb
    cms --> cai
    cfe --> cnfs
    kc --> kdb
    vweb --> vdb
    vweb --> vredis
    vwk --> vredis
    vwk --> vdb
    cms -->|"ORT Server API, public URL"| ocore
    owk --> ext
    cms --> ext
    vwk -->|"advisory imports"| ext

Figure 1 – Reference architecture of the installation.

Four principles shape the architecture:

6.2. Hosting platform #

ItemReference installation
ProviderOVHcloud Public Cloud
Kubernetes serviceOVHcloud Managed Kubernetes Service (MKS), Free plan. OVHcloud operates the control plane (99.5% SLO).
RegionSBG5 – Strasbourg, France (European Union)
Kubernetes version1.33
Node pool6 nodes: 4 × b2-15 (4 vCPU, 15 GB RAM) and 2 × b2-30 (8 vCPU, 30 GB RAM), i.e. 32 vCPU and 120 GB RAM in total
Node operating systemUbuntu 22.04 LTS (managed by OVHcloud)
Network pluginCanal (Calico and Flannel), managed by OVHcloud
Persistent storageOpenStack Cinder CSI block storage, classes csi-cinder-classic and csi-cinder-high-speed (cluster default); volumes in section 21.6
Load balancingOVHcloud Public Cloud Load Balancer (size S), created by the LoadBalancer Service of the ingress controller
DDoS protectionStandard OVHcloud anti-DDoS protection, included with Public Cloud

6.3. Namespace layout #

NamespaceContentHelm chart (repository)
ingress-nginxingress-nginx controller and its LoadBalancer ServiceUpstream ingress-nginx 4.13.2 (kubernetes/ingress-nginx); values in occtet-helm-chart
cert-managercert-manager controller, webhook and CA injectorUpstream jetstack/cert-manager v1.18.2 (cert-manager/cert-manager); values in occtet-helm-chart
defaultEntry point: one Ingress per host name and four nginx reverse proxies. The same chart creates the cluster-wide ClusterIssuers.occtet-entrypoint (occtet-helm-chart)
keycloakKeycloak and its PostgreSQL database; GitLab Agent used by the CIWrapper chart occtet-keycloak (occtet-keycloak)
ort-serverORT Server core, UI, orchestrator and workers; PostgreSQL; RabbitMQ; Loki, Promtail and Grafana; housekeeping CronJobs; GitLab Agent used by the CI; dashboards of ORT Server and the CronJob occtet-stats-refreshUmbrella chart prod-stack (occtet-ort-server-helm-chart); chart occtet-stats (occtet-stats)
curatorCurator frontend, 13 microservices, PostgreSQL with pgvector, NATS JetStream, Ollama, NFS provisioner; GitLab Agent used by the CIUmbrella chart occtet-curator-stack, with the Bitsea chart occtet-curator as dependency (occtet-curator-helm-chart)
vulnerablecodeVulnerableCode web with its nginx sidecar, RQ scheduler, RQ workers (six with the values, section 6.8), PostgreSQL, Redis, two NetworkPoliciesvulnerablecode (occtet-vulnerablecode-helm-chart)
kube-systemCluster components managed by OVHcloud (network plugin, DNS, metrics-server, API connectivity)OVHcloud

6.4. Entry point and reverse-proxy layer #

A request travels as follows:

C4Deployment
    title Path of a request to a public host name

    Person(client, "Client", "Browser, osc CLI, script or CI pipeline")

    Deployment_Node(ovh, "OVHcloud Public Cloud", "Region SBG5, Strasbourg") {
        Container(lb, "Load Balancer", "OVHcloud Public Cloud Load Balancer", "Floating IP of the four host names")
        Deployment_Node(mks, "Managed Kubernetes cluster", "OVHcloud MKS") {
            Deployment_Node(ns_ingress, "ingress-nginx", "Namespace") {
                Container(controller, "ingress-nginx controller", "nginx", "TLS termination; certificates from cert-manager")
            }
            Deployment_Node(ns_default, "default", "Namespace") {
                Container(ingress, "Ingress of the host name", "Ingress", "One per host name, read by the controller")
                Container(proxy, "Reverse proxy of the service", "nginx", "Path filtering, forwarded headers")
            }
            Deployment_Node(ns_service, "Namespace of the service", "ort-server, curator, keycloak or vulnerablecode") {
                Container(service, "Service", "Kubernetes Service", "Reached by its cluster DNS name")
            }
        }
    }

    Rel(client, lb, "1. Request", "HTTPS")
    Rel(lb, controller, "2. Forwards", "TCP")
    Rel(controller, ingress, "3. Applies the host rule")
    Rel(ingress, proxy, "4. Routes", "HTTP")
    Rel(proxy, service, "5. Forwards", "HTTP")

    UpdateRelStyle(client, lb, $offsetX="10", $offsetY="-40")
    UpdateRelStyle(lb, controller, $offsetX="-90", $offsetY="-60")
    UpdateRelStyle(controller, ingress, $offsetX="-55", $offsetY="-30")
    UpdateLayoutConfig($c4ShapeInRow="2", $c4BoundaryInRow="3")

Figure 2 – Path of a request through the entry point (C4 deployment view). TLS ends at the ingress controller; inside the cluster, the request travels in plain HTTP.

The reverse proxy reaches the service by its cluster DNS name, <service>.<namespace>.svc.cluster.local, given for each host name in the column Upstream of the table below.

Host nameIngressReverse proxyRouting rulesUpstream
ort-server.occtet.euingress-ort-servernginx-ort-proxy/api → core REST API; everything else → web UI. Forwarded headers, the Authorization header and cookies are passed through.ort-server-core.ort-server, ort-server-ui.ort-server
curator.occtet.euingress-curatornginx-curator-proxy/ → redirect to /boc; all paths → frontend; WebSocket upgrade enabledocctet-curator-frontend.curator
keycloack.occtet.euingress-keycloacknginx-keycloack-proxyAllow-list of the paths needed by OpenID Connect clients and the administration console (/realms/, /resources/, /.well-known/, /api/, /admin/); / redirects to the login of the administration console; /metrics and /health blocked; all other paths denied; sticky upstream; custom page for 404 and 5xx errorsocctet-keycloak.keycloak
vulnerablecode.occtet.euingress-vulnerablecodenginx-vulnerablecode-proxyAll paths → web service; forwarded headers passed through; WebSocket upgrade enabledvulnerablecode-web.vulnerablecode

The Ingresses, proxies and ClusterIssuers are defined by the chart occtet-entrypoint of occtet-helm-chart, whose values hold the ACME contact address; the Makefile gives it the domain of the host names (DOMAIN). All four proxies use the nginx:stable image, read their configuration from a ConfigMap, and expose a local /alive endpoint. The chart uses /alive for the readiness and liveness probes of the proxies and for its Helm test.

6.5. Identity service: Keycloak #

6.6. Analysis service: ORT Server #

ORT Server is installed with the umbrella chart prod-stack (0.1.1) of the repository occtet-ort-server-helm-chart. The repository does not keep a Chart.lock; the exact dependency versions below are pinned in Chart.yaml. GitLab CI deploys the chart on every merge to the default branch, through the GitLab Agent of namespace ort-server.

DependencyChart versionApplication versionSource
ort-server0.24.3ORT Server 0.95.0oci://ghcr.io/eclipse-apoapsis/charts – https://github.com/eclipse-apoapsis/helm-charts
postgres0.21.2PostgreSQL 18.6oci://registry-1.docker.io/cloudpirates
rabbitmq0.21.31RabbitMQ 4.3.6oci://registry-1.docker.io/cloudpirates
loki-stack2.10.3Loki 2.6.1, Promtail 3.5.1, Grafana 10.3.3https://grafana.github.io/helm-charts

Runtime components

The core, the orchestrator and the workers exchange messages through RabbitMQ. Worker executions are started as Kubernetes Jobs, with CPU and memory requests and limits set per worker (section 21.6).

Configuration and data

6.7. Curation service: Occtet Curator #

The Curator is installed with the umbrella chart occtet-curator-stack (0.1.0) of the repository occtet-curator-helm-chart (section 4.5), the same layout as the ORT Server repository (section 6.6). Its only dependency is the Bitsea chart occtet-curator, pinned in Chart.yaml, whose package carries its own PostgreSQL and NFS provisioner dependencies. The repository holds no chart templates: its values.yaml only sets what the OCCTET instance changes in the defaults of the Bitsea chart, under the key occtet-curator (section 21.2), and its values-dev.yaml the overrides of the local installation (section 12). GitLab CI deploys the repository root with values.yaml on every merge to the default branch, through the GitLab Agent of namespace curator.

DependencyChart versionApplication versionSource
occtet-curator0.1.0Curator latest images (Annex C)oci://ghcr.io/bitsea/charts – https://github.com/Bitsea/Occtet-Curator-Helm-Chart
postgres, alias postgresql (dependency of occtet-curator)0.20.4PostgreSQL 17 with pgvector (pgvector/pgvector:pg17-trixie)oci://registry-1.docker.io/cloudpirates
nfs-server-provisioner, alias nfsprovisioner (dependency of occtet-curator)1.8.0nfs-provisioner 4.0.8https://kubernetes-sigs.github.io/nfs-ganesha-server-and-external-provisioner/
ComponentRole
FrontendJmix and Vaadin web application on port 8090, served under /boc; Spring profiles live,oidc
ort-run-start-service, process-run-serviceStart ORT Server runs and import their results
spdx-service, cyclonedx-serviceImport SPDX and CycloneDX documents
spdx-export-service, cyclonedx-export-serviceExport curated SBOMs
licensematcher-service, ai-licensematcher-serviceLicence identification, rule-based and AI-assisted
copyrightfilter-service, ai-copyrightfilter-serviceCopyright filtering, rule-based and AI-assisted
vulnerability-serviceVulnerability data from VulnerableCode
foss-report-serviceFOSS compliance reports
download-serviceDownloads project sources into the shared project-data volume
NATS JetStreamMessage broker between the frontend and the microservices (stream occtet, subjects work.>)
PostgreSQL 17 + pgvectorApplication database
OllamaLocal LLM runtime for the AI-assisted services (model qwen3:8b, CPU only)
NFS provisionerReadWriteMany storage class for the shared project-data volume

The Curator images are pulled from ghcr.io/bitsea with the latest tag and pull policy Always. Annex C lists them.

6.8. Vulnerability database: VulnerableCode #

Besides the public VulnerableCode service (section 21.3, VulnerableCode), the platform runs its own VulnerableCode instance, in namespace vulnerablecode, published through the entry point at https://vulnerablecode.occtet.eu.

Chart. The instance is installed from the repository occtet-vulnerablecode-helm-chart (section 4.5), with the OCCTET chart vulnerablecode 0.0.1. The chart mirrors the upstream docker-compose.yml: same image, same commands, and nginx in front of gunicorn. It deploys VulnerableCode 40.1.0 (image ghcr.io/aboutcode-org/vulnerablecode), with the CloudPirates postgres chart 0.21.1 (PostgreSQL 18.6) and redis chart 0.36.0 (Redis 8.10.2), both locked by Chart.lock. The chart’s own Ingress is disabled.

ComponentKindRole
webDeployment, 1 replicaDjango application under gunicorn: web UI and REST API. Init containers wait for PostgreSQL, apply the database migrations and collect the static files. An nginx sidecar serves the static files, including the OCCTET branding (section 7.3).
schedulerDeployment, 1 replicaSchedules the importer and improver pipelines in Redis (rq-scheduler)
worker-defaultDeployment, 4 replicasRuns the pipelines of the default queue
worker-highDeployment, 2 replicasRuns the pipelines of the high queue
PostgreSQLStatefulSetVulnerability database
RedisStatefulSetRQ job queues and Django cache

With the values of the repository, the instance requests 2.75 vCPU and about 8.4 GiB of memory in total, on the node pool it shares with the other services (section 21.6).

Data. The importer pipelines collect advisories from public sources such as NVD, GitHub, OSV and Linux distributions.

Access. The instance is open on the Internet at https://vulnerablecode.occtet.eu. The web UI and the REST API need no account, because config.requireAuthentication is false, and anonymous API calls are throttled to 10 per minute.

Use by the toolchain. ORT Server can use either instance: the advisor configuration of a run selects the public service, which is the default, or the OCCTET instance, with its serverUrl. The vulnerability service of the Curator queries the public service, whose address is fixed in its code (section 21.3, VulnerableCode).

6.9. Integration chain #

ORT Server and the Curator work together in two ways. Both end the same way: the Curator imports the results of the finished ORT run, and they are curated there. Both rely on the shared realm occtet and on the technical ORT account of the Curator, an ORT Server superuser (section 8.3.1, Granting permissions).

A – ORT Server as entry pointB – Curator as entry point
Started byA user, a script or a CI pipeline, in ORT ServerA Curator user, with ORT Starter
Organisation, product and repository in ORT ServerCreated by the user (web UI, REST API, osc CLI) or by the portfolio scripts (section 13.4, Automation for large portfolios)Created by the Curator if missing, named after the Curator organisation and project
RunStarted in ORT ServerStarted by the Curator, on the version of the project
Import into the CuratorAutomatic: the Curator creates the organisation and the project, named after the ORT organisation and productAutomatic, into the project that started the run
Typical useAnalysis campaigns (section 14.2, Scenario B – Analysis campaign on the Eclipse Foundation portfolio), projects that already use ORT Server or its API in their CIA project audited from the Curator, from analysis to curated SBOM

A – ORT Server as entry point. An ORT Server user, a script or a CI pipeline creates the organisation, the product and the repository in ORT Server, then starts the runs (section 13.2, Using ORT Server). Nothing has to be configured in the Curator: every two minutes, it looks for the runs that have finished, creates an organisation and a project named after the ORT organisation and product if they do not exist, and imports the results. The data is then curated in the Curator. A Curator user sees the imported project once a Curator admin has added them to its organisation (section 8.3.1, Granting permissions). This is how the runs of the analysis campaigns reach the Curator.

sequenceDiagram
    autonumber
    actor U as ORT Server user, script or CI
    participant KC as Keycloak realm occtet
    participant ORT as ORT Server API and orchestrator
    participant W as ORT workers
    participant VC as VulnerableCode and OSV
    participant FE as Curator frontend
    participant PR as Process Run service
    participant VS as Vulnerability service
    actor C as Curator user
    U->>KC: Sign in, or exchange the offline token of /profile/token
    KC-->>U: Access token with audience ort-server
    U->>ORT: Create organisation, product and repository (UI, API, osc or portfolio scripts)
    U->>ORT: Start a run
    ORT->>W: Jobs config, analyzer, advisor, scanner, evaluator, reporter
    W->>VC: Advisor queries to OSV and VulnerableCode
    W-->>ORT: Results, SBOMs and reports stored
    loop Every 2 minutes
        FE->>ORT: Latest runs finished or finished with issues (technical ORT account)
        FE->>PR: NATS subject work.ortProcessRun, for each run not processed yet
        PR->>ORT: Read the SPDX or CycloneDX report, ORT issues and rule violations
    end
    PR-->>FE: Organisation and project created if missing, named after the ORT organisation and product, then results imported
    FE->>VS: Vulnerability lookup request over NATS
    VS->>VC: VulnerableCode bulk package query
    C->>FE: Curate inventory items, write VEX, export SPDX or CycloneDX

Figure 3 – A: ORT Server as entry point. Steps 12 to 14, the Curator’s vulnerability lookup and the curation, VEX and export, have not been validated on the instance (section 14.1, Scenario A – Integration test between the Curator and ORT Server).

B – Curator as entry point. A Curator user creates a project, with the URL and the version of the repository, and starts an analysis with ORT Starter (section 13.3, Using Occtet Curator). With the technical ORT account, the Curator’s ORT Run Start service creates in ORT Server the organisation, the product and the repository, if they are missing, named after the Curator organisation and project. It then starts the run on the version of the project. For a private GitHub repository, it also stores the GitHub token of the project as secrets of the ORT product. The finished run is imported as in A.

sequenceDiagram
    autonumber
    actor U as Curator user
    participant FE as Curator frontend
    participant KC as Keycloak realm occtet
    participant RS as ORT Run Start service
    participant ORT as ORT Server API and orchestrator
    participant W as ORT workers
    participant PR as Process Run service
    participant VS as Vulnerability service
    participant VC as VulnerableCode and OSV
    U->>FE: Open curator.occtet.eu
    FE->>KC: OpenID Connect authorization code flow
    KC-->>FE: Tokens including realm roles
    U->>FE: Create project and start an ORT run (repository type, URL, version)
    FE->>RS: NATS subject work.ortRunStarter
    RS->>KC: Token request for the technical ORT account
    KC-->>RS: Access token with audience ort-server
    RS->>ORT: Create organisation, product and repository if missing, then start the run
    ORT->>W: Jobs config, analyzer, advisor, scanner, evaluator, reporter
    W->>VC: Advisor queries to OSV and VulnerableCode
    W-->>ORT: Results, SBOMs and reports stored
    loop Every 2 minutes
        FE->>PR: NATS subject work.ortProcessRun
        PR->>ORT: Read run status and results
    end
    PR-->>FE: Components, licences, copyrights, ORT issues and rule violations imported
    FE->>VS: Vulnerability lookup request over NATS
    VS->>VC: VulnerableCode bulk package query
    U->>FE: Curate inventory items, write VEX, export SPDX or CycloneDX

Figure 4 – B: the Curator as entry point. Steps 1 to 14 have been validated on the instance, up to the import of results; steps 15 to 17, the Curator’s vulnerability lookup and the curation, VEX and export, have not been validated (section 14.1, Scenario A – Integration test between the Curator and ORT Server).

In both chains, the advisor of ORT Server queries the public VulnerableCode service or the OCCTET instance, depending on the serverUrl of the run (section 6.8, Vulnerability database: VulnerableCode).

Import of finished runs, in A and B. Every two minutes (setting PROCESS_RUN_CRON), the Curator frontend reads from ORT Server, with the technical ORT account, the most recent run with status finished and the most recent run with status finished with issues. It hands each run it has not processed yet to the Process Run service. That service creates the Curator organisation and project named after the run’s ORT organisation and product, if they do not exist, then imports the SPDX report of the run, or its CycloneDX report when no SPDX report is available, with the ORT issues and rule violations. Runs started outside the Curator, such as the campaign runs of section 13.4, Automation for large portfolios, are therefore imported too. Only the latest run of each status is read at each poll, and the list of processed runs is kept in memory, so some runs can be missed when many finish between two polls.

7. Customisation and branding #

7.1. Keycloak theme keycloak-occtet-theme #

7.2. ORT Server branding (footer) #

ORT Server has a content-management feature for its user interface. The footer is a Markdown document in which the layout markers ::: align-left and ::: align-right position the blocks.

The OCCTET footer contains:

The Markdown source is ort-footer.md, in the repository occtet-ort-server-helm-chart. An ORT Server administrator applies it as follows:

  1. Open Administration → Content Management → Branding.
  2. Paste the Markdown into the footer form.
  3. Enable the footer and save.

7.3. VulnerableCode branding #

VulnerableCode has no setting for its header or footer. The chart therefore replaces two of its page templates and serves extra static files, without rebuilding the image:

The templates and logos are in the branding/ directory of the repository occtet-vulnerablecode-helm-chart. make deploy-vulnerablecode in occtet-helm-chart passes the templates to the chart (web.customization.templates) and loads the logos into the ConfigMap vulnerablecode-branding; the nginx sidecar serves them under /static/custom/. An overridden template no longer follows upstream changes, so both templates must be compared with the upstream ones whenever VulnerableCode is upgraded.

7.4. EU visibility #

The installation and its documentation carry the funding acknowledgement that the Grant Agreement requires (section 4.2):

8. Security architecture #

This section describes the controls in place and states their limits. The actions planned before the platform opens as a production service are listed in section 15.5, Operational readiness matrix.

8.1. Network and transport #

Data location. The platform stores all its data (databases, volumes, logs) in the OVHcloud Strasbourg region, in France. Some data is nonetheless sent to external services when analyses run:

The AI-assisted services of the Curator use the local LLM runtime Ollama, inside the cluster (section 6.7), so no data is sent to external AI services. The OCCTET VulnerableCode instance downloads advisory data from public sources. A run that uses it keeps its package identifiers on the platform, instead of sending them to the public VulnerableCode service (section 6.8, Vulnerability database: VulnerableCode).

8.2. Identity and authentication #

The realm occtet provides single sign-on for ORT Server (UI, API, CLI) and the Occtet Curator. It uses OpenID Connect with RS256-signed tokens. VulnerableCode is not connected to it (section 6.8).

ClientTypeUsed byFlowPurpose
ort-serverPublicORT Server web UI; portfolio scriptsAuthorization code with PKCE; direct access grants (password) are also enabled. On the OCCTET instance, the implicit flow is enabled too, and any redirect URI and web origin are accepted (section 15.5, Operational readiness matrix)User login. Its client ID is also the audience required by the ORT Server API. The portfolio scripts of section 13.4 obtain their tokens with the password grant.
ort-server-cliPublicORT Server token page, osc CLI, scriptsAuthorization code with PKCE, offline_accessOffline tokens; an audience mapper adds ort-server
api-accessConfidentialORT Server core; Occtet Curator; access job (section 8.3.1, Granting permissions)Client credentials for ORT Server and the access job; authorization code and token requests for the CuratorSee below
ort-ciConfidentialProjects’ CI pipelinesUser token generationTokens used by project CI pipelines to trigger analyses

The api-access client is shared. Its service account holds the client roles manage-realm, manage-users and manage-clients of realm-management, which ORT Server uses to administer users, groups and roles. The same client is also the Curator’s OpenID Connect client, and the Curator obtains ORT Server tokens through it. The hourly access job also uses its service account, to assign the realm role curator (section 8.3.1, Granting permissions). As a result, the Curator deployment holds the secret of a client that has realm-management rights. Giving the Curator its own client is the first identity hardening action (section 15.5).

ORT Server validates every API token in three ways:

8.3. Authorisation model #

ORT Server. Permissions follow the hierarchy organisation → product → repository. At each level a user can be assigned the reader, writer or admin role. ORT Server stores the assignments, including the superuser status, in its own database. An existing superuser makes other users superusers under Administration → User Management → Users. The Keycloak group SUPERUSERS only seeds the first superusers: ORT Server reads it when it starts with no role assignment (section 11.6, Step 4 – Keycloak).

Occtet Curator. Two application roles exist:

A row-level policy restricts curator users to the data of their own organisation. The roles come from the Keycloak token and are matched against the roles defined in the application.

Kubernetes. Only platform operators administer the cluster. Partners who operate a service receive a ServiceAccount whose Role covers only their namespace, plus read-only listing of namespaces. Housekeeping jobs run under dedicated ServiceAccounts with minimal rights.

8.3.1. Granting permissions #

Keycloak only signs users in; each tool keeps its own permissions. Every hour, the access job described below gives every account read access to the public organisations of ORT Server and the realm role curator. Any other permission is granted by hand, separately in each tool:

ORT ServerOcctet Curator
Stored inThe ORT Server databaseRoles: realm roles of the realm occtet, in Keycloak, which the Curator reads from the token at sign-in. Organisation membership: the Curator database
Granted byA superuser, or an admin of the organisation, product or repository; the access job for READER on the public organisationsRoles: a Keycloak administrator, or the access job for curator. Organisation membership: a Curator admin
HowORT Server UI: page of the organisation, product or repository → Users → Manage Users: exact user name (the e-mail address), role READER, WRITER or ADMIN, Assign role. Superusers: Administration → User Management → Users → Make superuser. The API does the same: PUT /api/v1/organizations/{id}/roles/{role} with {"username": "…"}, the same paths for products and repositories, and PUT /api/v1/admin/users/{username}/superuserKeycloak administration console, realm occtet → Users → the user → Role mapping → Assign role → admin or curator. Curator UI, as admin: Administration → Organizations → the organisation → Add member; a user is listed once it has signed in to the Curator
Takes effectImmediatelyRoles: at the next sign-in. Organisation membership: at the next page load

The two sets are independent: the realm roles admin and curator give no permission in ORT Server, and an ORT Server superuser has no access to the Curator. Adding a user to the Keycloak group SUPERUSERS after the first start of ORT Server changes nothing. The technical account of the Curator must be an ORT Server superuser.

Automatic access. A scheduled pipeline of the repository occtet-eclipse-projects runs the job grant-users-access every hour. The job runs the script grant-users-access.sh (section 13.4, Automation for large portfolios), which reads the users of the realm occtet and, for every enabled account, service accounts excluded:

A new account therefore reads the public organisations of ORT Server within the hour after its creation, and opens the Curator at its next sign-in after that. The job only adds missing permissions and never removes one, so it can run again at any time. It does not add users to a Curator organisation: a curator sees no Curator data until a Curator admin adds them to one. The job authenticates with an offline token of an ORT Server superuser (section 5.4, Programmatic access: API, CLI and CI), which it exchanges for access tokens, and with the secret of the client api-access (section 8.2, Identity and authentication). A request that gets no answer, for example while ORT Server is overloaded, is attempted again up to five times, one minute apart.

8.4. Secrets management #

8.5. Workload separation and supply chain #

These controls are applied:

ControlApplied
One namespace, Helm release and database per serviceYes. This is an organisational and RBAC boundary. It is a network boundary only for the VulnerableCode PostgreSQL and Redis, the only components protected by NetworkPolicies.
Dedicated ServiceAccountsORT Server workers run under ort-server-workers-sa, which has no RoleBinding. Housekeeping jobs have minimal Roles. Partner ServiceAccounts are scoped to one namespace.
Non-root executionORT Server worker pods run as user ID 1000. VulnerableCode containers run as user ID 65534 (nobody), without privilege escalation and with all capabilities dropped; its nginx sidecar runs as user ID 101 with a read-only root filesystem, and its pods mount no Kubernetes API token. No Pod Security Admission level is enforced on the namespaces.
Resource limitsCPU and memory requests and limits are set for every worker and service (section 21.6).
Short-lived analysis podsThird-party code is analysed in worker Jobs, which are removed after completion. These pods have outbound network access, which they need to fetch sources and query advisories.
Image pinningKeycloak, the Maven image of the Keycloak theme initContainer, PostgreSQL, RabbitMQ and the ingress controller are pinned by digest. The ORT Server and VulnerableCode images use versioned tags, and the VulnerableCode Redis image is pinned by digest. The Curator, NATS and Ollama images use latest. Annex C lists the images and their versions.
Dependency updatesRenovate proposes chart, image and dependency updates as merge requests in the repositories of ORT Server, the Curator, Keycloak and the theme. In occtet-helm-chart, it proposes the new commits of the chart repositories and the new versions of ingress-nginx and cert-manager. The CI deploys the ORT Server stack, the Curator and Keycloak once a merge request is merged.
CI security scansThe pipelines of the Keycloak, theme, ORT Server, Curator, portfolio-script and statistics repositories include the Eclipse Foundation’s shared AutoDevOps template, which adds SAST, secret detection and dependency scanning jobs.

8.6. Administrative access #

9. Monitoring and observability #

9.1. Logs #

9.2. Resource metrics and health checks #

9.3. Job supervision, housekeeping and retention #

CronJobSchedulePurpose
kubernetes-lost-jobs-finderEvery 2 minutesDetects worker jobs whose pods disappeared
kubernetes-long-running-jobs-finderEvery 5 minutesDetects worker jobs that exceed their per-worker timeout
kubernetes-reaperEvery 5 minutesRemoves finished worker jobs
delete-old-ort-runsDaily, 00:15Deletes the runs that finished before the retention period, except the latest run of each repository
delete-orphaned-entitiesDaily, 01:30Removes orphaned database entities
pod-cleanup (this installation)Every 10 minutesDeletes succeeded pods in namespace ort-server
occtet-stats-refresh (this installation)Every 15 minutesPrecomputes the vulnerabilities of the new runs for the statistics dashboards (section 9.4)

The ORT Server maintenance CronJobs (all but pod-cleanup and occtet-stats-refresh) run with the resources and JVM settings given in section 21.6.

Retention. delete-old-ort-runs deletes every run that finished more than DATA_RETENTION_ORT_RUN_DAYS days ago, except the latest run of each repository, the one with the highest identifier. The ORT Server chart keeps 90 days; the values of this installation keep 365 days (section 21.1), so that the runs of a campaign remain for a year after their repositories are analysed again, and with them the history of the statistics dashboards (section 9.4).

A run’s results, reports and SBOMs are deleted 365 days after the run finished, unless it is still the latest run of its repository. Users who need them longer must download them. Worker logs held in Loki are already lost whenever the Loki pod restarts (section 9.5).

VulnerableCode deletes its pipeline run records after 60 days.

9.4. Statistics dashboards #

The repository occtet-stats adds three dashboards to the Grafana of ORT Server, in the folder ORT Server:

DashboardContent
ORT Server · OverviewInventory, runs created by status, vulnerabilities, packages, rule violations and issues of the latest runs, per organisation and over time
ORT Server · VulnerabilitiesVulnerabilities by rating, by organisation, product and repository; their evolution; the vulnerabilities that appear and disappear; the most widespread vulnerabilities and vulnerable packages; vulnerabilities by ecosystem
ORT Server · RunsRuns and jobs over time, failed jobs by worker, execution and waiting times, status of the latest run of each repository, latest runs that failed with their error, ORT Server releases

9.5. Current limits #

10. Prerequisites #

AreaRequirement
CloudAn Public Cloud project with a Managed Kubernetes cluster. The reference uses 4 × b2-15 and 2 × b2-30 (32 vCPU, 120 GB RAM). Each node needs at least 15 GB RAM, because scanner and evaluator jobs request 8 GiB and may use up to 12 GiB. The local VulnerableCode instance requests a further 2.75 vCPU and 8.4 GiB (section 6.8).
StorageCinder CSI storage classes csi-cinder-classic and csi-cinder-high-speed. The reference workload uses 539 GiB of volumes, 102 GiB of them for VulnerableCode (section 21.6).
NetworkThe ability to create a LoadBalancer Service. Inbound TCP 80 (for the ACME HTTP-01 challenge) and 443. Outbound HTTPS to container registries, Helm chart repositories, Git hosting, package registries, the public VulnerableCode service, OSV, the advisory sources imported by the local VulnerableCode instance, and Let’s Encrypt.
DNSFour host names pointing to the load balancer’s public address
Toolsgit, kubectl, Helm 3.8 or later (tested with Helm 4.3), make, jq, openssl; the helm-diff plugin for make diff. Node.js 20 with Yarn 1, and Maven, are needed only to rebuild the theme.
AccessRead access to the deployment repositories (section 4.5); pull access to ghcr.io (images, and the ORT Server and Curator charts), Docker Hub and the Helm repositories of Grafana, Jetstack and ingress-nginx
Accounts and keysA GitHub OAuth App; an API key for the public VulnerableCode service (https://public.vulnerablecode.io/account/request_api_key/), which ORT Server and the Curator query (the local instance needs none); a technical ORT Server account for the Curator; optionally a GitLab project with the GitLab Agent

11. Installation #

11.1. Getting the deployment material #

BASE=https://gitlab.eclipse.org/eclipsefdn/open-source-services/occtet-eu/reference-installation
git clone --recurse-submodules "$BASE/occtet-helm-chart.git"   # installation; chart repositories as git submodules under repos/
git clone "$BASE/occtet-keycloak.git"                           # Keycloak
git clone "$BASE/occtet-eclipse-projects.git"                   # optional: portfolio campaigns (section 13.4)
git clone "$BASE/occtet-stats.git"                              # optional: statistics and dashboards (section 9.4)

cd occtet-helm-chart
helm repo add grafana https://grafana.github.io/helm-charts
make deps                    # downloads the dependencies of the ORT Server, Curator and VulnerableCode charts

In an existing clone of occtet-helm-chart, make init checks out the chart repositories at the commits it references. The variables ORT_SERVER_REPO, CURATOR_REPO, VULNERABLECODE_REPO and KEYCLOAK_REPO point the Makefile to other checkouts, for example a branch under review. The local installation of section 12 uses the clones of the chart repositories next to occtet-helm-chart instead of the submodules.

Two repositories are not cloned. ORT Server reads occtet-ort-server-config from Git at every run (section 21.1): fork it to change the parameters or the policy rules, and set its URL in the ORT Server values. Keycloak downloads the theme package built by occtet-keycloak-theme (section 7.1): clone that repository only to change the theme.

StepRepositoryFiles used
11.4, 11.5 Ingress controller, certificatesocctet-helm-chartMakefile, values/ingress-nginx.yaml, values/cert-manager.yaml
11.6 Keycloakocctet-keycloakChart.yaml, values.yaml, Makefile
11.7 ORT Serverocctet-ort-server-helm-chart, submodule of occtet-helm-chartChart.yaml, values.yaml, templates/, ort-footer.md
11.8 Occtet Curatorocctet-curator-helm-chart, submodule of occtet-helm-chartChart.yaml (Bitsea chart occtet-curator 0.1.0), values.yaml, Makefile (secrets-create)
11.9 VulnerableCodeocctet-vulnerablecode-helm-chart, submodule of occtet-helm-chartvulnerablecode/ (chart and Chart.lock), values.yaml, branding/
11.10 Entry pointocctet-helm-chartcharts/occtet-entrypoint/, values/occtet-entrypoint.yaml
13.4 Portfolio campaignsocctet-eclipse-projectssync-*.sh, bulk-run*.sh, env-example.sh
9.4 Statistics and dashboardsocctet-statsMakefile, grafana/ (chart, sql/, datasource.yaml), statistics.sh, env-example.sh

The make commands below run in the root of occtet-helm-chart, except those of section 11.6, Step 4 – Keycloak. The placeholders that remain in the commands (<…>) are passwords, client secrets and contact addresses. Choose them for each installation. Annex D gives the configuration reference of ORT Server, the Curator and VulnerableCode, the recommended proxy configuration, the Kubernetes Secrets, and the resource requests and persistent volumes; read the warnings of section 21.5 before deleting or recreating a Secret.

11.2. Installation order #

  1. Create the cluster (section 11.3).
  2. Install the ingress controller and create the DNS records (11.4).
  3. Install cert-manager (11.5).
  4. Install Keycloak and configure the realm (11.6).
  5. Install ORT Server (11.7).
  6. Install the Curator (11.8).
  7. Install VulnerableCode (11.9).
  8. Publish the services through the entry point, then verify the installation (11.10).

The entry point comes last: an nginx proxy refuses to start when the host name of its upstream service does not resolve. At the first installation, however, it first publishes Keycloak alone, right after step 4 (make deploy-entrypoint-bootstrap): ORT Server and the Curator call Keycloak by its public URL, and the Curator does not start without it. To publish only some of the services, set services.<service>.enabled: false for the others in values/occtet-entrypoint.yaml, or pass PUBLISH=<service keys> to make deploy-entrypoint.

Once the secrets exist, make deploy runs steps 2 and 3, publishes Keycloak if the entry point is not installed yet, runs steps 5, 6 and 7, then publishes the entry point, in that order; Keycloak is installed from its own repository. Each component is a Helm release in its own namespace (section 6.3). Makefile variables such as CURATOR_NAMESPACE change the namespaces, DOMAIN (default occtet.eu) the domain of every host name, and make help lists every target.

11.3. Step 1 – Cluster #

  1. In the OVHcloud Control Panel, create a Managed Kubernetes cluster and a node pool sized as in section 10.
  2. Download the kubeconfig and select its context.

The targets of occtet-helm-chart that change the cluster refuse to run unless the current kubectl context is OCCTET. On another cluster, pass KUBE_CONTEXT=<your context> to make. The Makefile of the Keycloak repository uses the current context without checking it. To try the procedure on a workstation first, see section 12.

11.4. Step 2 – Ingress controller and DNS #

make deploy-ingress-nginx                                   # upstream chart ingress-nginx 4.13.2, values/ingress-nginx.yaml
kubectl get svc -n ingress-nginx ingress-nginx-controller   # note the EXTERNAL-IP

Create a DNS A record for each of the four host names, pointing to the EXTERNAL-IP shown.

Further reading: https://docs.ovhcloud.com/en/guides/public-cloud/containers-orchestration/managed-kubernetes/secure-nginx-ingress-cert-manager and https://docs.ovhcloud.com/en/guides/public-cloud/containers-orchestration/managed-kubernetes/expose-applications-using-load-balancer.

11.5. Step 3 – Certificate management #

make deploy-cert-manager      # upstream chart jetstack/cert-manager v1.18.2 with its CRDs, values/cert-manager.yaml

values/cert-manager.yaml installs the CRDs with the release (installCRDs: true). Never set it to false on an existing installation: Helm would delete the CRDs, and every Certificate and ClusterIssuer with them. The ClusterIssuers letsencrypt-prod and letsencrypt-staging are created with the entry point (section 11.10). Let’s Encrypt needs host names reachable from the Internet; otherwise, clusterIssuers.enabled: false removes these ClusterIssuers and selfSignedCA.enabled: true creates a self-signed certificate authority for the Ingresses (ingress.clusterIssuer: occtet-ca), as on the local installation of section 12.

Further reading: https://cert-manager.io/docs/.

11.6. Step 4 – Keycloak #

Install, in the repository occtet-keycloak:

cd ../occtet-keycloak
make secrets-create          # creates keycloak-admin-secret and keycloak-postgresql-secret with random passwords
make install                 # helm dependency update, then helm install occtet-keycloak . -n keycloak

Only keycloak-admin-secret is used: it seeds the admin account at the first start. The chart generates the database password itself (section 21.5, Kubernetes Secrets). On the OCCTET instance, GitLab CI then deploys every merge of this repository (section 15.2, Maintenance and upgrades).

The theme initContainer in values.yaml downloads the theme from the public Maven package registry of occtet-keycloak-theme (section 7.1). A reproduction that changes the theme publishes it from its own copy of the theme repository and updates the Maven coordinates of the initContainer.

Reach the administration console. Publish Keycloak alone through the entry point, which needs ingress-nginx and cert-manager (sections 11.4 and 11.5), then open https://keycloack.<domain>/admin; or use a port-forward. Sign in as admin; the README of occtet-keycloak explains how to recover this access if the password is lost.

make secrets-get                                                 # prints the generated passwords, including the admin one
cd ../occtet-helm-chart && make deploy-entrypoint-bootstrap      # publishes Keycloak alone, at the first installation
kubectl port-forward -n keycloak svc/occtet-keycloak 8080:8080   # or: http://localhost:8080/admin

Configure the realm. The realm occtet is configured from scratch in the administration console, in the order below. ORT Server and the Curator share it (see Shared authentication below). When reproducing on another domain, replace the occtet.eu host names. The steps give the configuration to apply; the OCCTET instance still differs from it on the points listed in the Identity row of section 15.5. The file realm/occtet-dev.json of occtet-keycloak holds the result of steps 1 and 3 to 6, without GitHub sign-in, for the local installation of section 12, which imports it at startup.

1. Realm. Create the realm occtet (realm selector → Create realm), then set in Realm settings:

TabSettings
GeneralDisplay name OCCTET
LoginUser registration, Email as username, Login with email, Forgot password and Remember me on; Verify email, Duplicate emails and Edit username off
EmailAn SMTP server, which Forgot password and e-mail verification need. None is configured on the OCCTET instance.
ThemesLogin theme and Account theme keycloak-occtet-theme; Admin theme keycloak.v2; Email theme keycloak
SessionsSSO Session Idle 60 minutes; SSO Session Max 10 hours. The idle timeout must stay shorter than the maximum: a session ends after an hour without activity, and 10 hours after sign-in at the latest
TokensAccess Token Lifespan 1 hour
Security defensesBrute force detection: recommended; it is off on the OCCTET instance (section 15.5, Operational readiness matrix)

Keep the default role default-roles-occtet: it includes offline_access, which the CLI tokens need.

2. GitHub sign-in. Create a GitHub OAuth App whose authorization callback URL is https://keycloack.occtet.eu/realms/occtet/broker/github/endpoint, the Redirect URI that Keycloak shows for the provider. Then, in Identity providers → GitHub, keep the alias github and enter the Client ID and the Client Secret of the app, with Display name Or Sign in with GitHub, Trust email off and Sync mode Import.

3. Client scopes. In Client scopes:

4. Clients. In Clients → Create client, with client type OpenID Connect:

Settingort-serverort-server-cliapi-access
Used byORT Server web UIORT Server token page, osc CLI and scripts (section 5.4)ORT Server core, to administer users, groups and roles; Occtet Curator, to sign users in and to call ORT Server
Client authenticationOff (public client)Off (public client)On (confidential client)
Standard flowOnOnOn
Direct access grantsOff; on only for the portfolio scripts of section 13.4, which use the password grantOffOn: the Curator obtains ORT Server tokens for its technical account with the password grant
Service accounts rolesOffOffOn
Valid redirect URIshttps://ort-server.occtet.eu/*https://ort-server.occtet.eu/profile/token/callbackhttps://curator.occtet.eu/*
Valid post logout redirect URIshttps://ort-server.occtet.eu/*––
Web originshttps://ort-server.occtet.euhttps://ort-server.occtet.eu–
Client scopes tabAdd ort-server-api as DefaultAdd ort-server-api as Default; keep offline_access as Optional, since the token page requests itAdd ort-server-api as Default and ort-server-api-access as Optional; keep roles as Default

Leave the implicit flow off on the three clients. On ort-server-cli, PKCE Method S256 makes PKCE mandatory; the token page already uses it. On api-access, then:

5. Roles and groups.

6. Accounts.

7. Client secret. The client secret of api-access goes into the ORT Server secret ort-server-keycloak-credentials (key keycloak-api-secret, section 11.7) and into the Curator secret (key keycloak-client-secret, section 11.8).

Shared authentication. ORT Server (client ort-server) and the Curator (client api-access) are two clients of the same realm. A person has one account and one login page, with the same password or GitHub sign-in for both tools. Keycloak keeps one session per browser, so after signing in to one tool, the other opens without asking for credentials again. Each tool keeps its own permissions (section 8.3.1). Between the tools, the Curator calls ORT Server with its technical account, not with the session of the user (section 6.9).

Check. Once the entry point is published (section 11.10), sign in to ORT Server, then open the Curator: it should not ask for credentials again. The token test of section 5.4 checks the client ort-server-cli.

The OCCTET instance also has a confidential client ort-ci, for the CI pipelines of section 5.4.

Further reading:

11.7. Step 5 – ORT Server #

Secrets.

cd ../occtet-helm-chart
kubectl create namespace ort-server
kubectl create secret generic ort-server-db-credentials -n ort-server \
  --from-literal=database-password='<database password>'
kubectl create secret generic ort-server-rabbitmq-credentials -n ort-server \
  --from-literal=rabbitmq-user='ortserver' --from-literal=rabbitmq-password='<RabbitMQ password>'
kubectl create secret generic ort-server-keycloak-credentials -n ort-server \
  --from-literal=keycloak-api-secret='<client secret of api-access>'
kubectl create secret generic ort-server-user-secrets-encryption -n ort-server \
  --from-literal=master-password='<at least 16 characters>' --from-literal=salt='<at least 32 hexadecimal characters>'
kubectl create secret generic ort-server-grafana -n ort-server \
  --from-literal=admin-user='<Grafana user>' --from-literal=admin-password='<Grafana password>'
make secrets-check-ort-server

Instead of these commands, make -C repos/occtet-ort-server-helm-chart secrets-create creates the missing secrets with generated values, and prompts for the client secret of api-access. On a new installation, use it: it also stores the password hash of the RabbitMQ user (key rabbitmq-password-hash of ort-server-rabbitmq-credentials). RabbitMQ creates no default user when it imports its definitions at startup, and the CloudPirates chart does not give its own user a usable password; make deploy-ort-server therefore adds the user ortserver, with that hash, to the definitions. Without the hash, RabbitMQ refuses to start on an empty volume (no_such_user).

Install. In the values.yaml of the ORT Server repository, set ort-server.configFileProvider.gitConfig.repositoryUrl to the configuration repository, and check the Keycloak settings (section 21.1). Then:

make deploy-ort-server       # helm upgrade --install ort-server … --namespace ort-server --wait

The Makefile passes the namespace addresses of PostgreSQL, RabbitMQ and Loki, so the stack can also be installed in another namespace (ORT_SERVER_NAMESPACE).

On the OCCTET instance, the CI of the ORT Server repository also deploys the release on every merge, with the same chart and values. Run make diff-ort-server before deploying from occtet-helm-chart.

After installation:

  1. Apply the footer (section 7.2).
  2. Check under Administration → User Management → Users that the members of the group SUPERUSERS are superusers, and grant the superuser role to the other platform maintainers there.
  3. Create organisations/products/repositories
  4. Create the vulnerableCodeApiKey secret in each organisation (section 13.2).

Further reading: https://github.com/eclipse-apoapsis/helm-charts/tree/main/charts/ort-server and the ORT Server administration guide, https://eclipse-apoapsis.github.io/ort-server/docs/admin-guide/guides/keycloak-config.

11.8. Step 6 – Occtet Curator #

make -C repos/occtet-curator-helm-chart secrets-create   # namespace curator and secret occtet-curator-secrets
# Prompts for the database credentials, the technical ORT account and the api-access client; passwords are not echoed.
make secrets-check-curator
make deploy-curator          # checks the secret, then helm upgrade --install occtet-curator … --set global.namespace=curator

The repository’s values.yaml holds only the OCCTET overrides, under the key occtet-curator (section 21.2); the chart defaults are those of the Bitsea chart, at the version pinned in Chart.yaml, which make deps downloads (section 11.1). The chart places its resources in global.namespace, which stays under global and which the Makefile sets to the release namespace.

On the OCCTET instance, the CI of the Curator repository also deploys the release on every merge, with the same chart and values. Run make diff-curator before deploying from occtet-helm-chart.

Keycloak. The Curator relies on the realm configuration of section 11.6, which follows PR #250: the client api-access, the client scopes ort-server-api, ort-server-api-access and roles, the realm roles admin and curator, and the technical account, which must be an ORT Server superuser.

After installation, a Curator administrator sets the system path for project sources and the VulnerableCode API key. Section 21.2 lists the frontend values and the administrator settings.

11.9. Step 7 – VulnerableCode #

make vulnerablecode-secrets-create   # creates namespace vulnerablecode and secret vulnerablecode-secrets, with generated values
# The Makefile sets config.allowedHosts and config.csrfTrustedOrigins to vulnerablecode.<DOMAIN>
make deploy-vulnerablecode           # branding ConfigMap and templates, then helm upgrade --install … --wait
make test-vulnerablecode             # Helm test: Django and the static files answer

The repository’s values.yaml holds only the OCCTET overrides (section 21.3.2); the chart defaults are in vulnerablecode/values.yaml. Since the service is published through the entry point rather than by the chart’s Ingress, its public host name must be declared in config.allowedHosts and config.csrfTrustedOrigins. Otherwise Django rejects the requests (Bad Request (400)) and the form submissions (CSRF error). The Makefile of occtet-helm-chart sets both from DOMAIN; when the chart is installed another way, set them in values.yaml.

After installation, the scheduler starts the importer and improver pipelines by itself. The first full import takes a few days; follow it on /pipelines/dashboard/. To require an API key on the REST API, set config.requireAuthentication: true, then create API users with make vulnerablecode-create-api-user EMAIL=<address>.

Further reading: the README of occtet-vulnerablecode-helm-chart, and https://vulnerablecode.readthedocs.io/.

11.10. Step 8 – Entry point and verification #

Publish. In values/occtet-entrypoint.yaml, set the ACME contact address. The host names are ort-server., curator., keycloack. and vulnerablecode. followed by the domain, which the Makefile variable DOMAIN sets (default occtet.eu, for example make deploy DOMAIN=example.org): the Makefile passes it to the entry point and derives from it the URLs of the ORT Server, Curator, VulnerableCode and Keycloak values. Then:

make deploy-entrypoint       # ClusterIssuers, one TLS Ingress and one nginx proxy per service
make test-entrypoint         # each proxy answers /alive

cert-manager then obtains a Let’s Encrypt certificate for each host name. Section 21.4 gives a corrected configuration of the proxies, which keeps the forwarded protocol.

Verify.

make status
kubectl get pods -A
kubectl get certificate -n default
curl -sI https://ort-server.occtet.eu/
curl -s https://keycloack.occtet.eu/realms/occtet/.well-known/openid-configuration | jq .issuer
curl -s https://ort-server.occtet.eu/api/v1/auth/oidc-config/cli
curl -s -H 'User-Agent: VCIO_API_AGENT' https://vulnerablecode.occtet.eu/api/v3/ | jq .   # OCCTET instance

Then run the functional check of section 14.1, Scenario A – Integration test between the Curator and ORT Server, on a small public repository.

12. Local installation on minikube #

occtet-helm-chart also installs the whole platform on a local minikube cluster, with the Makefile variable ENV=dev. It serves two purposes: to test a change of a chart, of its values or of a secret script before it is merged and deployed by the CI, and to try the installation procedure of section 11 on a workstation before reproducing it on a cluster. It uses the same charts, values and secret scripts as production; only the overrides below differ.

OCCTET instanceLocal installation (ENV=dev)
Cluster and kubectl contextOVHcloud Managed Kubernetes, OCCTETminikube with the Docker driver, 8 CPUs, 12 GB of memory, 60 GB of disk, and inotify limits of the workstation raised to 1024 instances (fs.inotify.max_user_instances), which the cluster shares; context minikube, used whatever the current context
Host names*.occtet.eu, public DNS records*.occtet.local (Makefile variable DOMAIN), in /etc/hosts; inside the cluster, a CoreDNS rewrite rule resolves them to the ingress controller, so the applications call each other by their public URLs
CertificatesLet’s Encrypt (ACME HTTP-01)Self-signed certificate authority occtet-ca, created by the entry point (selfSignedCA) and limited to the names under occtet.local by X.509 name constraints; ORT Server and the Curator trust it through the ConfigMap occtet-ca of their namespace, the browsers once make dev-ca-install imported it
Ingress controllerLoadBalancer ServicePorts 80 and 443 of the minikube node (hostPort)
Chart repositoriesSubmodules at a pinned commitLocal clones next to occtet-helm-chart, linked under .dev/repos/: a change is tested before it is committed
Valuesvalues.yaml of each repositoryThe same, then its values-dev.yaml: smaller requests, volumes on the default storage class; no Ollama nor AI services and no NFS provisioner for the Curator; no import scheduler and one worker per queue for VulnerableCode
Keycloak realmConfigured by hand (section 11.6)realm/occtet-dev.json of occtet-keycloak, imported at the first start: the realm of section 11.6 without GitHub sign-in and without ort-ci, with an administrator account [email protected] (member of SUPERUSERS, realm role admin) and the technical account of the Curator
SecretsCreated per component (sections 11.6 (Step 4 – Keycloak) to 11.9 (Step 7 – VulnerableCode))Generated by the secrets-create targets of the component repositories; the client secret of api-access and the technical account are shared between the realm, ORT Server and the Curator

Install. Clone the repositories side by side, then run make dev-up:

BASE=https://gitlab.eclipse.org/eclipsefdn/open-source-services/occtet-eu/reference-installation
for repo in occtet-helm-chart occtet-keycloak occtet-ort-server-helm-chart occtet-curator-helm-chart occtet-vulnerablecode-helm-chart; do
  git clone "$BASE/$repo.git"
done
cd occtet-helm-chart
make dev-up         # cluster, DNS, secrets, every release, CA certificate and /etc/hosts line
make dev-accounts   # passwords of the development accounts

make dev-up runs make dev-cluster, make deps, make dev-secrets and make deploy with ENV=dev, in the installation order of section 11.2, Keycloak included. It then saves the root certificate of the CA to occtet-ca.crt and prints the /etc/hosts line to add. Every step can be run alone and again.

Trusting the CA. Browsers keep their own trust store: make dev-ca-install imports the CA, without sudo, into those of Chrome, Chromium and Firefox, snaps included, which do not read the trust store of the system; restart them afterwards. Command-line tools such as curl use the trust store of the system: sudo cp occtet-ca.crt /usr/local/share/ca-certificates/ && sudo update-ca-certificates. Each new cluster has a new CA, to trust again. The CA can only sign names under occtet.local, so trusting it does not expose any other site.

inotify limits of the workstation. With the Docker driver, the minikube node shares the kernel of the workstation and its inotify limits. Every process of the cluster runs as root of the workstation, and the installation alone uses about 90 inotify instances, one per container plus the kubelet, the control plane and Promtail, against a default limit of 128 per user (fs.inotify.max_user_instances on Ubuntu). Once it is reached, following the logs of a pod (kubectl logs -f, k9s) fails with failed to create fsnotify watcher: too many open files, although the pods keep running. Raising the limits fixes it, without restarting anything:

sudo sysctl -w fs.inotify.max_user_instances=1024 fs.inotify.max_user_watches=524288
printf 'fs.inotify.max_user_instances = 1024\nfs.inotify.max_user_watches = 524288\n' | sudo tee /etc/sysctl.d/99-inotify.conf

Verify. Open https://ort-server.occtet.local and https://curator.occtet.local, register an account from the login page, then sign in to both tools with it. ORT Server shows no organisation until a superuser, such as [email protected], grants a role in the ORT Server UI; the Curator follows the realm roles admin and curator, assigned in the administration console https://keycloack.occtet.local/admin/ (section 8.3.1).

The README of occtet-helm-chart (section Development on minikube) details the targets, the development work on a chart repository and the troubleshooting; the README of each component repository describes its values-dev.yaml.

13. Onboarding and usage #

13.1. Onboarding process and checklist #

SMEs and open source projects are onboarded in the same way, on the OCCTET instance or on Double Open’s own hosted ORT Server instance, where most SMEs tested (section 14.3):

  1. Initial contact, typically through OCCTET’s outreach, personal networks or partner referrals, or an access request by e-mail (section 5.3).
  2. Intake conversation, to identify the software to analyse. The getting-started guide (https://occtet.eu/toolkit/getting-started/) lists what to prepare: repository URL and revision, languages, private registries and a technical contact. It advises discussing data handling before confidential code is analysed on a hosted instance.
  3. Access, to run the participant’s own repositories through a hosted ORT Server instance, alongside the CRA self-assessment questionnaires developed in Work Package 2 (D2.1, D2.2).

For open source projects, task T4.4 applies the toolkit to the iconic projects of the Grant Agreement and to a wider benchmark of popular repositories (sections 14.4 and 14.5).

Checklist for a new user of the OCCTET instance:

  1. First sign-in. The user signs in once with a local account or with GitHub (section 5.3).
  2. Permissions. Within the hour after the first sign-in, the access job grants read access to the public organisations of ORT Server and the curator role (section 8.3.1, Granting permissions). An administrator grants the rest according to the profile, in each tool separately:
    • in ORT Server, a role on the user’s own organisation;
    • in the Curator, organisation membership;
    • for partners operating a service, a namespace-scoped ServiceAccount.
  3. ORT Server workspace.
    • Create the organisation.
    • Create the vulnerableCodeApiKey secret, holding the API key of the public VulnerableCode service (section 13.2).
    • Register products and repositories.
    • For private repositories, store the credentials as ORT Server secrets.
  4. Curator workspace. The user belongs to the right organisation, and the VulnerableCode key is configured once per Curator instance.
  5. Automation (optional). Generate a token for the CLI, scripts or CI (section 5.4).

13.2. Using ORT Server #

  1. Model the portfolio. Create organisations, products and repositories.
  2. Declare the VulnerableCode API key. Request an API key for the public VulnerableCode service at https://public.vulnerablecode.io/account/request_api_key/. Then, in ORT Server, create a secret named exactly vulnerableCodeApiKey whose value is that key: open the organisation, the product or the repository, then Secrets → Add secret, and fill in Name, Value and Description. A secret created in the organisation serves all the runs of its products and repositories.
  3. Start a run. Choose a revision and configure the jobs; section 21.1 gives a reference configuration.
  4. Follow the run. Track the status of each job and download its logs.
  5. Review the results. Browse packages, vulnerabilities, rule violations and issues, then download the SBOMs (CycloneDX, SPDX) and the web report.

Without the vulnerableCodeApiKey secret, the advisor job fails in every run that selects the VulnerableCode advisor with the public service, that is, without serverUrl or with https://public.vulnerablecode.io/api/. The configuration script then requests the secret for the run (section 21.3.1). Runs that select only the OSV advisor do not need it.

The secret can also be created through the API, with an access token obtained as in section 5.4:

curl -X POST -H "Authorization: Bearer $ACCESS_TOKEN" -H 'Content-Type: application/json' \
  https://ort-server.occtet.eu/api/v1/organizations/<organization ID>/secrets \
  -d '{"name": "vulnerableCodeApiKey", "value": "<VulnerableCode API key>", "description": "Public VulnerableCode service"}'

The same request on /api/v1/products/<product ID>/secrets or /api/v1/repositories/<repository ID>/secrets limits the key to a product or a repository. When the secret exists at several levels, the one closest to the repository is used.

13.3. Using Occtet Curator #

The Curator user guide is published at https://bitsea.github.io/Occtet-Curator/; the same guide is built into the application, in its Help view. It covers the administration (organisations, system path, API key), projects, SBOM import, ORT runs, the curation of inventory items, files and audit notes, vulnerabilities and VEX, export, and a complete audit process.

Administrators create the organisations, assign users to them and define the system path where source code is stored. A curator then works as follows:

  1. Create a project (Projects).
  2. Import an SBOM: Import → SPDX Import, or start an ORT analysis with ORT Starter.
  3. Open SBOM Management (SBOMmgmt) and select the project: the file tree is on the left, the inventory items on the right.
  4. Review the vulnerabilities, licences and copyrights on the Overview page.
  5. Curate the inventory items. Each one holds the details of a software component: its vulnerabilities, licences, copyrights and files. Audit notes record the evidence and the decisions.
  6. Assess vulnerabilities: create and download VEX reports from the inventory item.
  7. Export: download the curated SBOM from the project page.

The aim is to turn an unverified SBOM into a validated, audit-ready software inventory.

13.4. Automation for large portfolios #

The scripts of the repository occtet-eclipse-projects use the ORT Server REST API, and for grant-users-access.sh, the Keycloak admin API:

ScriptFunction
sync-eclipse-projects.shCreates one product per non-archived Eclipse project, read from the Eclipse projects API, and adds its GitHub and GitLab repositories (organisation 14)
sync-eclipse-research-projects.shMaps every sub-group of the Eclipse Research Labs GitLab group to a product, and every project inside it to a repository (organisation 15)
bulk-run.shStarts runs for every repository of an organisation, product or repository, with a fixed job configuration: analyzer, OSV and VulnerableCode advisors, scanner, evaluator, and CycloneDX, SPDX and web-app reports. It paces submissions, refreshes expired tokens, and can resume an interrupted campaign. It can also restrict a campaign to the repositories whose latest run failed (-F) or has given statuses (-S), and only list them (-l).
bulk-run-stop.shStops every active run within a chosen scope
grant-users-access.shGives every account of the realm occtet the role READER on the public organisations of ORT Server and the realm role curator. A scheduled pipeline of the repository runs it every hour (section 8.3.1, Granting permissions).

14. Demonstration and validation #

The validation combines a technical integration test of the toolchain (14.1) with four validation tracks: an analysis campaign at scale (14.2), SME use cases (14.3), the iconic open source projects of the Grant Agreement (14.4) and a benchmark of widely used open source projects (14.5).

The figures of sections 14.1, 14.2, 14.6 (Consolidated results of the instance) and 14.7 (Limits observed) were extracted on 6 October 2026 with the script statistics.sh, through the APIs of ORT Server, the Curator and Keycloak (section 13.4, Automation for large portfolios), and cover runs created between 12 May and 4 October 2026. A campaign was running while the figures were extracted, so counts taken a few hours apart can differ by less than 1%. Annex B gives the definitions and describes the extraction. The figures of sections 14.3 to 14.5 come from the data pull of 11–12 October 2026 reported in the Double Open draft of this deliverable. That pull covers the OCCTET instance and Double Open’s own hosted ORT Server instance; Annex B does not apply to it.

14.1. Scenario A – Integration test between the Curator and ORT Server #

This scenario is a technical integration test, run by the project team. It is not SME feedback; that is covered in section 14.3.

ItemValue
Repositoryhttps://github.com/xNuminousx/ElementalCompanions (public, Git)
RevisionBranch master, resolved to commit 7a7bdcaf4df37e205239b073972c288ef0b4ee5e
Curator projectID 201, elementalCompanions, version master, created on 10 September 2026
ORT Server organisationA test organisation created automatically by the Curator on its first run
ORT runsIDs 7626 to 7636 (indexes 1 to 11), started from the Curator between 18 and 21 September 2026
Run durationAbout 2 minutes each
StepExpected resultResult obtainedStatus
1. Sign in to the Curator through KeycloakSession with Curator rolesDoneValidated
2. Start an ORT run from ORT StarterToken obtained; organisation, product and repository created in ORT Server; run startedOrganisation, product and repository created; 11 runs startedValidated
3. ORT Server pipelineAll jobs executed and reports producedAll 11 runs ended finished with issuesValidated
4. Import into the CuratorComponents and findings imported into the project7 inventory items imported into project 201Validated
5. Curation decisionAt least one inventory item curated, with an audit noteNo curated inventory item on the instanceNot validated
6. VEX statementVEX recorded for a vulnerabilityNo VEX statement on the instanceNot validated
7. Export of the curated SBOMSPDX or CycloneDX export of the curated projectNot performedNot validated

Conclusion. The integration between the Curator, Keycloak and ORT Server is validated up to the import of results. Curation decisions, VEX authoring and the export of a curated SBOM are available in the Curator, but have not yet been validated on this instance. The 11 runs repeat the same test on the same commit, while the integration was being finalised.

Curator instance content, for context:

IndicatorValue
Organisations / users / projects7 / 8 / 84
SPDX documents imported204
Software components / inventory items3,275 / 4,820
Inventory items marked as curated0
ORT issues / rule violations imported429 / 17,876 (none marked as resolved)
VEX statements0

14.2. Scenario B – Analysis campaign on the Eclipse Foundation portfolio #

This campaign is an additional validation. It was not foreseen in this form in the Description of Action. It tests the service at the scale of a large open-source portfolio.

Steps.

  1. sync-eclipse-projects.sh and sync-eclipse-research-projects.sh registered the Eclipse projects and the Eclipse Research Labs.
  2. bulk-run.sh started one run per repository, with OSV and VulnerableCode advisors, scanning, and CycloneDX, SPDX and web-app reports.

Inventory, started and completed analyses:

IndicatorEclipse projectsEclipse Research LabsTotal
Products registered44116457
Repositories registered (inventory)3,6444814,125
Repositories with at least one run started3,2134363,649
Repositories with at least one completed run2,6213592,980
Runs started4,2124404,652
Runs completed / failed / in progress2,912 / 785 / 515361 / 79 / 03,273 / 864 / 515

The campaign therefore covers the portfolio as an inventory: 4,125 repositories. 72% of those repositories have at least one completed analysis.

The Double Open data pull adds that the runs were submitted in two waves, in June and in September–October 2026, nearly all of them in bulk by a single Eclipse Foundation account. It also reports aggregate findings of roughly 352,000 package findings and 94,000 vulnerabilities across the corpus. Their definitions are not documented in Annex B, so they cannot be compared directly with the counts of section 14.6, Consolidated results of the instance. The campaign thus shows that the toolkit processes thousands of repositories without per-project customisation.

Waiting time and execution time (completed runs, medians, with the 90th percentile in brackets):

Execution timeWaiting timeTotal
Eclipse projects2 min (19 min)2.5 h (11.6 h)2.7 h
Eclipse Research Labs2 min (26 min)60.9 h (68.3 h)60.9 h
Other organisations3 min (91 min)1 min (14.1 h)9 min

Execution time is the sum of the durations of a run’s jobs. Waiting time is the total minus execution, i.e. time spent queued for cluster capacity. For the Research Labs, the 440 runs were submitted in one batch, so waiting accounts for almost all of their duration.

Concurrency and impact on interactive use.

14.3. SME use cases and feedback #

Testing. According to the data pull of 11–12 October 2026, 10 SMEs engaged with the toolkit. Eight tested it on their own software: 19 distinct products and repositories, analysed in 115 runs as code, dependencies and advisories changed over time.

SMEProducts testedNotes
A-SIT Plus GmbH2Positive feedback: ORT Server ran better out of the box than a commercial alternative
Autoliitto Palvelut Oy3The only SME with sustained, partly self-driven use, through its own CI pipeline
Basemark Oy1Analysed cleanly; a second product is currently not supported by the tooling
DH Electronics GmbH1A Yocto analysis was developed during the project and run for this SME (section 14.7, Limits observed)
Datasance6The only SME tested on the OCCTET reference instance rather than on Double Open’s hosted service
Mediamaisteri Oy1Testing explicitly concluded
Obeo S.A.S.2–
ReductSoftware UG3Stable and clean results
Total19

SMEs that declined. Two SMEs declined to proceed: TeraTron GmbH, because it does not want to share source code outside the company, and Mahlo GmbH, because its embedded C++ build tooling is not currently supported. Other SMEs, not recorded as OCCTET testers, declined for the same reason as TeraTron: they do not want to expose their source code to a SaaS service, even one hosted and controlled in the EU. TeraTron’s response is therefore representative of SMEs that would rather run the tools inside their own perimeter, which the reproducible installation of sections 10 and 11 allows.

Feedback. Three SMEs gave qualitative feedback. The table summarises the SME testing tracker; the entries are reformulated from the tracking notes, not direct quotations. The full analysis is in D4.2.

CaseORT / technical tooling feedbackCRA questionnaires
SME AThe SME reported that ORT tooling worked well. The feedback also highlighted typical onboarding matters requiring support.The SME reported using the questionnaires and found the third questionnaire’s requirements stringent. The tracker records the first questionnaire as very helpful, while feedback on the second suggests limited usefulness. Questionnaire completion status needs confirmation because the tracker contains inconsistent entries.
SME BInitial analyses worked successfully. Following hands-on use, the SME provided substantial feedback by email and during a meeting, reporting that ORT Server worked well out of the box.Questionnaire use and results are not confirmed in the tracker.
SME CThe testing discussions identified needs for dependency detection, vulnerability matching, SBOM generation, AI-assisted triage and integration into developer workflows. OCCTET tooling was considered interesting and appeared to address the identified needs. However, the SME prioritised its existing commercial tooling and internal automation; future adoption would depend on gaps in that solution.CRA requirements were discussed, but questionnaire completion and questionnaire-specific feedback are not recorded.

These cases provide qualitative evidence of successful ORT Server use and identify onboarding and workflow integration needs. Questionnaire feedback was documented separately where available. No SME feedback on the Curator workflow is recorded, and the SME testing tracker does not record completion of a Curator-based workflow covering import, curation, VEX authoring and export; therefore, these cases should not be presented as validation of that full workflow.

14.4. Iconic open source projects #

For Milestone 9 (section 4.2), the Grant Agreement names seven candidate projects: Adoptium, Eclipse IDE, Eclipse Mosquitto, Jetty, Tomcat, Kafka and Hadoop. The Double Open draft reports that the toolkit has been applied to all seven. Run data is confirmed for four of them on the instances surveyed:

ProjectRepositoriesPackages
Adoptium7910,853
Eclipse IDE (Platform, JDT, PDE and Equinox projects)382,179
Eclipse Mosquitto4Not reported
Jetty242,473

These four are Eclipse Foundation projects, part of the portfolio registered in section 14.2. The runs of the three Apache Software Foundation projects, Tomcat, Kafka and Hadoop, remain to be completed or located; they may exist on an instance not yet surveyed.

14.5. General open source ecosystem benchmark #

Beyond the SME and iconic projects, the toolkit was run on the OCCTET ORT Server, together with task T4.4, on 96 widely used open source projects. They cover 10 language ecosystems: C/C++, C#, Swift, Rust, Ruby, Python, Go, PHP, JavaScript and JVM. About ten repositories were chosen per ecosystem, by download counts and community adoption.

IndicatorValue
Projects analysed96
Failed runs2, both with usable partial results
Packages identified10,043
Vulnerabilities, by severity120 critical, 525 high, 468 medium, 87 low

The same instance also analyses the repositories of the AboutCode organisation behind the toolkit’s federated data source: VulnerableCode, FederatedCode and PurlDB.

14.6. Consolidated results of the instance #

Runs

IndicatorValue
Organisations / products / repositories registered18 / 561 / 4,417
ORT runs4,978
Completed: finished, or finished with issues792 + 2,728 = 3,520, i.e. 78.9% of the 4,463 runs that had ended
Failed943
In progress at extraction515
ORT Server releases used15, from 0.64.0 to 0.94.0

What finished with issues means. A run gets this status when at least one job completed but reported issues. Of the 2,728 runs concerned, the evaluator reported policy rule violations in 2,685 and the analyzer reported dependency resolution problems in 906. Finished with issues therefore mixes compliance findings to review (rule violations) with technical problems that make the results incomplete (dependency resolution, provenance, advisory errors).

Vulnerabilities and packages

IndicatorValue
Distinct vulnerability identifiers7,184, an upper bound: aliases are not merged, so the same vulnerability can appear under a CVE and a GHSA identifier
Identifiers reported by OSV / VulnerableCode / both3,999 / 3,095 / 195, matched by identical identifier only
VulnerableCode errors≈50,700 HTTP 500 errors, ≈31,100 unhandled HTTP 301 redirects, ≈19,200 timeouts, raised as issues by the advisor: the VulnerableCode results are partial
Distinct packages: type, namespace, name and version84,663
Distinct components: type, namespace and name, versions ignored29,058

Reports. 26,098 report and SBOM files were generated.

Users. Between April and October 2026, 37 user accounts were created in the realm occtet. Ten are linked to a GitHub account and 28 have a local password. These categories overlap: one account can have both, and accounts created through GitHub may have no password.

Annex B details these figures: the issues of each job, the vulnerability identifiers by prefix, the vulnerability and package records, the advisor results and the packages by ecosystem.

14.7. Limits observed #

  1. Failure analysis is incomplete. 1,118 jobs failed; 618 of them (55%) carry no error message, and 12 failed runs have no failed job recorded. Among the failures that can be classified, the identified causes are:

    • the requested branch does not exist, because the campaign used a fixed revision (69);
    • Git sub-modules could not be fetched (28);
    • inconsistent analyzer results with duplicate identifiers (40);
    • the scanner rejected checkout paths (57) or found duplicate stored scan results (34);
    • the reporter ran out of memory (57).

    These causes point to repository specifics and to sizing. The unclassified share is too large to attribute the failures to repositories in general. Since then, the memory limit of the reporter has been raised to 6 GiB, with 75% of it for the heap (section 21.6).

  2. VulnerableCode reliability. Frequent server errors, unhandled redirects and timeouts make the VulnerableCode results incomplete. Moving to the endpoint’s final URL to avoid the redirects, plus retries, needs to be investigated. The local VulnerableCode instance (section 6.8) is another option once its first import is complete; ORT Server can be pointed to it through the advisor configuration (section 21.3.2).

  3. Throughput for large batches. Analyses take minutes, but large batches wait for cluster capacity (section 14.2, Scenario B – Analysis campaign on the Eclipse Foundation portfolio).

  4. Per-organisation configuration. The VulnerableCode key must be defined in each organisation.

  5. History of the first campaigns lost. Until October 2026, a run was deleted 90 days after it finished once its repository had been analysed again. About 5,200 runs created between 19 May and 3 June 2026 were deleted, among them the June 2026 runs of eclipse-research-labs. The retention is now 365 days (section 9.3).

  6. Curation workflow not validated. Curation, VEX and the curated export have not been exercised on the instance (section 14.1, Scenario A – Integration test between the Curator and ORT Server), and no SME feedback on the Curator workflow is recorded (section 14.3). So far, the Curator has had only limited external use.

  7. Policy rules. The evaluator applies the example rules of the ORT project, with their placeholders, not a reviewed OCCTET policy (section 21.1). The rule violations counted in section 14.6 come from these rules.

  8. Build ecosystems not covered. Some build ecosystems are not supported yet: vcpkg (C++) and some older, embedded-targeted C++ toolchains surfaced during SME testing, and Mahlo GmbH declined for that reason (section 14.3). Embedded targets and C++ are a known difficult area, for commercial services too. The Yocto analysis developed during the project, and run for DH Electronics, shows that such areas can be covered, with technology-specific work; vcpkg could be handled the same way. BENNING, a prospective contact outside the SME definition, raised the same need for its mixed environment: old and new Yocto-based builds alongside JavaScript and web components. It said it would try the toolkit, and asked for better embedded support and for CRA-ready report models.

  9. Onboarding partly manual. Most SME runs so far were carried out by Double Open staff on the SMEs’ behalf, rather than through self-service use.

14.8. Assessment against the Grant Agreement commitments #

Commitment (section 4.2)StatusEvidenceOpen items (section 15.5, Operational readiness matrix)
T4.3: a ready-to-use reference solution, deployed as a cloud service with containersAchieved for ORT Server, the Occtet Curator, Keycloak and a local VulnerableCode instance is also deployed. End users install nothing: they sign in with an e-mail address or a GitHub account, permissions are then granted, and the API, CLI and CI cover automated use4.5, 5, 6, 10 to 12Backup and restore; Continuity; Vulnerability data
T4.3: a toolchain that allows end-to-end testsValidated up to the import of results; curation, VEX and the export of a curated SBOM are validated on the instance6.9, 14.1Validation
T4.3: usable by SMEsDemonstrated for ORT Server, on Double Open’s hosted instance for early adopters; and more recently the curator5.3, 13, 14.3Onboarding; Validation
T4.3: feasibility at scale and path to a fully operational SaaSFeasibility shown on the Eclipse Foundation portfolio and on 96 projects in 10 language ecosystems.14.2, 14.5, 14.7, 15Backup and restore; Identity; Network; Observability; Capacity
Milestone 8 – SME validationProduct count reached: 19 SME products against a target of 15. Qualitative feedback is recorded for three SMEs; the outreach and the collection of written, actionable feedback continue until the end of the project in October 202614.3Validation
Milestone 9 – FOSS validationRun data confirmed for Adoptium, Eclipse IDE, Eclipse Mosquitto and Jetty; the runs of Tomcat, Kafka and Hadoop remain to be completed or located14.4Validation
Open source and EU visibilityAchieved: every component is open source, the deployment material is public, and the installation and its documentation carry the funding acknowledgement4.5, 7.4, 15.1–

15. Operations, continuity and costs #

15.1. Hosting and continuity #

ItemStatus
Hosting until the end of the project (31 October 2026)OVHcloud Public Cloud, region SBG5, operated by the Eclipse Foundation
Hosting after 31 October 2026Confirmed: the platform remains available, hosted by the Eclipse Foundation, until early 2027
Administration and support after 31 October 2026To be specified
Kubernetes control planeOperated by OVHcloud (MKS Free plan, 99.5% SLO)
EnvironmentsProduction only. A local installation on minikube (section 12) tests the changes of the deployment repositories before they are merged

Sustainability of the software. The components of the toolkit do not depend on this instance. They are open source projects, developed in public repositories (section 4.2, Grant Agreement reference):

ComponentProject and repositoriesLicence
ORT ServerEclipse Apoapsis, a project of the Eclipse Foundation (https://projects.eclipse.org/projects/technology.apoapsis): https://github.com/eclipse-apoapsis/ort-server, Helm charts https://github.com/eclipse-apoapsis/helm-chartsApache-2.0
OSS Review Toolkithttps://github.com/oss-review-toolkit/ortApache-2.0
Occtet Curatorhttps://github.com/Bitsea/Occtet-Curator, Helm chart https://github.com/Bitsea/Occtet-Curator-Helm-ChartApache-2.0; Helm chart EPL-2.0
VulnerableCode, FederatedCodehttps://github.com/aboutcode-org/vulnerablecode, https://github.com/aboutcode-org/federatedcodeApache-2.0
OCCTET deployment materialPublic repositories of the Eclipse Foundation GitLab (section 4.5)EPL-2.0; Keycloak theme MIT; ORT Server configuration Apache-2.0

Anyone can use these projects, propose changes through pull or merge requests, and fork them. After the platform stops, organisations can still reproduce the installation (sections 10 and 11) or build on the upstream projects.

15.2. Maintenance and upgrades #

ComponentHow it is upgraded
ORT Server stackGitLab CI deploys the umbrella chart on every merge to the default branch. Renovate opens merge requests for chart and image updates. Manual alternative: make diff-ort-server, then make deploy-ort-server in occtet-helm-chart. The release counted 238 Helm revisions on 14 October 2026.
Occtet CuratorGitLab CI deploys the umbrella chart on every merge to the default branch. The version of the Bitsea chart is pinned in Chart.yaml, and Renovate opens a merge request when Bitsea publishes a new one: compare its defaults with values.yaml and review the effect with make diff in the Curator repository, or make diff-curator. Changes of the chart itself are proposed to the Bitsea repository, which publishes a version on each tag. The images use latest with pull policy Always: a restart of the deployments (make restart-all in the Curator repository) pulls the latest Bitsea images and can change the running version, so record the digests of the running images before restarting.
KeycloakGitLab CI deploys the chart on every merge to the default branch; Renovate proposes the CloudPirates chart updates. Before a version bump, read the Keycloak upgrading guide for every version crossed and back up the database: Keycloak migrates its schema at start-up and cannot go back. For a theme update, merge into the theme repository, then restart Keycloak (section 7.1).
VulnerableCodemake diff-vulnerablecode, then make deploy-vulnerablecode in occtet-helm-chart. For a new version, set image.tag (or the chart’s appVersion) and read the upstream changelog first. The new web pod applies the database migrations, then the scheduler and the workers restart. Compare the branding templates with the new upstream ones (section 7.3).
Chart repositoriesTest a change on the local installation of section 12 before proposing it (make deploy-<component> ENV=dev deploys the local clone). In occtet-helm-chart, Renovate opens a merge request when the repository of a chart has new commits. Review the effect with make diff-<component>, merge, then run make deploy-<component>.
Entry pointmake deploy-ingress-nginx and make deploy-cert-manager, whose versions are pinned in the Makefile of occtet-helm-chart and updated by Renovate; make deploy-entrypoint for the ClusterIssuers, Ingresses and proxies

15.3. Infrastructure cost estimate (one year) #

The estimate covers infrastructure only. It uses OVHcloud list prices excluding VAT (https://www.ovhcloud.com/fr/public-cloud/prices/, consulted on 6 October 2026):

ItemQuantityUnit pricePer monthPer year
Instance b2-15, monthly billing4€48.05 / month€192.20€2,306.40
Instance b2-30, monthly billing2€97.24 / month€194.48€2,333.76
Managed Kubernetes control plane (Free plan)1included€0.00€0.00
Load Balancer S1€0.0083 / hour€6.06€72.71
Gateway S1€0.0028 / hour€2.04€24.53
Floating IP1€0.0027 / hour€1.97€23.65
Block Storage High Speed~338 GB€0.000119 / GB / hour€29.36€352.34
Block Storage High Speed – VulnerableCode volumes102 GB€0.000119 / GB / hour€8.86€106.33
Block Storage Classic~269 GB€0.000059 / GB / hour€11.59€139.03
Public traffic–included€0.00€0.00
Total excluding VAT€446.56€5,358.75
VAT at 19%€1,018.16
Total including VAT€6,376.91

Storage reconciliation.

BasisClassicHigh SpeedTotal
Volumes in use by workloads before VulnerableCode (section 21.6)209 GiB228 GiB437 GiB
All volumes known to Kubernetes, including 120 GiB of volumes no longer attached to a workload319 GiB238 GiB557 GiB
September 2026 invoice, average provisioned~269 GB~338 GB~607 GB

The VulnerableCode volumes are left out of this comparison, since they did not exist in September. The difference between the invoice and the volumes known to Kubernetes comes from volumes created outside Kubernetes, or changed during September. It has to be reconciled in the OVHcloud console. Removing the unattached volumes would save about €67 per year.

Not included:

Scaling unit costs (per year, excluding VAT): one more b2-15 node costs €576.60, one more b2-30 node €1,166.88, and the MKS Standard plan €788.40. The Standard plan has a 99.99% SLA but is not available in SBG5.

15.4. Scalability #

Demonstrated. 4,978 runs between 12 May and 4 October 2026, and up to 31 jobs in parallel on 32 vCPU and 120 GB RAM (sections 14.2 (Scenario B – Analysis campaign on the Eclipse Foundation portfolio) and 14.6 (Consolidated results of the instance)). The waiting times of section 14.2 also show the limit: large batches wait for capacity.

Horizontal scaling. Every analysis step runs as an independent Kubernetes Job, so throughput grows with the number of nodes. The node pool can be resized or autoscaled.

Workload separation. The local VulnerableCode instance shares the node pool with the analysis workers, and its requests (2.75 vCPU, 8.4 GiB) reduce the capacity left for analyses. A dedicated pool for analysis workers, and separating bulk campaigns from interactive runs, would keep interactive use responsive.

Availability. Databases and message brokers run as single instances. Higher availability requires a region offering the MKS Standard plan, and replicated or managed data services.

15.5. Operational readiness matrix #

Owners and target dates are to be agreed between the partners responsible for the platform after 31 October 2026.

AreaControl in placeLimitActionOwnerTarget date
Backup and restoreManual dump and restore procedures documented for the Keycloak and VulnerableCode databases, in the READMEs of their repositoriesNo scheduled backup of the four databases, the Curator project files or the ORT Server encryption keys; no restore testedImplement scheduled database backups and volume snapshots, back up the encryption Secrets, document and test a restore for every componentTo be agreedTo be agreed
ContinuityPlatform hosted by the Eclipse Foundation until early 2027; open source components and deployment material (section 15.1)Administration and support not specified; what follows early 2027 not specifiedFormalise responsibilities and the support channel; decide what follows early 2027To be agreedBefore 31 October 2026
IdentitySSO, MFA available, security headersapi-access client shared between ORT Server administration and the Curator; password grant enabled on the public client ort-server; on the OCCTET instance, ort-server also accepts any redirect URI and web origin, with the implicit flow enabled; authentication policies to strengthenApply the client settings of section 11.6; dedicated Curator client; least-privilege clients; scripts authenticated with offline tokens of ort-server-cli instead of the password grant; password policy, brute-force detection, e-mail verification, MFA for administrators; audit eventsTo be agreedTo be agreed
NetworkHTTPS for public endpoints, path filtering; NetworkPolicies for the VulnerableCode PostgreSQL and RedisNo NetworkPolicies for the other services; unencrypted internal traffic; direct service exposure outside the ingressNetworkPolicies per namespace; remove non-ingress exposure; restrict administrative interfacesTo be agreedTo be agreed
Ingress controlleringress-nginx 1.13.2Upstream project retired on 24 March 2026Migrate to a maintained controller or a Gateway API implementationTo be agreedTo be agreed
Reverse proxiesPath filtering, /aliveORT Server, Curator and VulnerableCode proxies forward X-Forwarded-Proto: http; VulnerableCode API links use http://Apply the configuration of section 21.4To be agreedTo be agreed
ObservabilityLoki, Grafana with the statistics dashboards of ORT Server, metrics-server, ORT job supervisionNo alerting; ephemeral logs; Promtail end of life since 2 March 2026Metrics and alerting, availability probes, persistent logs, migration to Grafana AlloyTo be agreedTo be agreed
Data retentionRuns kept 365 days after they finished, and the latest run of each repository kept (section 9.3)Runs deleted by the 90-day retention before October 2026 are lost, with their history in the dashboards; the database of ORT Server grows with the retained runs (61 GB of 200 GiB used on 6 October 2026)Communicate the retention period to users; monitor the volume of the PostgreSQL of ORT Server; decide the retention after the projectTo be agreedTo be agreed
SecretsKubernetes Secrets referenced by existingSecret; masked and protected CI/CD variables for the access jobNo external store, no rotation; the access job holds an offline token of an ORT Server superuser and the secret of api-access, which has realm-management rightsExternal secret store, rotation procedure; least privilege for the access job: a technical account that is admin of the public organisations only, and a Keycloak client limited to user role mappingsTo be agreedTo be agreed
ReleasesRenovate in the ORT Server, Curator, Keycloak, theme and occtet-helm-chart repositories; CI deployment of ORT Server, the Curator and Keycloak; Helm tests for the entry point and VulnerableCode; local installation on minikube to test changes (section 12)Curator, NATS and Ollama on latest; no shared staging environmentVersioned images, staging environment, post-deployment smoke test (scenario A)To be agreedTo be agreed
Vulnerability dataOSV and VulnerableCode advisors; public VulnerableCode service and OCCTET instance, selected per run in ORT ServerErrors from the public VulnerableCode service; first import of the OCCTET instance not complete; Curator vulnerability service fixed on the public service; aliases not merged in the statisticsFix redirects and retries; monitor advisor errors; make the VulnerableCode address of the Curator configurableTo be agreedTo be agreed
Policy rulesExample rules of the ORT projectNot a reviewed OCCTET policy; placeholders of the exampleDefine and review an OCCTET rule set with the partnersTo be agreedTo be agreed
Ecosystem coverageORT analyzer for the main language ecosystems (section 14.5, General open source ecosystem benchmark); Yocto analysis developed for one SMEvcpkg and some older embedded C++ toolchains not supportedTechnology-specific support, for example for vcpkg, following the Yocto approachTo be agreedTo be agreed
OnboardingAccess on request; shared realm; getting-started guide; hourly job giving every account read access to the public organisations of ORT Server and the curator role; scripts for large portfoliosPartly manual: most SME runs carried out by Double Open staff; roles on private organisations and Curator organisation membership granted by hand; VulnerableCode key defined per organisationSelf-service onboarding with fewer manual stepsTo be agreedTo be agreed
ValidationIntegration test up to import; SME use of ORT Server on 19 products, mostly on Double Open’s hosted instance; run data for 4 of the 7 iconic projectsCuration, VEX and export not validated; no SME feedback on the Curator;Run and document the complete Curator workflow with an SME; complete or locate the runs of the three Apache projectsTo be agreedTo be agreed
Capacity6 nodes, 31 concurrent jobs observedQueuing for large batchesNode pool autoscaling, dedicated worker pool, fair schedulingTo be agreedTo be agreed

16. Conclusion #

D4.1 delivers part of the OCCTET toolkit as a reference implementation run as a containerised cloud service: the Eclipse Apoapsis ORT Server and the Occtet Curator, with Keycloak for single sign-on and the VulnerableCode and OSV vulnerability data sources. Partners can reproduce it by following the installation procedure of sections 10 to 12.

The integration test, the analysis campaign on the Eclipse Foundation portfolio and the ecosystem benchmark ran on the OCCTET instance (sections 14.1, 14.2 and 14.5). Seven of the eight SMEs that tested ORT Server did so on Double Open’s own hosted instance; only Datasance tested on the OCCTET instance (section 14.3). Section 14.8 assesses these results: the reference installation is in place, analysis at scale is demonstrated and the product target of Milestone 8 is reached, while the collection of SME feedback and the runs of the three Apache projects of Milestone 9 remain open.

The path to a fully operational service is set out in section 15.5, Operational readiness matrix. The main open items are scheduled backups, the administration and support arrangements of the platform, which remains available until early 2027, identity hardening, alerting, and the validation of the complete Curator workflow with an SME. Beyond the platform, the software remains available as open source projects, open to contributions and forks by the community (section 15.1, Hosting and continuity).

17. References #

TopicLink
OCCTET projecthttps://occtet.eu
OCCTET toolkit and getting-started guidehttps://occtet.eu/toolkit/, https://occtet.eu/toolkit/getting-started/
OCCTET public deliverableshttps://occtet.eu/resources/deliverables/
D2.1 – CRA SME Requirements and Self-Assessment Checklistshttps://occtet.eu/resources/deliverables/all/D2.1-CRA-SME-requirements-and-self-assessment-checklists-v1.3.pdf
D2.2 – SME CRA Self-Assessment Model & Surveyhttps://occtet.eu/resources/deliverables/all/D2.2-SME-CRA-Self-Assessment-Model-&-Survey-v1.1.pdf
D3.1 – FedDB Betahttps://occtet.eu/resources/deliverables/all/D3.1-FedDB-Beta-Del-v1.1.pdf
D3.2 – BasicChainhttps://occtet.eu/resources/deliverables/all/D3.2%e2%80%93BasicChain-v1.1.pdf
D3.3 – Toolkit Demohttps://occtet.eu/resources/deliverables/all/D3.3-Toolkit-Demo-Del-v1.1.pdf
D3.4 – Federated Database Finalhttps://occtet.eu/resources/deliverables/all/D3.4-FedDB-Final-Del-v1.0.pdf
D4.2 – Tools Comparison Reporthttps://occtet.eu/resources/deliverables/#d42 (PDF not yet published)
OCCTET Description of ActionOCCTET Project Consortium, Description of Action, Grant Agreement No. 101190474, 2024
Cyber Resilience ActRegulation (EU) 2024/2847 of the European Parliament and of the Council of 23 October 2024, https://eur-lex.europa.eu/eli/reg/2024/2847/oj
OCCTET deployment repositorieshttps://gitlab.eclipse.org/groups/eclipsefdn/open-source-services/occtet-eu/reference-installation
ORT Serverhttps://github.com/eclipse-apoapsis/ort-server, documentation https://eclipse-apoapsis.github.io/ort-server/
ORT Server Helm chartshttps://github.com/eclipse-apoapsis/helm-charts
ORT Server example configurationhttps://github.com/mnonnenmacher/ort-server-config
OSS Review Toolkithttps://oss-review-toolkit.org/ort/
Occtet Curatorhttps://github.com/Bitsea/Occtet-Curator, user guide https://bitsea.github.io/Occtet-Curator/
Occtet Curator Helm charthttps://github.com/Bitsea/Occtet-Curator-Helm-Chart, published as oci://ghcr.io/bitsea/charts/occtet-curator
Curator ↔ ORT Server integration (PR #250)https://github.com/Bitsea/Occtet-Curator/pull/250
CloudPirates Helm chartshttps://github.com/CloudPirates-io/helm-charts
Keycloakhttps://www.keycloak.org/documentation, upgrading guide https://www.keycloak.org/docs/latest/upgrading/index.html
Keycloakifyhttps://docs.keycloakify.dev/
Eclipse Foundation theme “Solstice”https://gitlab.eclipse.org/eclipsefdn/software-dev/frameworks-and-tools/keycloak-solstice-theme (not publicly readable)
VulnerableCodehttps://public.vulnerablecode.io, https://github.com/aboutcode-org/vulnerablecode, documentation https://vulnerablecode.readthedocs.io/
FederatedCode, PurlDBhttps://github.com/aboutcode-org/federatedcode, https://github.com/aboutcode-org/purldb
OSVhttps://osv.dev/
OVHcloud Public Cloud priceshttps://www.ovhcloud.com/fr/public-cloud/prices/
OVHcloud Load Balancer for MKShttps://docs.ovhcloud.com/en/guides/public-cloud/containers-orchestration/managed-kubernetes/expose-applications-using-load-balancer
OVHcloud: secure an Nginx Ingress with cert-managerhttps://docs.ovhcloud.com/en/guides/public-cloud/containers-orchestration/managed-kubernetes/secure-nginx-ingress-cert-manager
Ingress-NGINX retirementhttps://kubernetes.io/blog/2025/11/11/ingress-nginx-retirement/
Promtail end of life, migration to Alloyhttps://grafana.com/docs/loki/latest/send-data/promtail/, https://grafana.com/docs/alloy/latest/set-up/migrate/from-promtail/
cert-managerhttps://cert-manager.io/docs/
GitLab Agent for Kuberneteshttps://docs.gitlab.com/user/clusters/agent/

18. Annex A: Glossary #

TermMeaning
ACMEAutomatic Certificate Management Environment, the protocol used by Let’s Encrypt
AIArtificial intelligence
APIApplication programming interface
CRACyber Resilience Act, Regulation (EU) 2024/2847
CVE, GHSAVulnerability identifiers (Common Vulnerabilities and Exposures; GitHub Security Advisory)
CycloneDX, SPDXStandard SBOM formats
DEMDeliverable type: demonstrator, pilot, prototype
DO, ECLDouble Open; Eclipse Foundation Europe
DoADescription of Action, Annex 1 of the Grant Agreement
ECCCEuropean Cybersecurity Industrial, Technology and Research Competence Centre
EUEuropean Union
FOSSFree and open source software
HelmPackage manager for Kubernetes
LLMLarge language model
MFAMulti-factor authentication
MKSOVHcloud Managed Kubernetes Service
OIDCOpenID Connect
ORTOSS Review Toolkit
PKCEProof Key for Code Exchange
PURLPackage URL
RBACRole-based access control
RQRedis Queue, the job queue used by the VulnerableCode scheduler and workers
SaaSSoftware as a service
SASTStatic application security testing
SBOMSoftware Bill of Materials
SMESmall and medium-sized enterprise
TLSTransport Layer Security
VEXVulnerability Exploitability eXchange
WPWork package

19. Annex B: Statistics – detailed results and definitions #

Source. The figures are extracted with the script statistics.sh of the repository occtet-stats (section 13.4, Automation for large portfolios), through the APIs of the instance only, without database access:

The script first saves the API responses to a directory, then computes Markdown tables from these files. The figures can therefore be recomputed and checked later without a new extraction, and an interrupted extraction resumes where it stopped. Run the script when no analysis campaign is running: under load, ORT Server can answer slowly, and the load balancer closes a request left 50 seconds without an answer.

Definitions.

Detailed results (section 14.6, Consolidated results of the instance).

Jobs that reported issues in the 2,728 runs finished with issues:

JobJobs finished with issuesMain nature of the issues
Evaluator2,685Policy rule violations reported
Analyzer906Dependency resolution problems: NuGet (≈7,500 issues), unresolved package provenance (≈3,400), Maven and Gradle artefacts not resolved (≈2,500), projects without dependency information (≈570)
Reporter621Issues during report generation
Scanner559Provenance resolution, including Git fetches for nested repositories (≈1,200)
Advisor371Errors from the VulnerableCode service (section 14.6)

Vulnerability and package records:

IndicatorValueDefinition
Distinct vulnerability identifiers, by prefixGHSA 3,514; CVE 2,904; PYSEC 345; GO 157; RUSTSEC 141; others7,184 distinct external_id values in total, without alias merging
Vulnerability records16,022Records stored by ORT Server; one identifier can have several records
Advisor results120,587 (VulnerableCode 94,724; OSV 25,863)One result per package and advisor in each run, so repeated runs count again
Package records≈94,100Records in ORT Server’s package table; one identifier can have several records

Package records by ecosystem:

EcosystemRecordsEcosystemRecords
Maven40,988Go2,005
npm34,788Bazel729
PyPI6,280Composer656
Crates (Rust)5,110RubyGems619
NuGet2,643CocoaPods, Pub, Swift, Carthage310

20. Annex C: Reference images #

Container images deployed by the installation of section 11, with their versions. The tags latest, stable and pg17-trixie move: the image behind them changes over time.

ORT Server

ImageVersion
ghcr.io/eclipse-apoapsis/ort-server-core0.95.0
ghcr.io/eclipse-apoapsis/ort-server-ui0.95.0
ghcr.io/eclipse-apoapsis/ort-server-orchestrator0.95.0
ghcr.io/eclipse-apoapsis/ort-server-config-worker0.95.0
ghcr.io/eclipse-apoapsis/ort-server-analyzer-worker0.95.0
ghcr.io/eclipse-apoapsis/ort-server-advisor-worker0.95.0
ghcr.io/eclipse-apoapsis/ort-server-scanner-worker0.95.0
ghcr.io/eclipse-apoapsis/ort-server-evaluator-worker0.95.0
ghcr.io/eclipse-apoapsis/ort-server-reporter-worker0.95.0
ghcr.io/eclipse-apoapsis/ort-server-maintenance-tasks (housekeeping CronJobs)0.95.0
docker.io/library/postgres18.6
docker.io/library/rabbitmq4.3.6-management
docker.io/library/busybox (RabbitMQ initContainer)1.37.0
docker.io/grafana/loki2.6.1
docker.io/grafana/promtail3.5.1
docker.io/grafana/grafana10.3.3
quay.io/kiwigrid/k8s-sidecar (Grafana sidecar)1.19.2
docker.io/alpine/k8s (CronJob pod-cleanup)1.36.1

Keycloak

ImageVersion
docker.io/keycloak/keycloak26.8.0
docker.io/library/postgres (database)18.0
docker.io/library/postgres (initContainer wait-for-postgres)18.6
docker.io/library/maven (theme initContainer)3.10.0-eclipse-temurin-27, referenced by digest only

Entry point

ImageVersion
registry.k8s.io/ingress-nginx/controllerv1.13.2
registry.k8s.io/ingress-nginx/kube-webhook-certgen (admission webhook jobs)v1.6.2
quay.io/jetstack/cert-manager-controllerv1.18.2
quay.io/jetstack/cert-manager-webhookv1.18.2
quay.io/jetstack/cert-manager-cainjectorv1.18.2
quay.io/jetstack/cert-manager-startupapicheck (post-installation check)v1.18.2
docker.io/library/nginx (reverse proxies of ORT Server, the Curator, Keycloak and VulnerableCode)stable

Occtet Curator

ImageVersion
ghcr.io/bitsea/occtet-frontendlatest
ghcr.io/bitsea/occtet-nats-ort-run-start-servicelatest
ghcr.io/bitsea/occtet-nats-process-run-servicelatest
ghcr.io/bitsea/occtet-nats-vulnerability-servicelatest
ghcr.io/bitsea/occtet-nats-spdx-servicelatest
ghcr.io/bitsea/occtet-nats-spdx-export-servicelatest
ghcr.io/bitsea/occtet-nats-cyclonedx-servicelatest
ghcr.io/bitsea/occtet-nats-cyclonedx-export-servicelatest
ghcr.io/bitsea/occtet-nats-licensematcher-servicelatest
ghcr.io/bitsea/occtet-nats-ai-licensematcher-servicelatest
ghcr.io/bitsea/occtet-nats-copyrightfilter-servicelatest
ghcr.io/bitsea/occtet-nats-ai-copyrightfilter-servicelatest
ghcr.io/bitsea/occtet-nats-foss-report-servicelatest
ghcr.io/bitsea/occtet-nats-download-servicelatest
docker.io/library/natslatest
docker.io/ollama/ollamalatest
docker.io/pgvector/pgvector (PostgreSQL with pgvector)pg17-trixie
registry.k8s.io/sig-storage/nfs-provisionerv4.0.8
docker.io/library/busybox (initContainers)1.36

VulnerableCode

ImageVersion
ghcr.io/aboutcode-org/vulnerablecode40.1.0
docker.io/nginxinc/nginx-unprivileged1.30-alpine
docker.io/library/postgres18.6
docker.io/library/redis8.10.2
docker.io/library/busybox (initContainers)1.38

21. Annex D: Configuration reference #

The values, files and Kubernetes Secrets that a reproduction reads or adapts, component by component; the installation steps of section 11 point to them. The Keycloak realm is configured in section 11.6.

21.1. ORT Server #

Keycloak integration (keys under ort-server: in values.yaml):

KeyValue
ui.authority, ui.clientIdhttps://keycloack.occtet.eu/realms/occtet, ort-server
core.keycloak.jwtUrihttps://keycloack.occtet.eu/realms/occtet/protocol/openid-connect/certs
core.keycloak.jwtIssuer, jwtRealm, jwtAudiencehttps://keycloack.occtet.eu/realms/occtet, occtet, ort-server
core.keycloak.subjectClientIdort-server
core.keycloak.clientId, accessTokenUrl, apiUrlapi-access, …/protocol/openid-connect/token, https://keycloack.occtet.eu/admin/realms/occtet
core.keycloak.existingSecretort-server-keycloak-credentials
core.cli.keycloakBaseUrl, keycloakRealm, keycloakClientIdhttps://keycloack.occtet.eu, occtet, ort-server-cli

Maintenance tasks (ort-server.tasks.configs in values.yaml). The values repeat the five tasks of the ORT Server chart, because Helm replaces a list instead of merging it, with one change: delete-old-ort-runs keeps the runs 365 days (dataRetention.ortRunDays: 365, DATA_RETENTION_ORT_RUN_DAYS of its CronJob) instead of 90 (section 9.3). Compare the list with the values of the chart when upgrading it.

Grafana (loki-stack.grafana.sidecar in values.yaml). The datasource sidecar provisions the ConfigMaps and Secrets labelled grafana_datasource=1: the Loki datasource and the database of ORT Server. The dashboard sidecar loads the ConfigMaps labelled grafana_dashboard=1 into the folder named by their annotation grafana_folder (section 9.4).

Configuration repository (occtet-ort-server-config, Apache-2.0). It started as a fork of mnonnenmacher/ort-server-config, the example configuration of the ORT Server authors, extended for OCCTET.

The chart enables the Git configuration provider (CONFIG_FILE_PROVIDER=git-config) for the core and the workers. At the start of a run, the config worker resolves the configuration once: the branch given in the run’s jobConfigContext, or the head of the default branch. It records the resulting commit, and every worker of the run reads the files at that commit. Nothing is deployed: a change applies to the runs started after it reaches the default branch. To test a change, push it to a branch and start a run on a test repository with that branch as jobConfigContext. An error in ort-server.params.kts makes every new run fail.

FileRead byRole
ort-server.params.ktsConfig worker, once per runCompletes and validates the job configurations. It:
• adds the report formats cyclonedx, ortresult, runstatistics, spdxdocument and webapp when the run requests none;
• disables the notifier;
• sets the ScanCode options minVersion 32.2.1 and maxVersion 33.0.0, so that only stored scan results of ScanCode versions in that range are reused;
• injects the VulnerableCode API key (section 21.3.1);
• adds the label createdBy with the ORT Server and ORT versions.
evaluator.rules.ktsEvaluatorPolicy rules: the example rules of the ORT project’s ort-config, selected by run labels. They keep the placeholders of the example and are not a reviewed OCCTET policy.
license-classifications.ymlEvaluator, reporterAbout 2,750 licences in 15 categories, generated from the ScanCode LicenseDB
copyright-garbage.ymlEvaluator, reporterCopyright statements to ignore, mostly scanner noise
reporter.how-to-fix-text-provider.ktsReporterHow-to-fix texts for issues; it currently returns none

The repository has no ort-server.conf and no resolutions.yml: ORT Server uses its default rule set and reporter settings, which point to these files, and no resolution applies to all runs.

Reference run configuration (body of POST /api/v1/repositories/{repositoryId}/runs, excerpt):

{
  "revision": "main",
  "jobConfigs": {
    "analyzer": { "allowDynamicVersions": true, "skipExcluded": true },
    "advisor": {
      "advisors": ["OSV", "VulnerableCode"],
      "config": {
        "OSV": { "options": { "serverUrl": "https://api.osv.dev" }, "secrets": {} },
        "VulnerableCode": { "options": { "serverUrl": "https://public.vulnerablecode.io/api/" }, "secrets": {} }
      },
      "skipExcluded": true
    },
    "scanner": {
      "scanners": ["ScanCode"],
      "createMissingArchives": true, "skipConcluded": true, "skipExcluded": true
    },
    "evaluator": {},
    "reporter": {
      "formats": ["CycloneDX", "SpdxDocument", "WebApp"],
      "config": {
        "CycloneDX": { "options": { "outputFileFormats": "XML,JSON" }, "secrets": {} },
        "SpdxDocument": { "options": { "outputFileFormats": "YAML,JSON" }, "secrets": {} }
      }
    }
  }
}

21.2. Occtet Curator #

Frontend environment (occtet-curator.frontend.env in the values.yaml of occtet-curator-helm-chart):

VariableValue
ortBaseUrlhttps://ort-server.occtet.eu
ortTokenUrlhttps://keycloack.occtet.eu/realms/occtet/protocol/openid-connect/token
ortClientIdapi-access. The client ID and secret of the user login come from the keys keycloak-client-id and keycloak-client-secret of the Secret occtet-curator-secrets
keycloakIssuerUrihttps://keycloack.occtet.eu/realms/occtet
frontendBaseUrlhttps://curator.occtet.eu/boc (login redirect URI: {frontendBaseUrl}/login/oauth2/code/keycloak)
springProfilesActivelive,oidc
maxFileSize, maxRequestSize200MB: largest upload accepted by the frontend (MAX_FILE_SIZE, MAX_REQUEST_SIZE)

Other OCCTET values (under occtet-curator): imageRegistry ghcr.io/bitsea, imageTag latest and imagePullPolicy Always (section 15.2); postgresql.persistence.size and nats.persistence.size 50Gi, the sizes of the existing volumes. global.namespace (curator) stays under global. The other settings, such as the JVM options of the frontend and the resource requests and limits (section 21.6), are the defaults of the Bitsea chart 0.1.0.

OpenID Connect. Scopes requested: openid, profile, email and ort-server-api-access. User name attribute: preferred_username.

Administrator settings:

KeyPurpose
general.base_pathSystem path where project sources are stored
general.vulnerability_db_tokenVulnerableCode API token, stored encrypted
ort.ort_license_match, ort.ort_copyright_filterRun the licence matcher or the copyright filter after an ORT run

21.3. VulnerableCode #

Two VulnerableCode instances are available:

InstanceURLUsed byAPI key
Public servicehttps://public.vulnerablecode.ioORT Server advisor; Curator vulnerability serviceRequired
OCCTET instance on this cluster (section 6.8)https://vulnerablecode.occtet.euORT Server advisor, when the run sets its serverUrlNot required

21.3.1. Public service #

Its REST API is https://public.vulnerablecode.io/api/v3/, documented at https://public.vulnerablecode.io/api/docs/. To reproduce the toolchain configuration, request an API key at https://public.vulnerablecode.io/account/request_api_key/. Then configure the service in each tool of the chain, as described below.

ORT Server

  1. URL. The run configuration enables the advisor VulnerableCode with serverUrl: https://public.vulnerablecode.io/api/.
  2. API key. Create an ORT Server secret named vulnerableCodeApiKey, whose value is the API key requested above, in the organisation, or in a product or repository, that runs the analyses (section 13.2).
  3. Injection. When the public instance is used, ort-server.params.kts injects the key into the advisor configuration:
it.takeUnless {
    it.options["serverUrl"] == null || it.options["serverUrl"] == "https://public.vulnerablecode.io/api/"
} ?: it.copy(secrets = it.secrets + mapOf(
    "apiKey" to ResolvableSecret(name = "vulnerableCodeApiKey", source = SecretSource.USER)))

If the secret is missing, the advisor job fails (section 13.2).

Occtet Curator. An administrator enters the API key in the Curator configuration. The vulnerability service then sends bulk queries by package URL to POST /api/v3/packages on https://public.vulnerablecode.io.

Observed reliability. Many calls from ORT Server to the public VulnerableCode service failed during the period (sections 14.6 (Consolidated results of the instance) and 14.7 (Limits observed)), so VulnerableCode results on this instance are incomplete.

21.3.2. Local instance #

Deployment settings (values.yaml of the VulnerableCode deployment repository; the chart defaults are in vulnerablecode/values.yaml):

KeyValue
image.tagEmpty, so the chart’s appVersion applies: 40.1.0
config.allowedHosts, config.csrfTrustedOriginsvulnerablecode.occtet.eu, https://vulnerablecode.occtet.eu
config.requireAuthenticationfalse: the REST API needs no API key
web.ingress.enabledfalse: the instance is published through the entry point (section 6.4)
workers.queues.default.replicaCount, workers.queues.high.replicaCount4 and 2, the upstream recommendation; the import resumed with one worker per queue (section 6.8)
postgresql.persistence.size100 GiB, on the cluster’s default storage class
existingSecretvulnerablecode-secrets, the chart default (section 21.5)

Use by the toolchain:

21.4. Recommended proxy configuration #

The ORT Server, Curator and VulnerableCode proxies of the chart occtet-entrypoint set X-Forwarded-Proto to $scheme, which is http behind the ingress. A reproduction should keep the protocol received from the ingress instead, as the Keycloak proxy already does. The proxy configurations are the templates charts/occtet-entrypoint/templates/_nginx-<service>.tpl; for ORT Server, the change is:

map $http_x_forwarded_proto $proxy_x_forwarded_proto {
  default $http_x_forwarded_proto;
  ''      $scheme;
}

server {
  listen 80;
  location /alive { access_log off; return 200 "OK\n"; }
  location /api {
    proxy_pass        http://ort_server;            # ort-server-core.ort-server.svc.cluster.local:8081
    proxy_set_header  Host $host;
    proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header  X-Forwarded-Proto $proxy_x_forwarded_proto;
    proxy_set_header  X-Forwarded-Host $host;
    proxy_set_header  Authorization $http_authorization;
  }
  location / {
    proxy_pass        http://ort_server_ui;         # ort-server-ui.ort-server.svc.cluster.local:8082
    proxy_set_header  Host $host;
    proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header  X-Forwarded-Proto $proxy_x_forwarded_proto;
  }
}

Applying the same change to the chart, for the three proxies, is part of section 15.5, Operational readiness matrix.

21.5. Kubernetes Secrets #

NamespaceSecretKeys
keycloakkeycloak-admin-secretadmin-password: password of the admin account, read at the first start only
keycloakocctet-keycloak-postgrespostgres-password and the connection details. Generated by the chart at the first installation; Keycloak uses it to connect to its database
keycloakkeycloak-postgresql-secretdb-username, db-password. Created by make secrets-create, but not used by the current values
ort-serverort-server-db-credentialsdatabase-password
ort-serverort-server-rabbitmq-credentialsrabbitmq-user, rabbitmq-password
ort-serverort-server-keycloak-credentialskeycloak-api-secret
ort-serverort-server-user-secrets-encryptionmaster-password, salt
ort-serverort-server-grafanaadmin-user, admin-password
curatorocctet-curator-secretsusername, postgres-password, database, ort-username, ort-password, keycloak-client-id, keycloak-client-secret
vulnerablecodevulnerablecode-secretssecret-key, altcha-hmac-key, postgres-password, db-username, db-password, db-name
defaultone TLS Secret per host nameCreated by cert-manager

The encryption material in ort-server-user-secrets-encryption is required to read the ORT Server user secrets. Losing it makes those secrets unrecoverable.

Do not delete occtet-keycloak-postgres: the chart would generate a new password that no longer matches the one stored in the database, and Keycloak could no longer connect.

PostgreSQL reads the VulnerableCode database credentials only when its volume is first initialised. Recreating vulnerablecode-secrets with new values while keeping the volume therefore breaks the database connection.

21.6. Resource requests and persistent volumes #

Requests and limits of CPU and memory set by the values of the components, and the persistent volumes of the installation.

Keycloak. Requests of 0.5 CPU and 512 MiB; limits of 1 CPU and 1 GiB.

ORT Server. Worker executions are started as Kubernetes Jobs, sized per worker:

WorkerCPU request / limitMemory request / limit
config1 / 21 GiB / 1 GiB
analyzer1 / 24 GiB / 6 GiB
advisor1 / 21 GiB / 1 GiB
scanner2 / 48 GiB / 12 GiB
evaluator1 / 28 GiB / 12 GiB
reporter1 / 34 GiB / 6 GiB
core (Deployment)1 / 42 GiB / 4 GiB

By default, the JVM in a container uses 25% of the memory limit for its heap. The orchestrator therefore passes -XX:MaxRAMPercentage=75 to the reporter jobs (variable REPORTER_JDK_JAVA_OPTIONS). The maintenance CronJobs of section 9.3 run with the same option, requests of 0.1 CPU and 512 MiB, and limits of 1 CPU and 2 GiB.

VulnerableCode (section 6.8):

ComponentCPU request / limitMemory request / limit
web0.5 / 2, plus 0.05 / 0.5 for nginx1 GiB / 4 GiB, plus 64 MiB / 256 MiB for nginx
scheduler0.1 / 0.5256 MiB / 1 GiB
worker-default, 4 replicas0.25 / 2 per replica1 GiB / 4 GiB per replica
worker-high, 2 replicas0.25 / 2 per replica1 GiB / 4 GiB per replica
PostgreSQL0.5 / 21 GiB / 4 GiB
Redis0.1 / 0.5128 MiB / 512 MiB

In total, the instance requests 2.75 vCPU and about 8.4 GiB of memory on the node pool it shares with the other services.

Persistent volumes. Sizes are the Kubernetes volume requests (GiB), which OVHcloud provisions and bills as GB of block storage.

NamespaceVolumeSizeStorage classNotes
ort-serverPostgreSQL data200 GiBcsi-cinder-classic61 GB used on 6 October 2026
ort-serverRabbitMQ data8 GiBcsi-cinder-classic
ort-serverShared volume mounted at /mnt/secrets1 GiBcsi-cinder-classicMounted by the core
keycloakPostgreSQL data8 GiBcsi-cinder-high-speed
curatorPostgreSQL data50 GiBcsi-cinder-high-speed674 MB used on 6 October 2026
curatorNATS JetStream50 GiBcsi-cinder-high-speed
curatorOllama models50 GiBcsi-cinder-high-speed
curatorNFS server backing volume100 GiBcsi-cinder-high-speedServes the ReadWriteMany class nfs, from which the 90 GiB project-data volume is allocated
vulnerablecodePostgreSQL data100 GiBcsi-cinder-high-speedCreated on 6 October 2026; database of 15 GB on 15 October 2026
vulnerablecodeRedis data2 GiBcsi-cinder-high-speed
Total in use539 GiB209 classic, 330 high-speed

Section 15.3 reconciles these volumes with the billed storage.