Kontekst
Dette projekt er Java 21 + Spring Boot control-plane-siden af den bredere migrering af en IoT audit-platform, som jeg beskriver i Migrering af en IoT Audit Engine fra Scala (ZIO) til Java 21 + Spring Boot.
Det offentlige repository ligger på GitHub:
ThomasBonderup/certificate-control-plane-api
Problemet er operationelt mere end kryptografisk: når antallet af embedded IoT-enheder vokser, bliver certificate lifecycle hurtigt til mere end “gem et certifikat et sted”. Teams skal vide, hvilke certifikater der findes, hvilken tenant der ejer dem, hvilke assets der afhænger af dem, hvilke der nærmer sig udløb, hvem der ejer renewal, hvilken status renewal har, og hvilke workflow-ændringer der skal blive til holdbar audit-evidens.
Servicen er bevidst afgrænset som et control plane, ikke som certifikatparser eller audit execution engine. Den ejer platform-state omkring certifikater, assets, bindings, workflow-status, tenant-grænser, API-dokumentation og operationel integration. Teknisk probe execution, TLS-validering, findings-generering og rapportlogik ligger uden for denne service.
Den opdeling holder audit execution og platform workflow i hver sit plan og gør grænsen tydelig gennem API’er, schema migrations, tests og dokumentation.
Hvad API’et gør
Repository’et implementerer et Spring Boot API til certificate lifecycle management i embedded- og IoT-miljøer:
- Certifikatinventar med status, gyldighedsperiode, owner, noter, fingerprint, issuer, serial number og renewal workflow-felter.
- Asset-inventar med tenant, type, environment, hostname, location og audit-felter.
- Certificate-to-asset bindings modelleret som first-class records med binding type, endpoint, port og uniqueness constraints.
- Reverse lookup API’er fra asset til certificate bindings og bundne certifikater.
- Expiring-soon og attention-needed views for certifikater, der er udløbet, blokerede, tæt på udløb, uden owner eller ikke bevæger sig gennem renewal.
- Summary endpoints for tenant-scoped certificate posture.
- Renewal history API’er baseret på gemte status-change rækker.
- Evidence ingestion-fundamenter, der accepterer raw probe evidence og publicerer envelopes til Kafka.
Controllerne er dokumenteret med OpenAPI annotations og eksponeret gennem lokal Swagger UI. Den committede Postman collection dækker token acquisition, authorization failures, certificate/asset/binding flows og beskyttede actuator endpoints.
Spring Boot-arkitektur
Implementeringen bruger en konventionel, men produktionsorienteret Spring Boot-struktur:
controller-klasser ejer HTTP-routes, validation entry points, response codes,Locationheaders, pagination defaults og OpenAPI metadata.apirecords definerer request- og response-DTO’er med validation annotations og Swagger schema examples.servicesholder applikationslogik, transaktionsgrænser, tenant enforcement, renewal workflow-regler, outbox writing og event publication.repositoriesbruger Spring Data JPA derived queries og JPQL til tenant-scoped filters, expiring-soon queries, attention-needed queries og summary counts.model-klasser definerer JPA entities og enums for certifikater, assets, bindings, renewal history, evidence envelopes og outbox events.commonogconfiggiver shared API errors, JSON security errors, tenant/user providers, pageable sanitization, OpenAPI setup, scheduling og Spring Security.
Det gør projektet nemt at reviewe, fordi de centrale ansvar er eksplicitte: request shape, sikkerhed, forretningsregel, persistencemodel, integrationspunkt og testdækning har alle synlige hjem.
Sikkerhed, tenancy og authorization
Sikkerhed er ikke kun beskrevet i README’en; den er implementeret og testet:
- Servicen kører som stateless OAuth2 resource server.
- Lokal udvikling bruger Keycloak med rendered realm template og JWTs, der indeholder et
tenantIdclaim. - Spring Security mapper scopes til authorities og udtrækker Keycloak realm/client roles til
ROLE_*authorities. GET /api/**krævercontrolplane.read;POST,PATCHogDELETE /api/**krævercontrolplane.write.- Actuator health og info er public; metrics og Prometheus kræver
controlplane.read. @EnableMethodSecurityaktiverer method-level authorization.- Sletning af certifikater og assets er beskyttet med
@PreAuthorize("hasRole('ADMIN')"). - Tenant-aware services bruger det autentificerede
tenantIdclaim som den reelle grænse. - Create requests med
tenantIdskal matche JWT-claimet. - Cross-tenant resource access skjules bevidst som not found.
Testpakken dækker de vigtigste failure modes: manglende token, forkert scope, write-token der forsøger read, read-token der forsøger write, non-admin delete attempts, cross-tenant lookup-adfærd og mismatched tenant input.
Persistence og schema evolution
Persistence-laget er bygget omkring PostgreSQL og Flyway frem for ad hoc schema creation:
certificatesgemmer certificate metadata, tenant ownership, status, validity timestamps, owner, notes, audit fields, blocked reason og renewal update timestamp.assetsgemmer tenant-scoped operational assets med type, environment, hostname, location og audit fields.certificate_bindingsmodellerer certificate-to-asset relationships med uniqueness constraint på tværs af certificate, asset, binding type, endpoint og port.certificate_renewal_status_historygemmer workflow transitions til senere inspektion.outbox_eventsgemmer pending event payloads som PostgreSQLjsonbmed aggregate metadata, topic, key, status og publication timestamps.- Hibernate validerer schema ved startup med
ddl-auto: validate, mens Flyway ejer schema changes. - Indexes understøtter tenant, status, expiry, asset, binding, history og outbox lookup paths.
Det betyder noget, fordi certificate lifecycle-platforme står og falder med kedelig korrekthed: schema ownership, tenant filters, forudsigelige queries og auditable state transitions.
Kafka, evidence og outbox
Eventing-implementeringen har tre nyttige dele:
- Raw evidence ingestion accepterer probe evidence, mapper det til en
EvidenceEnvelopeog publicerer det til Kafka. - Renewal status changes repræsenteres som
CertificateRenewalStatusChangedEventrecords. - Renewal status updates skriver en outbox event som del af certificate update-flowet, og en
OutboxPublisherpublicerer pending rows til Kafka og markerer dem som published.
Outbox pattern bruges her for driftssikkerhed omkring renewal workflow-events.1 I stedet for kun at opdatere certificate-rækken og derefter forsøge at publicere direkte til Kafka som en separat best-effort sideeffekt, skriver servicen en pending event i databasen som del af update-flowet. En publisher kan derefter læse pending rows, publicere dem og markere dem som published. Det giver systemet et holdbart handoff point mellem database-state og asynkron event delivery.
Den vigtige nuance: dette er et outbox-backed flow for renewal status publication, ikke en påstand om at alle asynkrone stier i servicen er outbox-backed. Raw evidence ingestion publicerer stadig direkte til Kafka. Den distinktion holder implementeringsgrænsen ærlig og viser samtidig, hvor driftssikkerheden var vigtigst for lifecycle audit-events.
Dokumentation og lokal developer experience
Repository’et er sat op, så det kan reviewes, køres og afprøves manuelt:
README.mdforklarer service scope, lokal setup, Keycloak token flows, tenant enforcement rules, OpenAPI, Swagger UI, Postman-brug, actuator endpoints og sikkerhedsforventninger.specs/architecture.mddokumenterer control-plane/execution-plane split, domain model, layered structure, persistence model og den aktuelle API-overflade.docker-compose.ymlstarter PostgreSQL, Kafka, Keycloak, API-containeren og optional Kafka UI med local-only port bindings..env.exampleog Postman environment templates er sanitized.- Swagger UI findes lokalt på
/swagger-ui.html, bakket af/v3/api-docs. - Postman collection indeholder read/write/admin token flows, no-token demos, read/write failure demos, certificate/asset/binding requests og protected actuator checks.
Den type dokumentation er et leverancesignal: backenden er ikke kun implementeret; den er pakket, så en anden engineer hurtigt kan vurdere adfærden.
Testdækning
Den automatiserede testpakke er en af projektets stærkeste dele.
./gradlew test passerer, og den genererede JUnit XML rapporterer 88 tests på tværs af 13 testklasser. Dækningen handler ikke kun om happy-path CRUD:
- MockMvc integration tests for certificates, assets, bindings, audit runs og authorization-adfærd.
- Testcontainers PostgreSQL integration coverage for persistence-backed API-adfærd.
- Spring Security test support for JWT scopes, tenant claims, roles og protected actuator endpoints.
- Tests for pagination, sorting, malformed Swagger sort fallback, filters, summary counts, expiry windows, attention-needed rules, renewal workflow transitions og tenant isolation.
- Service tests for renewal-status outbox writing.
- Tests for Kafka producer configuration, renewal-status consumer persistence, outbox event writing og outbox publication ordering.
Jeg beskriver testpakken ud fra den adfærd, den beviser, ikke med en headline-procent, fordi repository’et ikke aktuelt publicerer en separat code coverage report.
Outcome
Resultatet er et offentligt backendprojekt, der demonstrerer:
- Java 21 og Spring Boot platform delivery.
- REST API-design med validation, response DTO’er, pageable/sortable endpoints og klare HTTP-semantikker.
- Spring Security-fundamenter med JWT resource server configuration, Keycloak role extraction, scope checks, method-level authorization og JSON security errors.
- Tenant-aware service-design med eksplicit claim enforcement og cross-tenant protections.
- PostgreSQL/Flyway schema ownership med JPA/Hibernate validation.
- Kafka-integration, evidence ingestion-fundamenter, renewal history og et outbox-backed event flow.
- OpenAPI, Swagger UI, Postman, Docker Compose, Keycloak local setup, Actuator, Prometheus metrics og praktisk udviklerdokumentation.
- En meningsfuld automatiseret testpakke, der beviser adfærd på tværs af web, service, persistence, security og integration boundaries.
For teknisk kontekst kan du læse den relaterede migrationsartikel eller inspicere repository’et direkte:
Læs Java-migrationsartiklen eller se det offentlige GitHub-repository.
Referencer
Footnotes
-
Chris Richardson, Transactional outbox pattern, Microservices.io. ↩