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 #
| Item | Value |
|---|---|
| Project title | Open 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 acronym | OCCTET |
| Grant agreement | 101190474 |
| Programme | Digital Europe Programme (DIGITAL), call DIGITAL-ECCC-2024-DEPLOY-CYBER-06 |
| Instrument | DIGITAL JU SME Support Action |
| Granting authority | European Cybersecurity Industrial, Technology and Research Competence Centre (ECCC) |
| Project start and duration | 1 November 2024, 24 months |
| Deliverable | D4.1 – Ready to use installation |
| Deliverable type | DEM – Demonstrator, pilot, prototype |
| Work package / task | WP4 – Validation – Use Cases / T4.3 |
| Dissemination level | PU – Public |
| Due date (DoA) | 31 October 2026 (month 24) |
| Submission date | 31 October 2026 (planned) |
| Version | 1.0 (draft 7) |
| Status | Draft – not yet approved |
| Date | 15 October 2026 |
| Lead beneficiary | DO – Double Open |
| Main author(s) | Martin von Willebrand, Sebastian Schuberth, … (list to be completed) |
| Contributing partner(s) | DO, BS, ECL |
| Reviewer | ECL – Eclipse Foundation Europe |
| Licence | CC BY 4.0 (Attribution 4.0 International) |
| Source | https://gitlab.eclipse.org/eclipsefdn/open-source-services/occtet-eu/reference-installation/reference-installation-documentation |
Version history
| Version | Date | Issued by | Status | Description |
|---|---|---|---|---|
| 0.1 | 2026-10-06 | DO | Draft | First draft |
| 0.2 | 2026-10-15 | ECL | Draft | Revised 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.

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:
- ORT Server: automated dependency, licence and vulnerability analysis: https://ort-server.occtet.eu
- Occtet Curator: SBOM curation and assessment: https://curator.occtet.eu
- VulnerableCode: vulnerability database: https://vulnerablecode.occtet.eu
- Keycloak: single sign-on shared by ort-server and the curator: https://keycloack.occtet.eu
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
| Claim | Evidence |
|---|---|
| Analysis at scale - Eclipse Foundation portfolio | Of 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 ecosystem | 96 widely used projects in 10 language ecosystems analysed on the OCCTET instance: 10,043 packages identified (section 14.5) |
| Vulnerability data is collected | 7,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 #
- 11.1. Getting the deployment material
- 11.2. Installation order
- 11.3. Step 1 – Cluster
- 11.4. Step 2 – Ingress controller and DNS
- 11.5. Step 3 – Certificate management
- 11.6. Step 4 – Keycloak
- 11.7. Step 5 – ORT Server
- 11.8. Step 6 – Occtet Curator
- 11.9. Step 7 – VulnerableCode
- 11.10. Step 8 – Entry point and verification
- 14.1. Scenario A – Integration test between the Curator and ORT Server
- 14.2. Scenario B – Analysis campaign on the Eclipse Foundation portfolio
- 14.3. SME use cases and feedback
- 14.4. Iconic open source projects
- 14.5. General open source ecosystem benchmark
- 14.6. Consolidated results of the instance
- 14.7. Limits observed
- 14.8. Assessment against the Grant Agreement commitments
- 21.1. ORT Server
- 21.2. Occtet Curator
- 21.3. VulnerableCode
- 21.3.1. Public service
- 21.3.2. Local instance
- 21.4. Recommended proxy configuration
- 21.5. Kubernetes Secrets
- 21.6. Resource requests and persistent volumes
4. Introduction #
4.1. Purpose and scope #
This document describes the reference installation delivered by task T4.3. It is written for three audiences:
- Project reviewers find what was delivered and how it was validated in sections 2 (Executive summary) and 4 (Introduction) to 9 (Monitoring and observability), then 14 (Demonstration and validation) to 16 (Conclusion).
- Organisations that reproduce the installation follow the installation procedure of sections 10 (Prerequisites), 11 (Installation) and 12 (Local installation on minikube), with the configuration reference of Annex D.
- Partners who use or operate the toolkit find access, onboarding and usage in sections 5 (Reference instance and access) and 13 (Onboarding and usage).
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:
- WP4, objective 2. Set up a production-ready reference implementation that easily onboards external SMEs and iconic open source projects.
- Task T4.3. Exploit the tools defined in WP3 to create a reference, ready-to-use, software-as-a-service solution. The solution is deployed on cloud infrastructure using container technology and is included in a whole toolchain. It must prove the feasibility of offering the service at scale and show the path to a fully operational SaaS (section 4.4, Expected results of task T4.3).
- Milestone 8 – SME validation, month 24. Written actionable feedback from SMEs that have tested the framework and tools on at least 15 of their projects or solutions (section 14.3).
- Milestone 9 – FOSS validation, month 24. Well-established open source foundations, Eclipse and Apache, run the tool on iconic projects to initiate a federated database (section 14.4).
- Open source and EU visibility. Part B of the Grant Agreement commits the toolkit to an open source licence. The installation and its documentation carry the funding acknowledgement required of all public deliverables (section 7.4).
4.3. Relationship to WP3 deliverables and to D4.2 #
D4.1 builds on the toolkit deliverables of Work Package 3:
- D3.1 – FedDB Beta (month 6, lead AboutCode) established the first demonstrator of the federated and shared software metadata platform.
- D3.2 – BasicChain (month 12, lead Bitsea) released a beta of all WP3 tools in downloadable and installable form. It was the first point at which the tools could be called one after another.
- D3.3 – Toolkit Demo (month 17, extended to version 1.1 in May 2026, lead Bitsea) demonstrated the toolkit on WP4 use cases. It is the source of the SME, iconic-project and open source testing summarised in section 14.
- D3.4 – Federated Database Final (month 17, lead AboutCode) delivered the final release of the federated platform: VulnerableCode and the wider AboutCode/FederatedCode ecosystem. It extends the platform from vulnerability data to package origin, licence metadata and complete CycloneDX SBOMs, identified by package URL. The public VulnerableCode service is, with OSV, a data source of the ORT Server advisor, and the data source of the Curator’s vulnerability service (section 21.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.3 | How it is addressed | Sections |
|---|---|---|
| Deploy the WP3 tools as a ready-to-use reference solution, in the form of a cloud service using containers | Production instance on OVHcloud Managed Kubernetes; all components containerised and installed with Helm; deployment material in version-controlled repositories | 4.5, 5, 6, 11 |
| Integrate the components into a toolchain that allows end-to-end tests | Shared identity realm; ORT Server analysis are automatically exported to the Curator, or the Curator starts ORT Server runs and imports their results; vulnerability services connected | 6.9, 14.1 |
| Demonstrate that the solution is usable by SMEs | Sign-in, web interfaces access, data isolation per organisation; testing of ORT Server and the Curator by SMEs on their own products | 5.3, 13, 14.3 |
| Show the feasibility of a service at scale and the path to a fully operational SaaS | Analysis campaign on the Eclipse Foundation portfolio; benchmark of 96 open source projects; infrastructure cost model | 14.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.
| Artefact | Location | Content | Licence |
|---|---|---|---|
| Running instance | https://ort-server.occtet.eu, https://curator.occtet.eu, https://keycloack.occtet.eu, https://vulnerablecode.occtet.eu | ORT Server, Occtet Curator, Keycloak, VulnerableCode | – |
| This document | reference-installation-documentation | D4.1 in Markdown, also published as a web page by GitLab Pages (Hugo site in site/) | CC BY 4.0 |
| Installation of the platform | occtet-helm-chart | Makefile 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; Renovate | EPL-2.0 |
| Keycloak deployment | occtet-keycloak | Wrapper chart occtet-keycloak, values, Makefile, CI deployment, Renovate; development realm occtet, imported at startup on a local cluster | EPL-2.0 |
| Keycloak theme | occtet-keycloak-theme | Keycloakify theme sources, Storybook, CI that publishes the theme package | MIT |
| ORT Server deployment | occtet-ort-server-helm-chart | Umbrella chart prod-stack, values, Makefile, CI deployment, Renovate, footer | EPL-2.0 |
| ORT Server configuration | occtet-ort-server-config | ORT Server parameter validation script, evaluator rules, licence classifications, copyright garbage list | Apache-2.0 |
| Occtet Curator deployment | occtet-curator-helm-chart | Umbrella 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, Renovate | EPL-2.0 |
| VulnerableCode deployment | occtet-vulnerablecode-helm-chart | Chart vulnerablecode (web UI and REST API, RQ scheduler and workers, PostgreSQL, Redis), OCCTET values and branding, Makefile, examples | EPL-2.0 |
| Portfolio and access scripts | occtet-eclipse-projects | sync-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; documentation | EPL-2.0 |
| Statistics and dashboards | occtet-stats | statistics.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); documentation | EPL-2.0 |
| Upstream charts | https://github.com/eclipse-apoapsis/helm-charts, https://github.com/Bitsea/Occtet-Curator-Helm-Chart, https://github.com/CloudPirates-io/helm-charts | ORT Server, Occtet Curator and Keycloak charts, and the PostgreSQL, RabbitMQ and Redis charts | See 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 #
| Component | Role | Status for this installation |
|---|---|---|
| ORT Server | Server 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 Curator | Imports, 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 theme | Single sign-on for ORT Server and the Curator (realm occtet) | Deployed on this instance |
| ScanCode | Licence and copyright scanner | Run by the ORT Server scanner worker inside the cluster (section 21.1) |
| VulnerableCode | Vulnerability 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). |
| OSV | Open-source vulnerability database | Consumed 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 URL | Used 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 #
| Service | URL | Purpose |
|---|---|---|
| ORT Server – web UI | https://ort-server.occtet.eu | Manage organisations, products and repositories; start runs; browse results; download reports |
| ORT Server – REST API | https://ort-server.occtet.eu/api/v1 | Programmatic access (reference: https://eclipse-apoapsis.github.io/ort-server/api/ort-server-api) |
| ORT Server – token page | https://ort-server.occtet.eu/profile/token | Long-lived token for the CLI and scripts |
| Occtet Curator | https://curator.occtet.eu | Redirects to the application, served under /boc |
Keycloak – realm occtet | https://keycloack.occtet.eu/realms/occtet | OpenID Connect issuer; discovery document at /.well-known/openid-configuration |
| Keycloak – account console | https://keycloack.occtet.eu/realms/occtet/account | Self-service management of profile, password and linked accounts |
| VulnerableCode – OCCTET instance | https://vulnerablecode.occtet.eu | Web 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:
| Component | Version |
|---|---|
| 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 |
| Keycloak | 26.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) |
| VulnerableCode | 40.1.0, installed with Helm chart vulnerablecode 0.0.1 |
| PostgreSQL | 18.6 (ORT Server and VulnerableCode), 18.0 (Keycloak), 17 with pgvector (Curator) |
| RabbitMQ | 4.3.6 |
| Redis | 8.10.2 (VulnerableCode) |
| NATS JetStream, Ollama | latest images (Annex C); LLM model qwen3:8b |
| Loki, Promtail, Grafana | 2.6.1, 3.5.1, 10.3.3 (chart loki-stack 2.10.3) |
| ingress-nginx | Controller 1.13.2 (chart 4.13.2) |
| cert-manager | 1.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.
- 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.
- 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. - 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:
| Organisation | Content |
|---|---|
| FOSS Validation | One product per iconic project named in the Grant Agreement (section 14.4, Iconic open source projects) |
| Renowned FOSS projects | The benchmark of widely used projects, one product per language ecosystem (section 14.5, General open source ecosystem benchmark) |
| eclipse-projects | The Eclipse Foundation projects (section 14.2, Scenario B – Analysis campaign on the Eclipse Foundation portfolio) |
| eclipse-research-labs | The 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:
| Profile | ORT Server | Occtet Curator | Kubernetes cluster |
|---|---|---|---|
| Onboarded SME or open-source project | Reader on the public organisations (automatic); role (reader, writer or admin) on its own organisation, products or repositories | curator role (automatic); member of its organisation, added by a Curator admin; sees only its organisation’s data | No access |
| Consortium partner working on a given service | Rights matching the service: roles on organisations, or superuser for those maintaining the platform | admin or curator role, as needed | A ServiceAccount restricted to the namespace of the service the partner works on, for example the Curator team on namespace curator |
| Platform operators | Superuser, granted under Administration → User Management → Users | admin | Cluster 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"| extFigure 1 – Reference architecture of the installation.
Four principles shape the architecture:
- One entry point. A single load balancer and ingress controller serve all four host names. TLS is terminated at the ingress, then a dedicated reverse proxy per service, in namespace
default, forwards requests to the service’s namespace. - One namespace per service. Keycloak, ORT Server, the Occtet Curator and VulnerableCode each run in their own namespace. Each has its own database and is managed as its own Helm release, with its own lifecycle.
- Helm everywhere. Every component is installed and upgraded with a Helm chart, as its own release. The entry point has its own chart,
occtet-entrypoint, and the repositoryocctet-helm-chartorchestrates all the releases (section 11). - Shared identity. ORT Server and the Curator trust the realm
occtetthrough its public issuer URL.
6.2. Hosting platform #
| Item | Reference installation |
|---|---|
| Provider | OVHcloud Public Cloud |
| Kubernetes service | OVHcloud Managed Kubernetes Service (MKS), Free plan. OVHcloud operates the control plane (99.5% SLO). |
| Region | SBG5 – Strasbourg, France (European Union) |
| Kubernetes version | 1.33 |
| Node pool | 6 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 system | Ubuntu 22.04 LTS (managed by OVHcloud) |
| Network plugin | Canal (Calico and Flannel), managed by OVHcloud |
| Persistent storage | OpenStack Cinder CSI block storage, classes csi-cinder-classic and csi-cinder-high-speed (cluster default); volumes in section 21.6 |
| Load balancing | OVHcloud Public Cloud Load Balancer (size S), created by the LoadBalancer Service of the ingress controller |
| DDoS protection | Standard OVHcloud anti-DDoS protection, included with Public Cloud |
6.3. Namespace layout #
| Namespace | Content | Helm chart (repository) |
|---|---|---|
ingress-nginx | ingress-nginx controller and its LoadBalancer Service | Upstream ingress-nginx 4.13.2 (kubernetes/ingress-nginx); values in occtet-helm-chart |
cert-manager | cert-manager controller, webhook and CA injector | Upstream jetstack/cert-manager v1.18.2 (cert-manager/cert-manager); values in occtet-helm-chart |
default | Entry point: one Ingress per host name and four nginx reverse proxies. The same chart creates the cluster-wide ClusterIssuers. | occtet-entrypoint (occtet-helm-chart) |
keycloak | Keycloak and its PostgreSQL database; GitLab Agent used by the CI | Wrapper chart occtet-keycloak (occtet-keycloak) |
ort-server | ORT 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-refresh | Umbrella chart prod-stack (occtet-ort-server-helm-chart); chart occtet-stats (occtet-stats) |
curator | Curator frontend, 13 microservices, PostgreSQL with pgvector, NATS JetStream, Ollama, NFS provisioner; GitLab Agent used by the CI | Umbrella chart occtet-curator-stack, with the Bitsea chart occtet-curator as dependency (occtet-curator-helm-chart) |
vulnerablecode | VulnerableCode web with its nginx sidecar, RQ scheduler, RQ workers (six with the values, section 6.8), PostgreSQL, Redis, two NetworkPolicies | vulnerablecode (occtet-vulnerablecode-helm-chart) |
kube-system | Cluster 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 name | Ingress | Reverse proxy | Routing rules | Upstream |
|---|---|---|---|---|
ort-server.occtet.eu | ingress-ort-server | nginx-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.eu | ingress-curator | nginx-curator-proxy | / → redirect to /boc; all paths → frontend; WebSocket upgrade enabled | occtet-curator-frontend.curator |
keycloack.occtet.eu | ingress-keycloack | nginx-keycloack-proxy | Allow-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 errors | occtet-keycloak.keycloak |
vulnerablecode.occtet.eu | ingress-vulnerablecode | nginx-vulnerablecode-proxy | All paths → web service; forwarded headers passed through; WebSocket upgrade enabled | vulnerablecode-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 #
- Chart. Wrapper chart
occtet-keycloak1.0.0, in the repositoryocctet-keycloak. It depends on the CloudPirateskeycloakchart 0.21.46, published as OCI artefactoci://registry-1.docker.io/cloudpiratesand pinned inChart.yaml. It deploys Keycloak 26.8.0, with the image pinned by digest, and a bundled PostgreSQL 18 database. The Keycloak version follows the CloudPirates chart version. - Deployment. GitLab CI deploys the chart on every merge to the default branch, through the GitLab Agent of namespace
keycloak, and Renovate proposes the chart updates. Keycloak migrates its database schema at start-up (section 15.2, Maintenance and upgrades). - Mode. Production mode, with
proxyHeaders: xforwarded. The Service is of type ClusterIP and the chart’s own Ingress is disabled; Keycloak is published through the entry point instead. Its resource requests and limits are in section 21.6. - Credentials. The password of the bootstrap administrator comes from the Secret
keycloak-admin-secret, created before installation and read only at the first start. The chart generates the database password at the first installation, in the Secretocctet-keycloak-postgres(section 21.5). - Theme. The theme is installed at start-up by an initContainer (section 7.1).
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.
| Dependency | Chart version | Application version | Source |
|---|---|---|---|
ort-server | 0.24.3 | ORT Server 0.95.0 | oci://ghcr.io/eclipse-apoapsis/charts – https://github.com/eclipse-apoapsis/helm-charts |
postgres | 0.21.2 | PostgreSQL 18.6 | oci://registry-1.docker.io/cloudpirates |
rabbitmq | 0.21.31 | RabbitMQ 4.3.6 | oci://registry-1.docker.io/cloudpirates |
loki-stack | 2.10.3 | Loki 2.6.1, Promtail 3.5.1, Grafana 10.3.3 | https://grafana.github.io/helm-charts |
Runtime components
- The core serves the REST API and enforces authentication and authorisation.
- The UI is the web front end.
- The orchestrator drives each run through its pipeline.
- The workers are the six pipeline stages: config, analyzer, advisor, scanner (ScanCode), evaluator and reporter. Each one runs as a short-lived Kubernetes Job.
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
- Configuration files. A Git-based configuration provider reads the parameter validation script, policy rules and licence classifications from the repository
occtet-ort-server-config(section 21.1). - User secrets. Secrets users create in ORT Server are stored encrypted in the ORT Server database.
- Worker logs. The log file service fetches worker logs from Loki.
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.
| Dependency | Chart version | Application version | Source |
|---|---|---|---|
occtet-curator | 0.1.0 | Curator 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.4 | PostgreSQL 17 with pgvector (pgvector/pgvector:pg17-trixie) | oci://registry-1.docker.io/cloudpirates |
nfs-server-provisioner, alias nfsprovisioner (dependency of occtet-curator) | 1.8.0 | nfs-provisioner 4.0.8 | https://kubernetes-sigs.github.io/nfs-ganesha-server-and-external-provisioner/ |
| Component | Role |
|---|---|
| Frontend | Jmix and Vaadin web application on port 8090, served under /boc; Spring profiles live,oidc |
ort-run-start-service, process-run-service | Start ORT Server runs and import their results |
spdx-service, cyclonedx-service | Import SPDX and CycloneDX documents |
spdx-export-service, cyclonedx-export-service | Export curated SBOMs |
licensematcher-service, ai-licensematcher-service | Licence identification, rule-based and AI-assisted |
copyrightfilter-service, ai-copyrightfilter-service | Copyright filtering, rule-based and AI-assisted |
vulnerability-service | Vulnerability data from VulnerableCode |
foss-report-service | FOSS compliance reports |
download-service | Downloads project sources into the shared project-data volume |
| NATS JetStream | Message broker between the frontend and the microservices (stream occtet, subjects work.>) |
| PostgreSQL 17 + pgvector | Application database |
| Ollama | Local LLM runtime for the AI-assisted services (model qwen3:8b, CPU only) |
| NFS provisioner | ReadWriteMany 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.
| Component | Kind | Role |
|---|---|---|
| web | Deployment, 1 replica | Django 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). |
| scheduler | Deployment, 1 replica | Schedules the importer and improver pipelines in Redis (rq-scheduler) |
| worker-default | Deployment, 4 replicas | Runs the pipelines of the default queue |
| worker-high | Deployment, 2 replicas | Runs the pipelines of the high queue |
| PostgreSQL | StatefulSet | Vulnerability database |
| Redis | StatefulSet | RQ 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 point | B – Curator as entry point | |
|---|---|---|
| Started by | A user, a script or a CI pipeline, in ORT Server | A Curator user, with ORT Starter |
| Organisation, product and repository in ORT Server | Created 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 |
| Run | Started in ORT Server | Started by the Curator, on the version of the project |
| Import into the Curator | Automatic: the Curator creates the organisation and the project, named after the ORT organisation and product | Automatic, into the project that started the run |
| Typical use | Analysis campaigns (section 14.2, Scenario B – Analysis campaign on the Eclipse Foundation portfolio), projects that already use ORT Server or its API in their CI | A 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 CycloneDXFigure 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 CycloneDXFigure 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 #
- Origin. The theme is a clone of the Eclipse Foundation’s Keycloak theme “Solstice” (https://gitlab.eclipse.org/eclipsefdn/software-dev/frameworks-and-tools/keycloak-solstice-theme), itself built on the Keycloakify starter. It was created in November 2025 as the login theme of ORT Server, then generalised in March 2026 into a single theme for the toolkit.
- Visual identity. The theme carries the OCCTET logo and background, a footer with the funding statement, and the “Co-funded by the European Union” and European Cybersecurity Competence Centre logos.
- Technology. Keycloakify 11 with React 18, PatternFly 5 and i18next. It overrides the login pages (login, OTP, TOTP set-up, WebAuthn, recovery codes, authenticator selection, credential deletion, error and expired-page screens) and provides a single-page account console.
- Repository. The sources are in the repository
occtet-keycloak-theme(GitLab project 11564). Storybook is the development environment; the pipeline also publishes it to GitLab Pages. - Build and distribution. On every merge to the default branch, the GitLab CI pipeline runs
yarn build-keycloak-theme, packages the theme as a JAR and publishes it to the Maven package registry of the theme repository. The coordinates are groupeclipsefdn.open-source-services.occtet-eu.reference-installation, artifactocctet-keycloak-themeand version0.0.1; each publication replaces the previous JAR. - Installation in Keycloak. No custom Keycloak image is needed. An initContainer, using a Maven image pinned by digest, fetches the theme JAR with
mvn dependency:getfrom that public registry. It then extracts it into the sharedkeycloak-themesvolume before Keycloak starts. To put a new theme build live, restart Keycloak:kubectl -n keycloak rollout restart statefulset occtet-keycloak. - Activation. In the realm
occtet, under Realm settings → Themes, the login and account themes are set tokeycloak-occtet-theme, the name of the theme in Keycloak.
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 project name and mission;
- the European Cybersecurity Competence Centre and “Co-funded by the European Union” logos;
- the funding statement;
- links to the OCCTET website, the ORT Server instance, the ORT Server source code and its issue tracker.
The Markdown source is ort-footer.md, in the repository occtet-ort-server-helm-chart. An ORT Server administrator applies it as follows:
- Open Administration → Content Management → Branding.
- Paste the Markdown into the footer form.
- 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:
navbar.htmlputs the OCCTET logo in the header;footer.htmladds the funding statement and the “Co-funded by the European Union” and European Cybersecurity Competence Centre logos, above the upstream VulnerableCode credits, which are kept unchanged.
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):
- the Keycloak login pages (7.1), the ORT Server footer (7.2) and the VulnerableCode footer (7.3) show the funding statement and the “Co-funded by the European Union” and European Cybersecurity Competence Centre logos;
- the README of every deployment repository of section 4.5 ends with a Funding section: the funding statement and the same logos;
- this document starts with the legal notice and the same logos.
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 #
- Public endpoints. The four host names are served over HTTPS only. One load balancer and one ingress controller publish them, and plain HTTP requests are redirected to HTTPS. cert-manager obtains and renews the certificates from Let’s Encrypt (ACME, HTTP-01 challenge).- TLS terminated at the ingress. Traffic inside the cluster is not encrypted. This covers ingress → reverse proxy → service, and services → their databases and message brokers (
sslMode: disablefor ORT Server). Its confidentiality therefore relies on the cluster network. - Security headers. Keycloak sends HTTP Strict Transport Security for one year including sub-domains, a Content Security Policy restricting framing to the same origin,
X-Frame-Options: SAMEORIGIN,X-Content-Type-Options: nosniff,Referrer-Policy: no-referrerandX-Robots-Tag: none. - Path filtering. The Keycloak proxy publishes only the endpoints that clients and administrators need, and blocks Keycloak’s metrics and health endpoints.
- Internal back-ends. Databases, message brokers and the observability back-ends are not published through the ingress; administrators reach them with
kubectl port-forward. Kubernetes NetworkPolicies are applied only in namespacevulnerablecode, where they allow only the VulnerableCode pods to reach its PostgreSQL and Redis. Elsewhere, the cluster network does not restrict traffic between namespaces (section 15.5, Operational readiness matrix). - DDoS protection. The standard OVHcloud anti-DDoS protection covers the Public Cloud resources.
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:
- Git repositories and package registries, which the analyzer and scanner contact to fetch sources and metadata;
- OSV and the public VulnerableCode service, which receive package identifiers (package URLs) for vulnerability lookups;
- GitHub, when a user signs in with a GitHub account.
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).
- Login methods. Users sign in with a local account, whose user name is their e-mail address, or through the GitHub identity provider. E-mail addresses received from GitHub are not trusted automatically.
- Multi-factor authentication. One-time passwords, WebAuthn security keys and recovery codes are available to users, but not enforced.
| Client | Type | Used by | Flow | Purpose |
|---|---|---|---|---|
ort-server | Public | ORT Server web UI; portfolio scripts | Authorization 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-cli | Public | ORT Server token page, osc CLI, scripts | Authorization code with PKCE, offline_access | Offline tokens; an audience mapper adds ort-server |
api-access | Confidential | ORT 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 Curator | See below |
ort-ci | Confidential | Projects’ CI pipelines | User token generation | Tokens 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:
- the signature, against the realm keys;
- the issuer, which must be
https://keycloack.occtet.eu/realms/occtet; - the audience, which must include
ort-server.
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:
admingives full access, including configuration and organisation management.curatorgives access to the curation views.
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 Server | Occtet Curator | |
|---|---|---|
| Stored in | The ORT Server database | Roles: realm roles of the realm occtet, in Keycloak, which the Curator reads from the token at sign-in. Organisation membership: the Curator database |
| Granted by | A superuser, or an admin of the organisation, product or repository; the access job for READER on the public organisations | Roles: a Keycloak administrator, or the access job for curator. Organisation membership: a Curator admin |
| How | ORT 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}/superuser | Keycloak 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 effect | Immediately | Roles: 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:
- grants the role
READERon each public organisation of ORT Server (section 5.3, Access modalities for partners), withPUT /api/v1/organizations/{id}/roles/READER. A user who already has a role on the organisation keeps it: ORT Server holds one role per user and organisation, so assigningREADERwould demote a writer or an admin; - assigns the realm role
curatorin Keycloak, unless the user has the roleadmin: the rolecuratoralso carries the row-level policy of the Curator, which would restrict an administrator to the data of one organisation.
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 #
- Out-of-band Secrets. Credentials are created as Kubernetes Secrets before installation, and the charts reference them with
existingSecret(section 21.5).make vulnerablecode-secrets-createinocctet-helm-chartgenerates every VulnerableCode value withopenssl, andmake secrets-createinocctet-keycloakgenerates random Keycloak passwords. The Keycloak database password is the exception: its chart generates it. - ORT Server user secrets. VCS credentials and API keys are stored in the ORT Server database, encrypted with a master password and a salt held in a Kubernetes Secret. The API returns only their names and descriptions.
- CI/CD variables. The hourly access job (section 8.3.1, Granting permissions) reads its credentials from masked and protected CI/CD variables of the GitLab project
occtet-eclipse-projects: an offline token of an ORT Server superuser and the secret of the clientapi-access. - Curator secrets. The VulnerableCode API token is stored as an encrypted configuration value.
8.5. Workload separation and supply chain #
These controls are applied:
| Control | Applied |
|---|---|
| One namespace, Helm release and database per service | Yes. 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 ServiceAccounts | ORT 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 execution | ORT 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 limits | CPU and memory requests and limits are set for every worker and service (section 21.6). |
| Short-lived analysis pods | Third-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 pinning | Keycloak, 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 updates | Renovate 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 scans | The 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 #
- Kubernetes. The cluster is administered with
kubectl,k9s,headlampand Helm through the Kubernetes API. - Grafana. It is not published on the Internet; administrators open it through
kubectl port-forward. Anonymous access is read-only: without an account, Grafana shows the dashboards but neither Explore nor the editing of dashboards, which need the Grafana admin account. Since a Viewer can still query every datasource through the Grafana API, Loki included, anonymous access must be disabled before Grafana is published. - CI deployment. GitLab CI deploys the ORT Server stack, the Curator and Keycloak through the GitLab Agent for Kubernetes, with one agent per namespace:
gitlab-agent-ort-server,gitlab-agent-curatorandgitlab-agent-keycloak. Each agent opens the connection to GitLab from the cluster, so no cluster credentials are stored in CI variables. Merge request pipelines never deploy. - Manual deployment. Administrators deploy the dashboards of
occtet-statswith its Makefile (section 9.4). They deploy the entry point and VulnerableCode, and any component outside the CI, withocctet-helm-chart(section 11). Its targets that change the cluster refuse to run on another kubectl context than the expected one. - Keycloak. The administration console is reachable through the public Keycloak host name.
- VulnerableCode. No administration console is published. Administrative tasks, such as creating API users, run
manage.pyin the web pod throughmaketargets ofocctet-helm-chartor of the VulnerableCode repository.
9. Monitoring and observability #
9.1. Logs #
- Collection. Promtail runs as a DaemonSet and ships container logs to Loki. Its pipeline strips
query=fields from log lines and extracts acomponentlabel from ORT Server log lines. - Exploration. Grafana is used to explore logs, and holds the statistics dashboards of ORT Server (section 9.4); it is reached through
kubectl port-forward. - Run logs. ORT Server’s log file service queries Loki, so users can download each run’s worker logs from the UI or the API.
9.2. Resource metrics and health checks #
- Usage metrics. metrics-server, managed by OVHcloud, provides
kubectl top nodesandkubectl top pods. - Health checks. Each reverse proxy answers
/alive;make test-entrypointinocctet-helm-chartchecks every proxy. - VulnerableCode pipelines. The dashboard https://vulnerablecode.occtet.eu/pipelines/dashboard/ of the OCCTET instance shows the state of every importer and improver run and the load factor of the workers.
make test-vulnerablecoderuns the chart’s Helm test, which checks that Django, behind the nginx sidecar, and the static files answer.
9.3. Job supervision, housekeeping and retention #
| CronJob | Schedule | Purpose |
|---|---|---|
kubernetes-lost-jobs-finder | Every 2 minutes | Detects worker jobs whose pods disappeared |
kubernetes-long-running-jobs-finder | Every 5 minutes | Detects worker jobs that exceed their per-worker timeout |
kubernetes-reaper | Every 5 minutes | Removes finished worker jobs |
delete-old-ort-runs | Daily, 00:15 | Deletes the runs that finished before the retention period, except the latest run of each repository |
delete-orphaned-entities | Daily, 01:30 | Removes orphaned database entities |
pod-cleanup (this installation) | Every 10 minutes | Deletes succeeded pods in namespace ort-server |
occtet-stats-refresh (this installation) | Every 15 minutes | Precomputes 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:
| Dashboard | Content |
|---|---|
| ORT Server · Overview | Inventory, runs created by status, vulnerabilities, packages, rule violations and issues of the latest runs, per organisation and over time |
| ORT Server · Vulnerabilities | Vulnerabilities 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 · Runs | Runs 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 |
- Data. The dashboards query the PostgreSQL database of ORT Server with the read-only role
occtet_stats, which reads only the tables they need: not the secrets, infrastructure services, environment variables or user names stored by ORT Server. Its statements stop after 60 seconds. The CronJobocctet-stats-refreshcomputes the vulnerabilities of each run once, when its advisor job completes, into the schemaocctet_statsof the same database, with the roleocctet_stats_refresh. Computing them from the results of the advisor at each display took longer than the statement timeout on eclipse-projects. The schema holds tables only, without views or foreign keys on the tables of ORT Server, so the migrations of ORT Server do not depend on it. - Definitions. They follow those of ORT Server and of Annex B. By default, a vulnerability affecting a package counts once in the selected scope, however many repositories use the package. The dashboards can also count vulnerability identifiers only, or each repository separately. The evolution charts count, at each point in time, the vulnerabilities of the latest runs of that time. They grow with every campaign that analyses new repositories; a filter keeps only the repositories already analysed at the start of the time range.
- Freshness and history. A run counts once the CronJob has processed it, within 15 minutes of the end of its advisor job. The history goes back to the oldest runs that ORT Server keeps (section 9.3).
- Deployment. The values of
occtet-ort-server-helm-chartenable the dashboard sidecar of Grafana, and limit its datasource sidecar to the ConfigMaps and Secrets labelledgrafana_datasource=1. Without that label, the sidecar copied every ConfigMap and Secret of the namespace, the secrets of ORT Server included, into the provisioning directory of Grafana. The administrators deploy the dashboards by hand with the Makefile ofocctet-stats:make db-rolescreates the roles and the schema,make datasourceapplies the datasource Secret,make deployinstalls the Helm releaseocctet-stats, andmake refreshruns the first precomputation.make testruns every query of the dashboards on Docker, against the database schema of an ORT Server release and synthetic data: run it before upgrading ORT Server.
9.5. Current limits #
- No alerting. There is no metrics stack with alerting, and no external availability probe.
- Ephemeral logs. Loki keeps logs on ephemeral storage.
- Promtail end of life. Promtail reached end of life on 2 March 2026 and receives no further updates. It must be replaced by a maintained collector such as Grafana Alloy (section 15.5, Operational readiness matrix).
10. Prerequisites #
| Area | Requirement |
|---|---|
| Cloud | An 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). |
| Storage | Cinder 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). |
| Network | The 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. |
| DNS | Four host names pointing to the load balancer’s public address |
| Tools | git, 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. |
| Access | Read 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 keys | A 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.
| Step | Repository | Files used |
|---|---|---|
| 11.4, 11.5 Ingress controller, certificates | occtet-helm-chart | Makefile, values/ingress-nginx.yaml, values/cert-manager.yaml |
| 11.6 Keycloak | occtet-keycloak | Chart.yaml, values.yaml, Makefile |
| 11.7 ORT Server | occtet-ort-server-helm-chart, submodule of occtet-helm-chart | Chart.yaml, values.yaml, templates/, ort-footer.md |
| 11.8 Occtet Curator | occtet-curator-helm-chart, submodule of occtet-helm-chart | Chart.yaml (Bitsea chart occtet-curator 0.1.0), values.yaml, Makefile (secrets-create) |
| 11.9 VulnerableCode | occtet-vulnerablecode-helm-chart, submodule of occtet-helm-chart | vulnerablecode/ (chart and Chart.lock), values.yaml, branding/ |
| 11.10 Entry point | occtet-helm-chart | charts/occtet-entrypoint/, values/occtet-entrypoint.yaml |
| 13.4 Portfolio campaigns | occtet-eclipse-projects | sync-*.sh, bulk-run*.sh, env-example.sh |
| 9.4 Statistics and dashboards | occtet-stats | Makefile, 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 #
- Create the cluster (section 11.3).
- Install the ingress controller and create the DNS records (11.4).
- Install cert-manager (11.5).
- Install Keycloak and configure the realm (11.6).
- Install ORT Server (11.7).
- Install the Curator (11.8).
- Install VulnerableCode (11.9).
- 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 #
- In the OVHcloud Control Panel, create a Managed Kubernetes cluster and a node pool sized as in section 10.
- 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:
| Tab | Settings |
|---|---|
| General | Display name OCCTET |
| Login | User registration, Email as username, Login with email, Forgot password and Remember me on; Verify email, Duplicate emails and Edit username off |
| An SMTP server, which Forgot password and e-mail verification need. None is configured on the OCCTET instance. | |
| Themes | Login theme and Account theme keycloak-occtet-theme; Admin theme keycloak.v2; Email theme keycloak |
| Sessions | SSO 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 |
| Tokens | Access Token Lifespan 1 hour |
| Security defenses | Brute 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:
- Create
ort-server-api, with type None, protocol OpenID Connect and Include in token scope off. In its Mappers tab, choose Configure a new mapper → Audience: nameort-server-api, Included Client Audienceort-server, Add to access token on, Add to ID token off. The access tokens of the clients that use this scope then carry the audience that the ORT Server API requires (section 8.2). - Create
ort-server-api-access, with type None and protocol OpenID Connect, without mapper. The Curator requests this scope when it signs users in, so it must be available to the Curator’s client. - Open the built-in scope
roles, mapperrealm roles, and set Add to ID token on. The Curator reads the rolesadminandcuratorfrom the ID token.
4. Clients. In Clients → Create client, with client type OpenID Connect:
| Setting | ort-server | ort-server-cli | api-access |
|---|---|---|---|
| Used by | ORT Server web UI | ORT 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 authentication | Off (public client) | Off (public client) | On (confidential client) |
| Standard flow | On | On | On |
| Direct access grants | Off; on only for the portfolio scripts of section 13.4, which use the password grant | Off | On: the Curator obtains ORT Server tokens for its technical account with the password grant |
| Service accounts roles | Off | Off | On |
| Valid redirect URIs | https://ort-server.occtet.eu/* | https://ort-server.occtet.eu/profile/token/callback | https://curator.occtet.eu/* |
| Valid post logout redirect URIs | https://ort-server.occtet.eu/* | – | – |
| Web origins | https://ort-server.occtet.eu | https://ort-server.occtet.eu | – |
| Client scopes tab | Add ort-server-api as Default | Add ort-server-api as Default; keep offline_access as Optional, since the token page requests it | Add 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:
- in Service accounts roles → Assign role, filter by clients and assign
manage-realm,manage-usersandmanage-clientsofrealm-management; - in Credentials, copy the client secret (step 7).
5. Roles and groups.
- In Realm roles → Create role, create
adminandcurator, the application roles of the Curator (section 8.3). - In Groups → Create group, create
SUPERUSERS. When ORT Server starts with no role assignment in its database, that is at its first start, it makes the members of this group superusers. Add the first platform administrators to it now, before ORT Server is installed. Afterwards, superusers are managed in ORT Server, under Administration → User Management → Users (section 8.3.1).
6. Accounts.
- Technical account of the Curator. In Users → Add user, create a user with an e-mail address as user name and Email verified on, then set its password in Credentials → Set password, with Temporary off. Add it to the group
SUPERUSERS, because the Curator creates organisations in ORT Server and only superusers may do so; if ORT Server already runs, make it a superuser under Administration → User Management → Users instead. Its user name and password go into the Curator secret (ort-username,ort-password, section 11.8). - Users. People create their account themselves, with their e-mail address or with GitHub (section 5.3). Administrators then grant their permissions, separately in each tool (section 8.3.1).
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:
- the README of
occtet-keycloak; - the ORT Server administration guide on Keycloak, https://eclipse-apoapsis.github.io/ort-server/docs/admin-guide/guides/keycloak-config; https://github.com/CloudPirates-io/helm-charts/tree/main/charts/keycloak; https://www.keycloak.org/docs/latest/server_admin/; https://www.keycloak.org/server/reverseproxy; https://docs.keycloakify.dev/.
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:
- Apply the footer (section 7.2).
- Check under Administration → User Management → Users that the members of the group
SUPERUSERSare superusers, and grant the superuser role to the other platform maintainers there. - Create organisations/products/repositories
- Create the
vulnerableCodeApiKeysecret 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 instance | Local installation (ENV=dev) | |
|---|---|---|
| Cluster and kubectl context | OVHcloud Managed Kubernetes, OCCTET | minikube 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 |
| Certificates | Let’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 controller | LoadBalancer Service | Ports 80 and 443 of the minikube node (hostPort) |
| Chart repositories | Submodules at a pinned commit | Local clones next to occtet-helm-chart, linked under .dev/repos/: a change is tested before it is committed |
| Values | values.yaml of each repository | The 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 realm | Configured 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 |
| Secrets | Created 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):
- Initial contact, typically through OCCTET’s outreach, personal networks or partner referrals, or an access request by e-mail (section 5.3).
- 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.
- 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:
- First sign-in. The user signs in once with a local account or with GitHub (section 5.3).
- Permissions. Within the hour after the first sign-in, the access job grants read access to the public organisations of ORT Server and the
curatorrole (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.
- ORT Server workspace.
- Create the organisation.
- Create the
vulnerableCodeApiKeysecret, 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.
- Curator workspace. The user belongs to the right organisation, and the VulnerableCode key is configured once per Curator instance.
- Automation (optional). Generate a token for the CLI, scripts or CI (section 5.4).
13.2. Using ORT Server #
- Model the portfolio. Create organisations, products and repositories.
- 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
vulnerableCodeApiKeywhose 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. - Start a run. Choose a revision and configure the jobs; section 21.1 gives a reference configuration.
- Follow the run. Track the status of each job and download its logs.
- Review the results. Browse packages, vulnerabilities, rule violations and issues, then download the SBOMs (CycloneDX, SPDX) and the web report.
Without the
vulnerableCodeApiKeysecret, the advisor job fails in every run that selects the VulnerableCode advisor with the public service, that is, withoutserverUrlor withhttps://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:
- Create a project (Projects).
- Import an SBOM: Import → SPDX Import, or start an ORT analysis with ORT Starter.
- Open SBOM Management (SBOMmgmt) and select the project: the file tree is on the left, the inventory items on the right.
- Review the vulnerabilities, licences and copyrights on the Overview page.
- 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.
- Assess vulnerabilities: create and download VEX reports from the inventory item.
- 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:
| Script | Function |
|---|---|
sync-eclipse-projects.sh | Creates 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.sh | Maps 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.sh | Starts 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.sh | Stops every active run within a chosen scope |
grant-users-access.sh | Gives 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.
| Item | Value |
|---|---|
| Repository | https://github.com/xNuminousx/ElementalCompanions (public, Git) |
| Revision | Branch master, resolved to commit 7a7bdcaf4df37e205239b073972c288ef0b4ee5e |
| Curator project | ID 201, elementalCompanions, version master, created on 10 September 2026 |
| ORT Server organisation | A test organisation created automatically by the Curator on its first run |
| ORT runs | IDs 7626 to 7636 (indexes 1 to 11), started from the Curator between 18 and 21 September 2026 |
| Run duration | About 2 minutes each |
| Step | Expected result | Result obtained | Status |
|---|---|---|---|
| 1. Sign in to the Curator through Keycloak | Session with Curator roles | Done | Validated |
| 2. Start an ORT run from ORT Starter | Token obtained; organisation, product and repository created in ORT Server; run started | Organisation, product and repository created; 11 runs started | Validated |
| 3. ORT Server pipeline | All jobs executed and reports produced | All 11 runs ended finished with issues | Validated |
| 4. Import into the Curator | Components and findings imported into the project | 7 inventory items imported into project 201 | Validated |
| 5. Curation decision | At least one inventory item curated, with an audit note | No curated inventory item on the instance | Not validated |
| 6. VEX statement | VEX recorded for a vulnerability | No VEX statement on the instance | Not validated |
| 7. Export of the curated SBOM | SPDX or CycloneDX export of the curated project | Not performed | Not 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:
| Indicator | Value |
|---|---|
| Organisations / users / projects | 7 / 8 / 84 |
| SPDX documents imported | 204 |
| Software components / inventory items | 3,275 / 4,820 |
| Inventory items marked as curated | 0 |
| ORT issues / rule violations imported | 429 / 17,876 (none marked as resolved) |
| VEX statements | 0 |
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.
sync-eclipse-projects.shandsync-eclipse-research-projects.shregistered the Eclipse projects and the Eclipse Research Labs.bulk-run.shstarted one run per repository, with OSV and VulnerableCode advisors, scanning, and CycloneDX, SPDX and web-app reports.
Inventory, started and completed analyses:
| Indicator | Eclipse projects | Eclipse Research Labs | Total |
|---|---|---|---|
| Products registered | 441 | 16 | 457 |
| Repositories registered (inventory) | 3,644 | 481 | 4,125 |
| Repositories with at least one run started | 3,213 | 436 | 3,649 |
| Repositories with at least one completed run | 2,621 | 359 | 2,980 |
| Runs started | 4,212 | 440 | 4,652 |
| Runs completed / failed / in progress | 2,912 / 785 / 515 | 361 / 79 / 0 | 3,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 time | Waiting time | Total | |
|---|---|---|---|
| Eclipse projects | 2 min (19 min) | 2.5 h (11.6 h) | 2.7 h |
| Eclipse Research Labs | 2 min (26 min) | 60.9 h (68.3 h) | 60.9 h |
| Other organisations | 3 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.
- Parallelism. At most 31 analysis jobs ran in parallel (hourly sampling); 4.2 ran on average when the instance was busy.
- Effect on other users. The 71 completed runs of other organisations that started during a campaign had a median total duration of 2.4 minutes (90th percentile: 103 minutes). The 176 started outside campaigns had a median of 28 minutes (90th percentile: 18.7 hours).
- Interpretation. No degradation of interactive runs attributable to the campaigns was observed. The sample is small, though, and the long tail outside campaigns has not been analysed.
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.
| SME | Products tested | Notes |
|---|---|---|
| A-SIT Plus GmbH | 2 | Positive feedback: ORT Server ran better out of the box than a commercial alternative |
| Autoliitto Palvelut Oy | 3 | The only SME with sustained, partly self-driven use, through its own CI pipeline |
| Basemark Oy | 1 | Analysed cleanly; a second product is currently not supported by the tooling |
| DH Electronics GmbH | 1 | A Yocto analysis was developed during the project and run for this SME (section 14.7, Limits observed) |
| Datasance | 6 | The only SME tested on the OCCTET reference instance rather than on Double Open’s hosted service |
| Mediamaisteri Oy | 1 | Testing explicitly concluded |
| Obeo S.A.S. | 2 | – |
| ReductSoftware UG | 3 | Stable and clean results |
| Total | 19 |
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.
| Case | ORT / technical tooling feedback | CRA questionnaires |
|---|---|---|
| SME A | The 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 B | Initial 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 C | The 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:
| Project | Repositories | Packages |
|---|---|---|
| Adoptium | 79 | 10,853 |
| Eclipse IDE (Platform, JDT, PDE and Equinox projects) | 38 | 2,179 |
| Eclipse Mosquitto | 4 | Not reported |
| Jetty | 24 | 2,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.
| Indicator | Value |
|---|---|
| Projects analysed | 96 |
| Failed runs | 2, both with usable partial results |
| Packages identified | 10,043 |
| Vulnerabilities, by severity | 120 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
| Indicator | Value |
|---|---|
| Organisations / products / repositories registered | 18 / 561 / 4,417 |
| ORT runs | 4,978 |
| Completed: finished, or finished with issues | 792 + 2,728 = 3,520, i.e. 78.9% of the 4,463 runs that had ended |
| Failed | 943 |
| In progress at extraction | 515 |
| ORT Server releases used | 15, 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
| Indicator | Value |
|---|---|
| Distinct vulnerability identifiers | 7,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 / both | 3,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 version | 84,663 |
| Distinct components: type, namespace and name, versions ignored | 29,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 #
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).
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).
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).
Per-organisation configuration. The VulnerableCode key must be defined in each organisation.
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).
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.
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.
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.
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) | Status | Evidence | Open items (section 15.5, Operational readiness matrix) |
|---|---|---|---|
| T4.3: a ready-to-use reference solution, deployed as a cloud service with containers | Achieved 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 use | 4.5, 5, 6, 10 to 12 | Backup and restore; Continuity; Vulnerability data |
| T4.3: a toolchain that allows end-to-end tests | Validated up to the import of results; curation, VEX and the export of a curated SBOM are validated on the instance | 6.9, 14.1 | Validation |
| T4.3: usable by SMEs | Demonstrated for ORT Server, on Double Open’s hosted instance for early adopters; and more recently the curator | 5.3, 13, 14.3 | Onboarding; Validation |
| T4.3: feasibility at scale and path to a fully operational SaaS | Feasibility shown on the Eclipse Foundation portfolio and on 96 projects in 10 language ecosystems. | 14.2, 14.5, 14.7, 15 | Backup and restore; Identity; Network; Observability; Capacity |
| Milestone 8 – SME validation | Product 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 2026 | 14.3 | Validation |
| Milestone 9 – FOSS validation | Run data confirmed for Adoptium, Eclipse IDE, Eclipse Mosquitto and Jetty; the runs of Tomcat, Kafka and Hadoop remain to be completed or located | 14.4 | Validation |
| Open source and EU visibility | Achieved: every component is open source, the deployment material is public, and the installation and its documentation carry the funding acknowledgement | 4.5, 7.4, 15.1 | – |
15. Operations, continuity and costs #
15.1. Hosting and continuity #
| Item | Status |
|---|---|
| Hosting until the end of the project (31 October 2026) | OVHcloud Public Cloud, region SBG5, operated by the Eclipse Foundation |
| Hosting after 31 October 2026 | Confirmed: the platform remains available, hosted by the Eclipse Foundation, until early 2027 |
| Administration and support after 31 October 2026 | To be specified |
| Kubernetes control plane | Operated by OVHcloud (MKS Free plan, 99.5% SLO) |
| Environments | Production 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):
| Component | Project and repositories | Licence |
|---|---|---|
| ORT Server | Eclipse 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-charts | Apache-2.0 |
| OSS Review Toolkit | https://github.com/oss-review-toolkit/ort | Apache-2.0 |
| Occtet Curator | https://github.com/Bitsea/Occtet-Curator, Helm chart https://github.com/Bitsea/Occtet-Curator-Helm-Chart | Apache-2.0; Helm chart EPL-2.0 |
| VulnerableCode, FederatedCode | https://github.com/aboutcode-org/vulnerablecode, https://github.com/aboutcode-org/federatedcode | Apache-2.0 |
| OCCTET deployment material | Public 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 #
| Component | How it is upgraded |
|---|---|
| ORT Server stack | GitLab 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 Curator | GitLab 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. |
| Keycloak | GitLab 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). |
| VulnerableCode | make 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 repositories | Test 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 point | make 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):
- Instances use monthly billing.
- Other resources are billed by the hour; a year counts 8,760 hours.
- Block storage is taken from the metered consumption of September 2026. The VulnerableCode volumes, created on 6 October 2026, are added at their provisioned size.
| Item | Quantity | Unit price | Per month | Per year |
|---|---|---|---|---|
| Instance b2-15, monthly billing | 4 | €48.05 / month | €192.20 | €2,306.40 |
| Instance b2-30, monthly billing | 2 | €97.24 / month | €194.48 | €2,333.76 |
| Managed Kubernetes control plane (Free plan) | 1 | included | €0.00 | €0.00 |
| Load Balancer S | 1 | €0.0083 / hour | €6.06 | €72.71 |
| Gateway S | 1 | €0.0028 / hour | €2.04 | €24.53 |
| Floating IP | 1 | €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 volumes | 102 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.
| Basis | Classic | High Speed | Total |
|---|---|---|---|
| Volumes in use by workloads before VulnerableCode (section 21.6) | 209 GiB | 228 GiB | 437 GiB |
| All volumes known to Kubernetes, including 120 GiB of volumes no longer attached to a workload | 319 GiB | 238 GiB | 557 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:
- staff time for administration, upgrades and incident handling;
- user support and onboarding;
- backup storage, since backups are not yet in place;
- any paid external service, such as commercial support or a higher-tier VulnerableCode access.
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.
| Area | Control in place | Limit | Action | Owner | Target date |
|---|---|---|---|---|---|
| Backup and restore | Manual dump and restore procedures documented for the Keycloak and VulnerableCode databases, in the READMEs of their repositories | No scheduled backup of the four databases, the Curator project files or the ORT Server encryption keys; no restore tested | Implement scheduled database backups and volume snapshots, back up the encryption Secrets, document and test a restore for every component | To be agreed | To be agreed |
| Continuity | Platform 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 specified | Formalise responsibilities and the support channel; decide what follows early 2027 | To be agreed | Before 31 October 2026 |
| Identity | SSO, MFA available, security headers | api-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 strengthen | Apply 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 events | To be agreed | To be agreed |
| Network | HTTPS for public endpoints, path filtering; NetworkPolicies for the VulnerableCode PostgreSQL and Redis | No NetworkPolicies for the other services; unencrypted internal traffic; direct service exposure outside the ingress | NetworkPolicies per namespace; remove non-ingress exposure; restrict administrative interfaces | To be agreed | To be agreed |
| Ingress controller | ingress-nginx 1.13.2 | Upstream project retired on 24 March 2026 | Migrate to a maintained controller or a Gateway API implementation | To be agreed | To be agreed |
| Reverse proxies | Path filtering, /alive | ORT Server, Curator and VulnerableCode proxies forward X-Forwarded-Proto: http; VulnerableCode API links use http:// | Apply the configuration of section 21.4 | To be agreed | To be agreed |
| Observability | Loki, Grafana with the statistics dashboards of ORT Server, metrics-server, ORT job supervision | No alerting; ephemeral logs; Promtail end of life since 2 March 2026 | Metrics and alerting, availability probes, persistent logs, migration to Grafana Alloy | To be agreed | To be agreed |
| Data retention | Runs 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 project | To be agreed | To be agreed |
| Secrets | Kubernetes Secrets referenced by existingSecret; masked and protected CI/CD variables for the access job | No 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 rights | External 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 mappings | To be agreed | To be agreed |
| Releases | Renovate 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 environment | Versioned images, staging environment, post-deployment smoke test (scenario A) | To be agreed | To be agreed |
| Vulnerability data | OSV and VulnerableCode advisors; public VulnerableCode service and OCCTET instance, selected per run in ORT Server | Errors 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 statistics | Fix redirects and retries; monitor advisor errors; make the VulnerableCode address of the Curator configurable | To be agreed | To be agreed |
| Policy rules | Example rules of the ORT project | Not a reviewed OCCTET policy; placeholders of the example | Define and review an OCCTET rule set with the partners | To be agreed | To be agreed |
| Ecosystem coverage | ORT analyzer for the main language ecosystems (section 14.5, General open source ecosystem benchmark); Yocto analysis developed for one SME | vcpkg and some older embedded C++ toolchains not supported | Technology-specific support, for example for vcpkg, following the Yocto approach | To be agreed | To be agreed |
| Onboarding | Access 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 portfolios | Partly 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 organisation | Self-service onboarding with fewer manual steps | To be agreed | To be agreed |
| Validation | Integration 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 projects | Curation, 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 projects | To be agreed | To be agreed |
| Capacity | 6 nodes, 31 concurrent jobs observed | Queuing for large batches | Node pool autoscaling, dedicated worker pool, fair scheduling | To be agreed | To 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 #
18. Annex A: Glossary #
| Term | Meaning |
|---|---|
| ACME | Automatic Certificate Management Environment, the protocol used by Let’s Encrypt |
| AI | Artificial intelligence |
| API | Application programming interface |
| CRA | Cyber Resilience Act, Regulation (EU) 2024/2847 |
| CVE, GHSA | Vulnerability identifiers (Common Vulnerabilities and Exposures; GitHub Security Advisory) |
| CycloneDX, SPDX | Standard SBOM formats |
| DEM | Deliverable type: demonstrator, pilot, prototype |
| DO, ECL | Double Open; Eclipse Foundation Europe |
| DoA | Description of Action, Annex 1 of the Grant Agreement |
| ECCC | European Cybersecurity Industrial, Technology and Research Competence Centre |
| EU | European Union |
| FOSS | Free and open source software |
| Helm | Package manager for Kubernetes |
| LLM | Large language model |
| MFA | Multi-factor authentication |
| MKS | OVHcloud Managed Kubernetes Service |
| OIDC | OpenID Connect |
| ORT | OSS Review Toolkit |
| PKCE | Proof Key for Code Exchange |
| PURL | Package URL |
| RBAC | Role-based access control |
| RQ | Redis Queue, the job queue used by the VulnerableCode scheduler and workers |
| SaaS | Software as a service |
| SAST | Static application security testing |
| SBOM | Software Bill of Materials |
| SME | Small and medium-sized enterprise |
| TLS | Transport Layer Security |
| VEX | Vulnerability Exploitability eXchange |
| WP | Work 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:
- ORT Server REST API: every run with the summary of its jobs (
GET /api/v1/runs, which needs a superuser token); the products of each organisation and their repositories; the statistics of the latest runs, per organisation (GET /api/v1/organizations/{id}/statistics/runs) and per product (GET /api/v1/products/{id}/statistics/runs); optionally, the vulnerabilities of the latest runs, to count distinct identifiers. - Occtet Curator REST API: the counts of organisations, users, projects, SPDX documents, software components, inventory items (curated or not), ORT issues and rule violations (resolved or not) and VEX statements. The Curator deployment accepts Keycloak bearer tokens on this API only with the Java option
-Djmix.resource-server.authenticated-url-patterns=/rest/**, and the token must carry the realm roleadmin. - Keycloak admin API: the user accounts of the realm
occtet, service accounts excluded, with their GitHub link and password credential.
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.
- Completed run: status
FINISHEDorFINISHED_WITH_ISSUES. - Ended run: completed or
FAILED. - Repository analysed: at least one run created for it.
- Execution time: the sum of the durations (end minus start) of a run’s jobs.
- Waiting time: the run’s total duration, from its creation to its end, minus its execution time.
- Findings: packages, vulnerabilities, rule violations and issues are counted as ORT Server counts them in its statistics: in the latest run of each repository whose analyzer, advisor or evaluator job completed. Repeated runs of a repository are not counted again. A vulnerability is counted once per vulnerable package.
- Large organisations: the statistics of an organisation that take longer than the 50 seconds of the load balancer, such as eclipse-projects, are summed from those of its products; a package or a vulnerability shared by two products is then counted twice.
- Distinct vulnerability identifiers: counted as reported, without merging aliases: the same vulnerability can appear under a CVE and a GHSA identifier.
Detailed results (section 14.6, Consolidated results of the instance).
Jobs that reported issues in the 2,728 runs finished with issues:
| Job | Jobs finished with issues | Main nature of the issues |
|---|---|---|
| Evaluator | 2,685 | Policy rule violations reported |
| Analyzer | 906 | Dependency resolution problems: NuGet (≈7,500 issues), unresolved package provenance (≈3,400), Maven and Gradle artefacts not resolved (≈2,500), projects without dependency information (≈570) |
| Reporter | 621 | Issues during report generation |
| Scanner | 559 | Provenance resolution, including Git fetches for nested repositories (≈1,200) |
| Advisor | 371 | Errors from the VulnerableCode service (section 14.6) |
Vulnerability and package records:
| Indicator | Value | Definition |
|---|---|---|
| Distinct vulnerability identifiers, by prefix | GHSA 3,514; CVE 2,904; PYSEC 345; GO 157; RUSTSEC 141; others | 7,184 distinct external_id values in total, without alias merging |
| Vulnerability records | 16,022 | Records stored by ORT Server; one identifier can have several records |
| Advisor results | 120,587 (VulnerableCode 94,724; OSV 25,863) | One result per package and advisor in each run, so repeated runs count again |
| Package records | ≈94,100 | Records in ORT Server’s package table; one identifier can have several records |
Package records by ecosystem:
| Ecosystem | Records | Ecosystem | Records |
|---|---|---|---|
| Maven | 40,988 | Go | 2,005 |
| npm | 34,788 | Bazel | 729 |
| PyPI | 6,280 | Composer | 656 |
| Crates (Rust) | 5,110 | RubyGems | 619 |
| NuGet | 2,643 | CocoaPods, Pub, Swift, Carthage | 310 |
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
| Image | Version |
|---|---|
ghcr.io/eclipse-apoapsis/ort-server-core | 0.95.0 |
ghcr.io/eclipse-apoapsis/ort-server-ui | 0.95.0 |
ghcr.io/eclipse-apoapsis/ort-server-orchestrator | 0.95.0 |
ghcr.io/eclipse-apoapsis/ort-server-config-worker | 0.95.0 |
ghcr.io/eclipse-apoapsis/ort-server-analyzer-worker | 0.95.0 |
ghcr.io/eclipse-apoapsis/ort-server-advisor-worker | 0.95.0 |
ghcr.io/eclipse-apoapsis/ort-server-scanner-worker | 0.95.0 |
ghcr.io/eclipse-apoapsis/ort-server-evaluator-worker | 0.95.0 |
ghcr.io/eclipse-apoapsis/ort-server-reporter-worker | 0.95.0 |
ghcr.io/eclipse-apoapsis/ort-server-maintenance-tasks (housekeeping CronJobs) | 0.95.0 |
docker.io/library/postgres | 18.6 |
docker.io/library/rabbitmq | 4.3.6-management |
docker.io/library/busybox (RabbitMQ initContainer) | 1.37.0 |
docker.io/grafana/loki | 2.6.1 |
docker.io/grafana/promtail | 3.5.1 |
docker.io/grafana/grafana | 10.3.3 |
quay.io/kiwigrid/k8s-sidecar (Grafana sidecar) | 1.19.2 |
docker.io/alpine/k8s (CronJob pod-cleanup) | 1.36.1 |
Keycloak
| Image | Version |
|---|---|
docker.io/keycloak/keycloak | 26.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
| Image | Version |
|---|---|
registry.k8s.io/ingress-nginx/controller | v1.13.2 |
registry.k8s.io/ingress-nginx/kube-webhook-certgen (admission webhook jobs) | v1.6.2 |
quay.io/jetstack/cert-manager-controller | v1.18.2 |
quay.io/jetstack/cert-manager-webhook | v1.18.2 |
quay.io/jetstack/cert-manager-cainjector | v1.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
| Image | Version |
|---|---|
ghcr.io/bitsea/occtet-frontend | latest |
ghcr.io/bitsea/occtet-nats-ort-run-start-service | latest |
ghcr.io/bitsea/occtet-nats-process-run-service | latest |
ghcr.io/bitsea/occtet-nats-vulnerability-service | latest |
ghcr.io/bitsea/occtet-nats-spdx-service | latest |
ghcr.io/bitsea/occtet-nats-spdx-export-service | latest |
ghcr.io/bitsea/occtet-nats-cyclonedx-service | latest |
ghcr.io/bitsea/occtet-nats-cyclonedx-export-service | latest |
ghcr.io/bitsea/occtet-nats-licensematcher-service | latest |
ghcr.io/bitsea/occtet-nats-ai-licensematcher-service | latest |
ghcr.io/bitsea/occtet-nats-copyrightfilter-service | latest |
ghcr.io/bitsea/occtet-nats-ai-copyrightfilter-service | latest |
ghcr.io/bitsea/occtet-nats-foss-report-service | latest |
ghcr.io/bitsea/occtet-nats-download-service | latest |
docker.io/library/nats | latest |
docker.io/ollama/ollama | latest |
docker.io/pgvector/pgvector (PostgreSQL with pgvector) | pg17-trixie |
registry.k8s.io/sig-storage/nfs-provisioner | v4.0.8 |
docker.io/library/busybox (initContainers) | 1.36 |
VulnerableCode
| Image | Version |
|---|---|
ghcr.io/aboutcode-org/vulnerablecode | 40.1.0 |
docker.io/nginxinc/nginx-unprivileged | 1.30-alpine |
docker.io/library/postgres | 18.6 |
docker.io/library/redis | 8.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):
| Key | Value |
|---|---|
ui.authority, ui.clientId | https://keycloack.occtet.eu/realms/occtet, ort-server |
core.keycloak.jwtUri | https://keycloack.occtet.eu/realms/occtet/protocol/openid-connect/certs |
core.keycloak.jwtIssuer, jwtRealm, jwtAudience | https://keycloack.occtet.eu/realms/occtet, occtet, ort-server |
core.keycloak.subjectClientId | ort-server |
core.keycloak.clientId, accessTokenUrl, apiUrl | api-access, …/protocol/openid-connect/token, https://keycloack.occtet.eu/admin/realms/occtet |
core.keycloak.existingSecret | ort-server-keycloak-credentials |
core.cli.keycloakBaseUrl, keycloakRealm, keycloakClientId | https://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.
| File | Read by | Role |
|---|---|---|
ort-server.params.kts | Config worker, once per run | Completes 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.kts | Evaluator | Policy 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.yml | Evaluator, reporter | About 2,750 licences in 15 categories, generated from the ScanCode LicenseDB |
copyright-garbage.yml | Evaluator, reporter | Copyright statements to ignore, mostly scanner noise |
reporter.how-to-fix-text-provider.kts | Reporter | How-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):
| Variable | Value |
|---|---|
ortBaseUrl | https://ort-server.occtet.eu |
ortTokenUrl | https://keycloack.occtet.eu/realms/occtet/protocol/openid-connect/token |
ortClientId | api-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 |
keycloakIssuerUri | https://keycloack.occtet.eu/realms/occtet |
frontendBaseUrl | https://curator.occtet.eu/boc (login redirect URI: {frontendBaseUrl}/login/oauth2/code/keycloak) |
springProfilesActive | live,oidc |
maxFileSize, maxRequestSize | 200MB: 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:
| Key | Purpose |
|---|---|
general.base_path | System path where project sources are stored |
general.vulnerability_db_token | VulnerableCode API token, stored encrypted |
ort.ort_license_match, ort.ort_copyright_filter | Run the licence matcher or the copyright filter after an ORT run |
21.3. VulnerableCode #
Two VulnerableCode instances are available:
| Instance | URL | Used by | API key |
|---|---|---|---|
| Public service | https://public.vulnerablecode.io | ORT Server advisor; Curator vulnerability service | Required |
| OCCTET instance on this cluster (section 6.8) | https://vulnerablecode.occtet.eu | ORT Server advisor, when the run sets its serverUrl | Not 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
- URL. The run configuration enables the advisor
VulnerableCodewithserverUrl: https://public.vulnerablecode.io/api/. - 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). - Injection. When the public instance is used,
ort-server.params.ktsinjects 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):
| Key | Value |
|---|---|
image.tag | Empty, so the chart’s appVersion applies: 40.1.0 |
config.allowedHosts, config.csrfTrustedOrigins | vulnerablecode.occtet.eu, https://vulnerablecode.occtet.eu |
config.requireAuthentication | false: the REST API needs no API key |
web.ingress.enabled | false: the instance is published through the entry point (section 6.4) |
workers.queues.default.replicaCount, workers.queues.high.replicaCount | 4 and 2, the upstream recommendation; the import resumed with one worker per queue (section 6.8) |
postgresql.persistence.size | 100 GiB, on the cluster’s default storage class |
existingSecret | vulnerablecode-secrets, the chart default (section 21.5) |
Use by the toolchain:
- ORT Server. A run uses the OCCTET instance when its advisor configuration sets
serverUrltohttps://vulnerablecode.occtet.eu/api/, or to the in-cluster addresshttp://vulnerablecode-web.vulnerablecode.svc.cluster.local/api/.ort-server.params.ktsinjects the API key only for the public URL, and the OCCTET instance requires none, so novulnerableCodeApiKeysecret is needed. - Occtet Curator. The address
https://public.vulnerablecode.iois fixed in the code of the vulnerability service (RestClientConfig). Using the OCCTET instance therefore requires a change in the Curator.
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 #
| Namespace | Secret | Keys |
|---|---|---|
keycloak | keycloak-admin-secret | admin-password: password of the admin account, read at the first start only |
keycloak | occtet-keycloak-postgres | postgres-password and the connection details. Generated by the chart at the first installation; Keycloak uses it to connect to its database |
keycloak | keycloak-postgresql-secret | db-username, db-password. Created by make secrets-create, but not used by the current values |
ort-server | ort-server-db-credentials | database-password |
ort-server | ort-server-rabbitmq-credentials | rabbitmq-user, rabbitmq-password |
ort-server | ort-server-keycloak-credentials | keycloak-api-secret |
ort-server | ort-server-user-secrets-encryption | master-password, salt |
ort-server | ort-server-grafana | admin-user, admin-password |
curator | occtet-curator-secrets | username, postgres-password, database, ort-username, ort-password, keycloak-client-id, keycloak-client-secret |
vulnerablecode | vulnerablecode-secrets | secret-key, altcha-hmac-key, postgres-password, db-username, db-password, db-name |
default | one TLS Secret per host name | Created 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:
| Worker | CPU request / limit | Memory request / limit |
|---|---|---|
| config | 1 / 2 | 1 GiB / 1 GiB |
| analyzer | 1 / 2 | 4 GiB / 6 GiB |
| advisor | 1 / 2 | 1 GiB / 1 GiB |
| scanner | 2 / 4 | 8 GiB / 12 GiB |
| evaluator | 1 / 2 | 8 GiB / 12 GiB |
| reporter | 1 / 3 | 4 GiB / 6 GiB |
| core (Deployment) | 1 / 4 | 2 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):
| Component | CPU request / limit | Memory request / limit |
|---|---|---|
| web | 0.5 / 2, plus 0.05 / 0.5 for nginx | 1 GiB / 4 GiB, plus 64 MiB / 256 MiB for nginx |
| scheduler | 0.1 / 0.5 | 256 MiB / 1 GiB |
| worker-default, 4 replicas | 0.25 / 2 per replica | 1 GiB / 4 GiB per replica |
| worker-high, 2 replicas | 0.25 / 2 per replica | 1 GiB / 4 GiB per replica |
| PostgreSQL | 0.5 / 2 | 1 GiB / 4 GiB |
| Redis | 0.1 / 0.5 | 128 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.
| Namespace | Volume | Size | Storage class | Notes |
|---|---|---|---|---|
ort-server | PostgreSQL data | 200 GiB | csi-cinder-classic | 61 GB used on 6 October 2026 |
ort-server | RabbitMQ data | 8 GiB | csi-cinder-classic | |
ort-server | Shared volume mounted at /mnt/secrets | 1 GiB | csi-cinder-classic | Mounted by the core |
keycloak | PostgreSQL data | 8 GiB | csi-cinder-high-speed | |
curator | PostgreSQL data | 50 GiB | csi-cinder-high-speed | 674 MB used on 6 October 2026 |
curator | NATS JetStream | 50 GiB | csi-cinder-high-speed | |
curator | Ollama models | 50 GiB | csi-cinder-high-speed | |
curator | NFS server backing volume | 100 GiB | csi-cinder-high-speed | Serves the ReadWriteMany class nfs, from which the 90 GiB project-data volume is allocated |
vulnerablecode | PostgreSQL data | 100 GiB | csi-cinder-high-speed | Created on 6 October 2026; database of 15 GB on 15 October 2026 |
vulnerablecode | Redis data | 2 GiB | csi-cinder-high-speed | |
| Total in use | 539 GiB | 209 classic, 330 high-speed |
Section 15.3 reconciles these volumes with the billed storage.