trust_list_proxy
Two-part component: (1) a single-shot Kotlin/JVM batch job that downloads and cryptographically validates the EU List of Trusted Lists (LOTL) and all member-state Trust Lists (TLs) via the EU DSS library, writing the validated files into a local cache directory; and (2) a thin nginx Docker image that serves that cache directory (plus a generic forward-proxy) to the FaceKom PDF Service at runtime. It is NOT a long-running service — main() runs to completion and exits (src/main/kotlin/Main.kt:7-15).
No README in this repo
There is no
README.md/README.adocanywhere in the tree (verified by fullgit ls-tree -r). The authoritative prose docs are the two Hungarian-language AsciiDoc files underdocumentation/(DSS-proxy.adoc,LOTLDownload.adoc) plus the PlantUML sequence diagramLOTLDownload.puml. This doc draws the “role in FaceKom” facts from those.
Base branch: master. HEAD at doc time: 0e3f0fe “Merge pull request #7 from TechTeamer/docs/update-proxy” (2024-07-23, agostonromhanyi). Project version 0.1.0 (gradle.properties:2).
1. Purpose & role in FaceKom
From documentation/DSS-proxy.adoc and documentation/LOTLDownload.adoc (verified, translated):
- eIDAS trust anchoring for PDF signature validation. The EU LOTL (
https://ec.europa.eu/tools/lotl/eu-lotl.xml) lists every member state’s Trust List; each TL enumerates the qualified trust-service-provider CA certificates for that country. The PDF Service needs these CA certs to trace a signed PDF back to an eIDAS Trust Anchor; if a TL is invalid, all its CAs are treated as untrusted and the PDF cannot be validated (LOTLDownload.adoc:24). - Why a proxy and not direct download.
DSS-proxy.adoc:16-22describes the proxy’s two runtime jobs: (a) serve thetl_cachecontents as a JSON directory listing + individual files so the PDF Service can fetch normalized-URL-named TL files, and (b) act as the single controlled internet egress for the PDF Service — every outbound request frompdfservice(incl. OCSP/CRL revocation lookups) is routed through this proxy. - DSS URL normalization contract.
DSS-proxy.adoc:7documents the file-naming convention: the DSS library normalizes a TL URL such ashttps://hun-domain.hu/hun-trustlist.xmltohttps___hun_domain_hu_hun_trustlist_xml. Both the downloader (this repo) and the PDF Service’s DSS use the identical normalization, so files land under matching names — that is what makes the nginx cache lookup work (e.g.…/tl_cache/https___ec_europa_eu_tools_lotl_eu_lotl_xml). - Three integration options are documented (
DSS-proxy.adoc:1-7): fulltl_cachedownload + offline run inside PDF Service; collect X509 certs to a base64 file handed to PDF Service; or PDF Service downloads/parses TLs from the proxy. The repo implements the cache-and-serve path.
See sibling architecture-overview for where the PDF Service sits. This repo is a leaf dependency feeding trust material to it.
2. Tech stack & runtime
| Aspect | Value | Source |
|---|---|---|
| Language | Kotlin (JVM) | build.gradle.kts:2 kotlin("jvm") version "1.9.23" |
| JVM toolchain | Java 18 | build.gradle.kts:38-40 jvmToolchain(18) |
| Build tool | Gradle 8.5 (Kotlin DSL) | gradle/wrapper/gradle-wrapper.properties:4 |
| Packaging | application plugin + fat jar | build.gradle.kts:4,42-50 |
| App name / main | trust-list-proxy / com.facekom.tlproxy.MainKt | build.gradle.kts:29-32 |
| Group | com.facekom.trustlistproxy | build.gradle.kts:7 |
| Config format | YAML via SnakeYAML | build.gradle.kts:17, ConfigurationBuilder.kt:3 |
| Logging | SLF4J + Logback (classic 1.4.14) | build.gradle.kts:16, logback.xml |
| Serving layer | nginx:latest Docker image | docker/nginx_proxy/Dockerfile:1 |
Core domain dependency — EU DSS (Digital Signature Service) 6.0 (build.gradle.kts:19-26):
eu.europa.ec.joinup.sd-dss:dss-tsl-validation:6.0— the Trust List validation job engine.eu.europa.ec.joinup.sd-dss:dss-service:6.0— HTTP data loaders (CommonsDataLoader,FileCacheDataLoader).eu.europa.ec.joinup.sd-dss:dss-utils-apache-commons:6.0.- BouncyCastle
bcprov-jdk18on/bcpkix-jdk18onpinned to 1.78.1, with the transitive BC fromdss-tsl-validationexplicitly excluded (build.gradle.kts:18-23) — comment cites CVE-2024-34447 and CVE-2024-29857 in the DSS-shipped BC version.
DSS 6.0 requires Java 17+; this project targets Java 18. The release pipeline runs the jar on the
openjdk:18Docker image (jenkins/release.Jenkinsfile:74).
3. Build & run
This is a Gradle project — there is no package.json (it is not a Node service). The closest analogue to “scripts” are the Gradle tasks defined/customized in build.gradle.kts:
| Gradle task / command | Effect | Source |
|---|---|---|
./gradlew build | Standard Gradle build; check is wired to also run OWASP dependencyCheckAnalyze (build.gradle.kts:54-56). CI uses clean build -x test -x check. | jenkins/ci.Jenkinsfile:14 |
./gradlew check | Runs tests + OWASP dependency-check (build.gradle.kts:55). Fails build on CVSS ≥ 6.6 (build.gradle.kts:67); scans runtimeClasspath; emits XML+HTML reports (build.gradle.kts:64-70). | build.gradle.kts |
./gradlew test | JUnit 5 platform (useJUnitPlatform(), build.gradle.kts:34-36). | build.gradle.kts |
./gradlew jar | Builds fat jar — bundles every compileClasspath entry via zipTree, Main-Class=com.facekom.tlproxy.MainKt, duplicatesStrategy=INCLUDE (build.gradle.kts:42-50). Output: build/libs/trust-list-proxy-0.1.0.jar. | build.gradle.kts, jenkins/release.Jenkinsfile:84 |
./gradlew run | Provided by the application plugin; runs MainKt. (Plugin applied at build.gradle.kts:4,52.) | inferred-from-plugin |
./gradlew printVersion | Custom task: prints project.version to stdout (build.gradle.kts:58-62). Used for CI versioning. | build.gradle.kts |
./gradlew dependencyCheckAnalyze | OWASP scan task (from org.owasp.dependencycheck plugin v8.2.1, build.gradle.kts:3). | build.gradle.kts |
Run the batch job (downloader):
java -jar build/libs/trust-list-proxy-0.1.0.jar
main() loads application.yml from the classpath, runs TrustListDownloader(...).downloadLists(), logs “Trust list download complete.”, exits (Main.kt:7-15).
Build & run the serving image (nginx):
- Dockerfile:
docker/nginx_proxy/Dockerfile—FROM nginx:latest AS NGINX; copiestl_cache/→/workspace/tl_cache/anddefault.conf→/etc/nginx/conf.d(so the cache must be populated by the batch job before the image is built). - Release pipeline does:
docker build ./docker/nginx_proxy/ -t tl-server-nginx(jenkins/release.Jenkinsfile:116). - IntelliJ run config
tlproxy-serverdeploys imagetlproxy-docker:latestas containertlproxy-serverwith--network "host"(.run/tlproxy-server.run.xml).
Two Dockerfiles? No — only ONE.
There is exactly one Dockerfile in the repo:
docker/nginx_proxy/Dockerfile. There is no Dockerfile that builds/runs the Kotlin jar — the jar is run directly on anopenjdk:18image in the pipeline (jenkins/release.Jenkinsfile:71-84), not packaged into its own image.
4. Top-level structure
| Path | What it is | Verified |
|---|---|---|
src/main/kotlin/ | Kotlin source: Main.kt + com/facekom/tlproxy/{configuration,logging,service}. 5 source files total. | listed + read |
src/main/resources/ | application.yml, logback.xml, and lotl-certs/ (8 PEM certs + dss-keystore.p12). | listed + read |
src/test/kotlin/ | 3 test files: 2 unit (configuration, service) + 1 functional/LotlCertificateTest.kt. | listed + read |
src/test/resources/ | config-test.yml (config-loading fixture). | read |
docker/nginx_proxy/ | nginx serving image: Dockerfile, default.conf, .dockerignore. Runtime cache goes in tl_cache/ (gitignored, .gitignore:44). | read all |
documentation/ | DSS-proxy.adoc, LOTLDownload.adoc (Hungarian prose), LOTLDownload.puml (sequence diagram). | read all |
jenkins/ | 3 pipelines: ci.Jenkinsfile, periodic-check.Jenkinsfile, release.Jenkinsfile. | read all |
gradle/wrapper/ | Gradle 8.5 wrapper jar + properties. | read properties |
.run/ | IntelliJ run configs: Debug.run.xml (remote JVM debug, port 5005), tlproxy-server.run.xml (docker deploy). | read both |
build.gradle.kts, settings.gradle.kts, gradle.properties, gradlew, gradlew.bat | Gradle build + wrapper scripts. | read |
5. Helper scripts under bin/
No first-party
bin/directory exists.The only executable helper scripts are the Gradle wrapper scripts at repo root (these are stock Gradle-generated, not first-party logic):
gradlew— POSIX shell wrapper that bootstraps Gradle 8.5 (header: “Copyright © 2015-2021 the original authors”,gradlew:1-4).gradlew.bat— Windows batch equivalent.
.gitignore:27-29explicitly ignoresbin/(except undersrc/main/src/test), and none is tracked. There are no custom shell/python/node helper scripts in this repo.
6. Key modules / source files
All first-party Kotlin (5 files) — read in full:
src/main/kotlin/Main.kt (entrypoint, 15 lines): loads application.yml, constructs TrustListDownloader, calls downloadLists(), logs progress. Linear, no args, no loop.
src/main/kotlin/com/facekom/tlproxy/service/TrustListDownloader.kt (the core, ~67 lines): builds and runs a DSS TLValidationJob. Key behavior:
downloadLists()→createTrustListValidationJob().offlineRefresh()(:22-24). Note it callsofflineRefresh(), but the online data loader is what actually hits the network (DSS semantics:offlineRefreshsynchronizes the in-memory model + cache using the configured loaders).createTrustListValidationJob()(:26-34):TrustedListsCertificateSource, the LOTL source, online + offline data loaders, andExpirationAndSignatureCheckStrategy()as the synchronization strategy (i.e. drop expired/invalid TLs — corroborated byLOTLDownload.adoc:22andLOTLDownload.puml:51-53).createLotlSource()(:36-46):LOTLSourcewithurl = configuration.listOfTrustListsUrl,isPivotSupport = true, EU LOTL/TL predicates (TLPredicateFactory.createEULOTLPredicate()/createEUTLPredicate()),OfficialJournalSchemeInformationURI(...)for signing-cert announcement, andGrantedTrustService()trust-service predicate (only “granted” services).createLotlCertificateSource()(:48-50): loadsKeyStoreCertificateSource(File("src/main/resources/lotl-certs/dss-keystore.p12"), "PKCS12", <password>.toCharArray()). Path is hard-coded relative to CWD (see gotcha #10).createOnlineLoader()(:52-58):FileCacheDataLoaderwith expiration0, realCommonsDataLoader(internet-capable, proxy-aware), cache dir =configuration.cacheDirectory.createOfflineLoader()(:60-66):FileCacheDataLoaderwith expiration-1andIgnoreDataLoader(never fetches), same cache dir.
src/main/kotlin/com/facekom/tlproxy/configuration/Configuration.kt (12 lines): plain mutable POJO with 4 fields — cacheDirectory, officialJournalSchemeInformationUri, listOfTrustListsUrl, lotlKeystorePassword. KDoc notes the YAML must carry the fully-qualified class tag.
src/main/kotlin/com/facekom/tlproxy/configuration/ConfigurationBuilder.kt (11 lines): createConfigFromFile(classPathResource) — SnakeYAML Yaml().loadAs(inputStream, Configuration::class.java) from a classpath resource.
src/main/kotlin/com/facekom/tlproxy/logging/Logger.kt (5 lines): top-level var logger = SLF4J LoggerFactory.getLogger("trust-list-proxy-log").
Quirky package nesting
Logger.ktdeclares packagecom.facekom.tlproxy.com.facekom.tlproxy.logging(doubled segment), andMain.kt:3imports it ascom.facekom.tlproxy.com.facekom.tlproxy.logging.logger. The physical path is.../tlproxy/logging/Logger.kt— the package name does not match the directory. This compiles (Kotlin permits it) but is clearly an unintended copy-paste artifact.
7. Configuration
Primary config: src/main/resources/application.yml (loaded from classpath by createConfigFromFile("application.yml"), Main.kt:9). Fields (verified values):
| Key | Default value | Meaning |
|---|---|---|
cacheDirectory | "docker/nginx_proxy/tl_cache/" | Where DSS writes downloaded TL files. Relative to CWD (comment: “base will be the project’s root … and the jar’s directory”). Note this default writes straight into the nginx image’s copy source. |
officialJournalSchemeInformationUri | https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=uriserv:OJ.C_.2019.276.01.0001.01.ENG | OJ URL identifying the LOTL signing-cert keystore; builds the keystore from pivots before that URL. |
listOfTrustListsUrl | https://ec.europa.eu/tools/lotl/eu-lotl.xml | The EU LOTL XML. |
lotlKeystorePassword | "facekom" | Password for dss-keystore.p12. Comment (application.yml:18): the certs are public, used only for signature validation, not signing — so the in-repo password is intentional/non-sensitive. |
The config object is not validated. There is no AJV/getconfig/JSON-schema layer (those are Node patterns; absent here). SnakeYAML
loadAsrequires the YAML to deserialize intoConfiguration—application.ymlhas no explicit!!com.facekom...type tag despite the KDoc inConfiguration.kt:6saying one "has to be part of the yaml file". It works becauseloadAs(..., Configuration::class.java)supplies the root type; the test fixtureconfig-test.ymlis likewise untagged.
Logging config: src/main/resources/logback.xml — single STDOUT ConsoleAppender, pattern %d [%thread] %-5level %logger %msg%n, root level warn (so the app’s own INFO “Loading configuration…” lines from Main.kt are suppressed at the root level unless the trust-list-proxy-log logger is raised — they are not configured, so they do not print).
Env vars: none read by application code (no System.getenv in the Kotlin sources). The only env-style values are in CI: NGINX_DOCKER_IMAGE_NAME=tl-server-nginx (release.Jenkinsfile:8).
nginx config: docker/nginx_proxy/default.conf —
root /workspace, listens on port 80 (IPv4+IPv6).location /tl_cache/→autoindex on; autoindex_format json;+sendfile(this is the JSON directory listing the PDF Service consumes, perDSS-proxy.adoc:18).location /→ generic forward proxy:resolver 8.8.8.8;andproxy_pass http://$http_host$uri$is_args$args;(this is the “controlled internet egress” path for the PDF Service,DSS-proxy.adoc:22).
8. Tests
- Framework: JUnit 5 (Jupiter),
kotlin("test").useJUnitPlatform()(build.gradle.kts:34-36). Run with./gradlew test. - Location:
src/test/kotlin/. Three files:
| Test | What it checks | Notes |
|---|---|---|
configuration/ConfigurationBuilderKtTest.kt | createConfigFromFile("config-test.yml") loads all 4 fields correctly. | Pure unit, fast. Fixture: src/test/resources/config-test.yml. |
service/TrustListDownloaderTest.kt | Full LOTL download writes ≥1 file into a temp cache. | @Disabled (:15) — “downloading the entire lotl takes a lot of time and it will be part of a pipeline anyway.” Cleans temp dir in @AfterEach. |
functional/LotlCertificateTest.kt | Runs LOTLWithPivotsAnalysis and asserts every cached validation DTO isValid — i.e. the in-repo Official Journal certs/keystore still validate the live LOTL pivot chain. | Network-dependent. Class comment (:21-26) documents the manual cert-refresh procedure when it fails (download new certs from eur-lex, rebuild keystore). |
CI invocations:
ci.Jenkinsfileruns Build →./gradlew check(vuln scan) →./gradlew test, publishing GitHub checks.periodic-check.Jenkinsfileruns only./gradlew :test --tests "LotlCertificateTest"and./gradlew check; on cert-test failure it emailsagoston.romhanyi@facekom.comto update the certs (:17-20); on vuln scan it archives + publishes the dependency-check report. This is the “planned daily check” described inLOTLDownload.adoc:26-31.
9. Dependencies on other FaceKom services
Evidenced relationships (no RabbitMQ / DB / Redis anywhere in this repo — verified absent):
- PDF Service (consumer, HTTP). Documented in
DSS-proxy.adoc/LOTLDownload.adoc. The PDF Service (a) fetches thetl_cacheJSON listing + individual TL files over HTTP from this nginx (location /tl_cache/), and (b) routes all its outbound internet traffic (incl. OCSP/CRL) through this nginx as a forward proxy (location /). The PDF Service configures its DSSProxyConfighostto this proxy’s URL (DSS-proxy.adoc:12). This repo contains no code calling the PDF Service — the dependency is inbound (PDF Service → proxy). - External (non-FaceKom) endpoints the batch job calls directly over HTTPS:
ec.europa.eu/tools/lotl/eu-lotl.xml(LOTL), the LOTL pivot XMLs, each member-state TL URL listed in the LOTL, andeur-lex.europa.eu(OJ cert reference). SeeLOTLDownload.pumlfor the full sequence.
The PDF Service lives in a separate repo — see architecture-overview / vuer_oss for the broader service map. This component is a one-way trust-material supplier.
10. Verified gotchas
Hard-coded keystore path breaks if CWD ≠ repo root
TrustListDownloader.kt:49(andLotlCertificateTest.kt:58) load the keystore viaFile("src/main/resources/lotl-certs/dss-keystore.p12")— a path relative to the current working directory, not the classpath. The fat jar bundles the resource, but this code reads from the source tree path, so the jar must be launched from a dir that hassrc/main/resources/...beside it. The release pipeline mounts the workspace into/usr/project/and runs the jar from there (release.Jenkinsfile:75,84).
cacheDirectorydefault writes into the Docker build contextDefault
cacheDirectory: "docker/nginx_proxy/tl_cache/"(application.yml:5) means the batch job’s output is exactly what the nginxDockerfilelaterCOPYs. Order matters: run the jar first, then build the nginx image.tl_cache/*is gitignored (.gitignore:44), so the dir may not exist on a fresh clone until the job runs — the pipeline verifies non-empty before building (release.Jenkinsfile:94-108).
App INFO logs are silent by default
logback.xmlroot level iswarn; thetrust-list-proxy-loglogger is not separately configured, so thelogger.info(...)calls inMain.ktdo not appear on stdout. Raise the level to see progress.
release.Jenkinsfileis partially stubbed / inconsistentThe Build/Check/Test stages are commented out (
release.Jenkinsfile:12-55). The verify stage greps/usr/jar/docker/nginx_proxy/tl_cache/(:100) while the jar runs against${WORKSPACE}/…/tl_cache/mounted at/usr/project/(:75) — the paths don’t line up. The jar version0.1.0is hard-coded (:84, with a// todo make versioning dynamic). The Harbor upload stage is a literalecho Implement me(:134). Treat this pipeline as WIP.
Doubled package name in
Logger.ktPackage is
com.facekom.tlproxy.com.facekom.tlproxy.logging(see §6). Cosmetic but confusing when searching imports.
No Git-LFS in this repo
There is no
.gitattributesand no LFS pointers.src/main/resources/lotl-certs/dss-keystore.p12is a real 16.8 KB PKCS#12 binary (committed directly), andcert-1.pem…cert-8.pemare plaintext PEM X509 certs (public EU Official Journal LOTL-signing certs). Safe to inspect; the.p12is binary (do not cat).
Unverified / gaps
- Did not run any build, test, or Docker command (read-only task) — task entries verbatim are sourced from
build.gradle.ktsand the Jenkinsfiles, not from execution. gradlew/gradlew.batread only at the header level (confirmed stock Gradle 8.5 wrapper); did not line-by-line audit the wrapper bootstrap logic (third-party generated).gradle/wrapper/gradle-wrapper.jaris a binary (not read).dss-keystore.p12and the 8.pemcerts: existence + first cert header confirmed; binary/cert internals not decoded.- The two
.adocdocs and.pumlare in Hungarian; “role in FaceKom” facts are my translation of them — meaning preserved, exact wording is translated. - DSS 6.0 library internals (
TLValidationJob,LOTLSource, data loaders, predicates) are third-party and not documented here beyond how this repo configures them. - Exact DSS
offlineRefresh()vs network behavior is asserted from the configured loaders +DSS-proxy/pumldocs, not from stepping the DSS source.
Sources
Files read in full unless noted:
build.gradle.kts,settings.gradle.kts,gradle.properties,.gitignoregradle/wrapper/gradle-wrapper.properties;gradlew(header only)src/main/kotlin/Main.ktsrc/main/kotlin/com/facekom/tlproxy/service/TrustListDownloader.ktsrc/main/kotlin/com/facekom/tlproxy/configuration/Configuration.ktsrc/main/kotlin/com/facekom/tlproxy/configuration/ConfigurationBuilder.ktsrc/main/kotlin/com/facekom/tlproxy/logging/Logger.ktsrc/main/resources/application.yml,src/main/resources/logback.xmlsrc/test/kotlin/com/facekom/tlproxy/configuration/ConfigurationBuilderKtTest.ktsrc/test/kotlin/com/facekom/tlproxy/service/TrustListDownloaderTest.ktsrc/test/kotlin/functional/LotlCertificateTest.ktsrc/test/resources/config-test.ymldocker/nginx_proxy/Dockerfile,docker/nginx_proxy/default.conf,docker/nginx_proxy/.dockerignoredocumentation/DSS-proxy.adoc,documentation/LOTLDownload.adoc,documentation/LOTLDownload.pumljenkins/ci.Jenkinsfile,jenkins/periodic-check.Jenkinsfile,jenkins/release.Jenkinsfile.run/Debug.run.xml,.run/tlproxy-server.run.xmlsrc/main/resources/lotl-certs/(dir listing;cert-1.pemheader;dss-keystore.p12file-type only)git ls-tree -r HEAD,git log -1,git branch -a(repo metadata)