TR 03187-Conformance Statement
CIVITAS/CORE aims to conform to Level 1 of BSI TR-03187, a German security recommendation for Urban Data Platforms. In the context of BSI TR-03187, CIVITAS/CORE implements requirements that pertain to "Entwickler" (Software Developers) and "Lösungsanbieter" (Integrators). Requirements that pertain to "Betreiber" (Operators) are out of scope.
The following sections detail how the pertinent requirements of BSI TR-03187 are fulfilled in CIVITAS/CORE.
Scope note on 3rd party components: The conformance assessment currently covers components developed by the CIVITAS/CORE project. Verification of 3rd party components (e.g. upstream container images, external libraries) is out of scope for the time being.
State covered: This statement reflects the current development state of CIVITAS/CORE — the staging branch of the deployment repository and the develop branch of the platform repository.
| Requirement | Status | Description |
|---|---|---|
| AR-1 Geringstmögliche Privilegien | Done | Least privilege enforced at multiple layers: OPA default allow := false with explicit permission grants only. Containers run as non-root (UID 1000/1001) with drop: [ALL] capabilities, readOnlyRootFilesystem: true, allowPrivilegeEscalation: false. Dedicated Kubernetes ServiceAccounts per deployment. Spring Security anyRequest().authenticated() catch-all. Mandated through Security Architecture Principles. |
| AR-2 Legacy-Clienttechnologien vermeiden | Done | For components developed by CIVITAS/CORE, ESLint rules block legacy HTML elements (<object>, <embed>, <applet>) at error level in the CI pipeline (see ESLint config and implementation ticket). |
| AR-3 Nicht verwendete Abhängigkeiten entfernen | In Progress | Unused-dependency detection for JVM modules runs in CI via mvn dependency:analyze -DfailOnWarning=true in the portal backend, config-adapter and AuthZ pipelines, each with a curated exclusion list for dependencies the analyzer cannot resolve through indirection. SSDLC policy (Section 6.6) and the Deployment Checklist mandate reasonable effort toward minimizing shipped artifacts. Gaps: the portal-model pipeline runs the same check but tolerates its failure (allow_failure: exit_codes: [1] in .gitlab/ci/portal-model.yml), and model-forge, which the portal backend ships, has no such check (fix: platform!896; clean-up of model-forge: platform#2362). The config-adapter ships as a single container image bundling every adapter module, so unused adapter code cannot be pruned per-deployment (platform#1870), and the frontend has no equivalent unused-package-dependency check (platform#1875). |
| AR-4 Version Pinning | In Progress | Dependencies and container images of the product are pinned to specific versions, ensured through the Deployment Standards (section "Version Management"), the Frontend Style Guide (section "Imports / Exports"), and the Backend Style Guide (section "Imports"). External Helm chart versions are pinned in components/*/charts.yaml and container images in components/*/images.yaml; Java dependencies are pinned in pom.xml, frontend dependencies via pnpm-lock.yaml, and Dockerfiles are version-tagged (portal-backend and authz-repo additionally pin their base image by SHA256 digest). Implementation ticket: Pin Helm image tags to release versions. Gaps: the CI job that computes the version tag of every published image runs on the floating alpine:latest, and about 60 CI job images use floating tags without digest (platform#2300); the postgres role-setup Job uses postgres:16-alpine from a chart template, outside images.yaml (platform#2300). Some images are pinned by mutable tag rather than digest, and the authz deployments use pullPolicy: Always (deployment#278). Renovate proposes updates centrally; they are merged only after approval on the dependency dashboard, approvals are behind, and some pins have no Renovate manager yet, so pins can age (platform#1472). |
| AR-5 Sitzungs-IDs zufällig und eindeutig | Done | All JPA entities use @GeneratedValue(strategy = GenerationType.UUID) (Level 4 random UUIDs). Frontend session IDs (UML modeler, pipeline editor) use crypto.randomUUID(). Backend is stateless (SessionCreationPolicy.STATELESS), no server-side session IDs. Auth sessions delegated to Keycloak. |
| AR-6 Sitzungsablauf nach Inaktivität | Done | Keycloak SSO Session Idle is set to 60 minutes. A frontend inactivity detector stops background token refresh and triggers logout after a period of no user interaction, so the Keycloak idle timeout takes effect. Implementation ticket: Add frontend inactivity detector to enforce session timeout. |
| AR-7 Rollenbasierte Benutzerverwaltung | Done | Role-based user management is implemented. Users are assigned role "Standard User" on registration. For details, see Authorization Data Model. |
| AR-8 Replay-Schutz | In Progress | User authentication is performed system-wide by OpenID Connect with signed JWT tokens including timestamps. Portal-backend has enable.idempotence: true (exactly-once per partition, portal-backend/src/main/resources/application.yaml), and the Kafka consumers commit offsets manually after handling. mTLS via Linkerd provides transport-layer replay protection between components. The Flowable saga orchestrator rejects a re-delivered trigger by its Kafka record coordinate (FlowableTriggerConsumer.java:192), and the adapters recover from duplicate creates rather than pre-checking for them: FrostSagaHandler.handleCreateProjectConflict() treats a FROST 409 as a duplicate and re-resolves the existing project, and the PostGIS adapter absorbs duplicate-key errors via SqlDdlSupport/PostgisDialect.isDuplicate(). Gaps: the saga trigger topic carries plain JSON without a message id, nonce or timestamp, so a re-produced identical trigger starts a second saga rather than being rejected — the Kafka-coordinate key is re-delivery idempotency, not replay protection; and deduplication is missing on the non-Flowable config-adapter adapter paths, which are the configured production set (ADAPTERS=apisix,keycloak,frost) and run alongside the orchestrator in the same process (platform#1584). |
| AR-9 Geheimnisse müssen änderbar sein | Done | As per SSDLC (section 9) and Deployment Standards (Section "Security"), secrets are stored through Kubernetes secrets or other secure means external to the application, in a changable manner. Exception: Keycloak stores secrets (passwords, signing keys) in database; all of them are changable through regular Keycloak mechanisms. |
| AR-10 Geheimnisse von Benutzern dürfen nicht zur Sicherstellung von Vertraulichkeit verwendet werden | Done | User credentials are managed exclusively by Keycloak and never used for data confidentiality. All confidentiality mechanisms (TLS, K8s secrets) use system-managed credentials. Architectural separation: Keycloak handles user secrets, everything else uses infrastructure-level secrets. The requirement is also part of the Security Architecture Principles. |
| AR-11 Kryptografische Verfahren sollen TR-02102-1 entsprechen | Done | Production code uses BSI-approved algorithms: AES-256-GCM for credential encryption (CryptoUtils.java), PBKDF2-HMAC-SHA256 with 600k iterations for key derivation (CryptoKeyLoader.java), HKDF-SHA256 for per-credential key isolation, RS256 and ES256 for JWT/WebAuthn signatures (Keycloak realm config). No weak ciphers (DES/3DES/RC4/Blowfish) in production code. Requirement to use BSI-approved cryptography is also part of Backend Code Style Guide and Deployment Standards. |
| AR-12 Standardframeworks für sicherheitskritische Funktionen | Done | Standard frameworks used for all security-critical functions: Spring Security (backend auth/authz), Keycloak (identity management), NextAuth (frontend session management), Jackson (deserialization), OPA (policy enforcement), Linkerd (mTLS). No custom cryptography or authentication implementations. Ensured through inclusion in Security Architecture Principles document. |
| AR-13 Ganze Zertifikatkette validieren | In Progress | Encryption between container instances is enforced through mandatory mTLS via the Linkerd service mesh, with defaultInboundPolicy: cluster-authenticated set cluster-wide (defaults/environment/global.yaml); the APISIX ingress edge requires an authenticated mesh identity by default. A Kyverno policy, shipped in Enforce mode, rejects meshed namespaces without an inbound policy; it does not yet check the value of that policy (fix: deployment!326). Accepted risk: the config-adapter's NiFi REST client does not validate the server certificate chain (trust-all X509TrustManager, always-true HostnameVerifier; the deployment sets NIFI_TLS_INSECURE_SKIP_VERIFY while the code reads NIFI_TLS_INSECURE). Both sides are in the mesh, so Linkerd mTLS authenticates the connection (platform#1644). Not applicable today: the APISIX configuration chart turns off certificate verification for HTTPS upstreams, but no shipped upstream uses HTTPS (deployment#370). Gap: the portal frontend turns off certificate verification for all outbound connections (NODE_TLS_REJECT_UNAUTHORIZED: "0"), including the token exchange with Keycloak, which goes through the public ingress and is not covered by the mesh (deployment#240). |
| AR-14 Alle vorgesehenen Sicherheitsmechanismen defaultmäßig anschalten | In Progress | Security mechanisms enabled by default: the frontend sets a nonce-based CSP, X-Content-Type-Options, X-Frame-Options, and Referrer-Policy via Next.js middleware (portal-frontend/src/middleware.ts); Keycloak has brute-force protection (10-attempt lockout, failureFactor: 10) in production; the Kyverno runtime policies ship enabled in Enforce mode (runtimePolicies.failureAction: Enforce in defaults/environment/global.yaml). APISIX removes forged identity headers from every client request with the global rule strip-forged-identity-headers, so they cannot bypass the APISIX→OPA filtering chain. Gaps: the default is a single namespace (singleNamespace: true), so trust-zone separation needs the operator to change it (deployment#182, see AR-16). The Keycloak client template enables the password grant (ROPC) for every client by default (deployment#236). The Superset route has no OPA check; 2.0 ships Superset without data sources, and the OPA integration is planned (platform#1613). Security response headers are missing on API responses: APISIX sets no HSTS/CSP (deployment#242), and the frontend middleware does not cover /api/* (platform#2341). |
| AR-15 Verschlüsselte Kommunikation | Done | Encryption between container instances is enforced through mandatory mTLS via the Linkerd service mesh, with defaultInboundPolicy: cluster-authenticated set cluster-wide (defaults/environment/global.yaml); the APISIX ingress edge requires an authenticated mesh identity by default. A Kyverno policy, shipped in Enforce mode, rejects meshed namespaces without an inbound policy. Caveat: some components have no transport encryption of their own and depend on the mesh: Kafka overrides the chart default with tls: false and has no client authentication (deployment#333), etcd serves plain HTTP although tls.enabled: true is set (deployment#244), and Valkey has TLS off. Five NiFi ports accept connections without a mesh identity (deployment#371). |
| AR-16 Abschirmung unterschiedlicher Vertrauenszonen | In Progress | Namespace-based trust-zone isolation is available: each component can be deployed into its own namespace, every component ships a default-deny ingress NetworkPolicy with explicit per-component allow rules (defaults/environment/networkpolicies.yaml.gotmpl), and Linkerd mTLS is mandatory between pods. Gaps: the shipped default is still a single namespace (singleNamespace: true), so separate trust zones exist only when the operator sets singleNamespace: false (deployment#182). No component restricts outbound traffic (deployment#204). The portal-backend NetworkPolicy also admits the portal frontend directly, not only the gateway (platform#2358). In multi-namespace mode the component-namespace resolver fails open when a namespace cannot be resolved (deployment#255). |
| AR-17 Integritätsgeschützter Kanal für Deployment | Done | CIVITAS/CORE is delivered as source code and Helm charts through GitLab and container images through the GitLab Container Registry over TLS, so transport-layer integrity and authentication of the delivery channel are ensured. Container images carry SLSA Build Provenance attestations (in-toto predicate https://slsa.dev/provenance/v1) generated by docker buildx --provenance=true, binding the image digest to the CI build metadata. Release container images are signed manually by the Product Owner prior to release. Automating signing within the CI pipeline is tracked in platform#1697. |
| CT-1 Container-Hardening | Done | All container workloads apply the shared hardening baseline (runAsNonRoot: true, capabilities.drop: [ALL], readOnlyRootFilesystem: true, allowPrivilegeEscalation: false, seccompProfile: RuntimeDefault) defined in defaults/environment/security.yaml.gotmpl and referenced by every chart's securityContext/podSecurityContext. Mandated through Deployment Standards (section "Security"). |
| CT-2 Werkzeuggestützte Prüfung auf Container-Sicherheitslücken | Done | Trivy scans are used to scan container images. (implementation ticket) |
| CT-3 Basisimages aus vertrauenswürdigen Quellen | In Progress | Container base images of the images CIVITAS/CORE builds are sourced from trusted, well-established providers: eclipse-temurin (Adoptium/Eclipse Foundation) for the Java components, node (Docker Official Image) for portal-frontend, alpine (Docker Official Image) for build stages, openpolicyagent/opa (CNCF project) for the policy engine, busybox (Docker Official Image) for init containers. AuthZ repository uses SHA256 digest pinning for reproducible builds. Mandated through Deployment Standards (section "Version Management"). Gaps: the NiFi bootstrap Job and init container use badouralix/curl-jq, an image from a personal Docker Hub namespace (platform#1643); GeoServer's message broker uses bitnamilegacy/rabbitmq with image verification turned off (platform#1654); NiFi downloads the PostgreSQL JDBC driver at every start without a checksum (deployment#372). |
| CT-4 Minimalistische Basisimages | Done | Base images across the deployment use minimized Alpine variants (e.g. eclipse-temurin:25-jre-alpine, node:22-alpine) for CIVITAS-developed services, and the OPA image uses a -static (distroless) variant — all minimal compared to full OS images. Mandated through the Backend Style Guide. |
| CT-5 Geheimnisse zur Laufzeit dynamisch bereitstellen | Done | As per SSDLC (section 9) and Deployment Standards (section "Security"), secrets are stored through Kubernetes secrets or other secure means external to the application. Helm templates use secretKeyRef to inject secrets from Kubernetes Secrets at runtime. No secrets are baked into container images. CI pipeline includes secret scanning to prevent hardcoded secrets from being merged. Exception: Keycloak stores secrets (passwords, signing keys) in its database; all of them are changeable through regular Keycloak mechanisms. Accepted risk: the config-adapter values contain literal default credentials for FROST and GeoServer instead of secretKeyRef, and the APISIX FROST route injects a fixed FROST credential; both services are reachable only inside the cluster, and generated credentials are planned (deployment#246, deployment#362). |
| CT-6 Gehärtete Clusterknoten | Not Applicable | Since CIVITAS/CORE assumes it runs on an existies kubernetes cluster, this requirement is out of scope for CIVITAS/CORE. |
| CT-8 Geringstmögliche Privilegien für Container-Images | Done | All CIVITAS/CORE-developed containers run as non-root: Helm deployments set runAsUser: 1000, runAsNonRoot: true. Dockerfiles create non-root users (UID 1001). Additionally: drop: [ALL] capabilities, readOnlyRootFilesystem: true, allowPrivilegeEscalation: false. Ensured through Deployment Standards (section "Security"). |
| CT-9 Transportverschlüsselung & Integrität zwischen Pods | Done | Encryption between container instances is enforced through mandatory mTLS via the Linkerd service mesh, with defaultInboundPolicy: cluster-authenticated set cluster-wide (defaults/environment/global.yaml); the APISIX ingress edge is explicitly scoped to require an authenticated mesh identity by default. A Kyverno policy additionally flags any meshed namespace that omits an inbound policy; running that policy in Enforce rather than Audit mode is the operator's responsibility. |
| MS-3 Vermeidung fester Credentials für Managed-Service-Zugriffe | Not Applicable | Not applicable, as any possible managed service access is outside the scope of the application's design. |
| AUT-1 Einheitliche Vertrauensniveaus in der Authentifizierung | Done | Authentication is centralized through Keycloak. For Keycloak, no fallback methods (e.g. password reset through security questions) are defined. Single trust level for all users. |
| AUT-2 Multi-Faktor-Authentisierung | In Progress | CIVITAS/CORE uses Keycloak as the identity provider for all components. MFA is available through Keycloak (WebAuthn and TOTP), configured via a custom browser flow with a 2FA subflow gated by a conditional-user-configured authenticator. OTP uses HmacSHA256; WebAuthn uses ES256/RS256. Gaps: the Keycloak admin console and the master realm are reachable at idm.<domain> with password-only login; the master realm has no MFA flow (fix: the gateway blocks the admin paths, deployment!324). Clients with the password grant (ROPC) cannot use WebAuthn (deployment#236). |
| AUT-3 Dedizierte Serviceaccounts | In Progress | Dedicated ServiceAccounts are created for config-adapter and etcd (serviceAccount.create: true); external Helm operators (cloudnative-pg, strimzi, keycloakx, apisix) ship their own. Mandated through Deployment Standards (section "Security"). Gaps: FROST, AuthZ/OPA, the portal backend and frontend, the APISIX configuration Job and the NiFi bootstrap Job declare no serviceAccountName and fall back to the namespace default ServiceAccount; no workload sets automountServiceAccountToken: false (fix for the components we own: deployment!327). Tracked via Minimize ServiceAccount token exposure across CIVITAS workloads. |
| AUT-4 Kryptografisch abgesicherter Zugriff auf Infrastruktur | Done | Infrastructure access is the operator's responsibility. CIVITAS/CORE provides Keycloak with WebAuthn support (ES256/RS256) as an available cryptographic authentication mechanism. Operators can enable cryptographically secured access for administration. Direct Keycloak admin access is not a supported use case in the CIVITAS/CORE operational model. |
| AUT-5 Zugriffskontrollmodell definieren | Done | The authorization model is documented here |
| AUT-6 Sitzungs-IDs invalidieren | Done | CIVITAS/CORE uses Keycloak for authentication and Keycloak supports this out of the box. |
| AUT-7 Passwortrichtlinien | In Progress | Keycloak enforces password policies. The master/admin realm requires at least 20 characters with uppercase, lowercase, digit and special character. Authentication is centralized through Keycloak via NextAuth (OIDC), so the realm policies apply to all user-facing authentication. Gap: the instance realm requires at least 12 characters with uppercase, lowercase and digit (length(12) and upperCase(1) and lowerCase(1) and digits(1)); this meets neither alternative of AUT-7 (25 characters, or 8 characters with high complexity) (deployment#369). |
| W-1 Deny All als Default | Done | Four-layer deny-all-by-default: (1) OPA default allow := false with explicit permission-based allow rules only (main.rego). (2) APISIX bearer_only: true rejects requests without valid JWT. (3) Spring Security anyRequest().authenticated() catch-all (SecurityConfig.java). (4) NextAuth middleware denies unauthenticated access to all non-public paths. This is mandated as part of the SSDLC (Section "3. Requirements you need to fulfill"). |
| W-2 Zugriffskontrollmodell definieren | Done | The authorization model is documented in the Authorization Data Model and implemented via OPA Rego policies (permission evaluation with provider-based endpoint mapping) and Keycloak RBAC. |
| W-3 Einheitlicher Zugriffskontrollmechanismus | Done | Unified access control for backend API traffic via APISIX (API gateway) + OPA (policy engine). The /v1/* portal-backend route passes through OIDC authentication (APISIX openid-connect plugin, bearer_only: true) and OPA authorization (components/portal/apisix-plugins.yaml).Deliberate exceptions to this chain: - Portal BFF (Next.js frontend): handles authentication via NextAuth; has no own authorization layer (access control to backend APIs still goes through APISIX + OPA). - Keycloak ( idm.*): self-authenticated — it is the identity provider. |
| W-4 Restriktive CORS-Policy | Done | No CORS configuration in backend (no @CrossOrigin annotations, no CorsConfiguration beans, no wildcard origins). Backend is not directly exposed to browsers — APISIX handles API gateway concerns. Ingress-level CORS is operator responsibility (see also W-17). CORS policy mandated through Backend Style Guide (section "CORS-Policy"). |
| W-5 Web-Server-Verzeichnisauflistung deaktivieren | Done | Spring Boot embedded Tomcat disables directory listing by default; no override found. Next.js serves only explicitly referenced assets from public/. No ResourceHandlerRegistry configured. |
| W-6 Nicht benötigte Datei-Metadaten und Sicherungskopien vermeiden | Done | Dockerfiles COPY only production artifacts (JARs, .next/, public/). No backup or metadata files in deployed images. AuthZ has explicit .dockerignore. Spring Boot and Jetty do not serve static files without specific provisions. |
| W-7 Sitzungs-IDs nach Logout invalidieren | Done | Federated logout implemented: NextAuth signOut() clears the JWT session and calls Keycloak's OIDC logout endpoint with id_token_hint (auth.config.ts signOut event). Both the local NextAuth JWT and the Keycloak SSO session are terminated. Implementation details documented in Authentication Flow. |
| W-8 Kurzlebige JWTs | Done | Keycloak token lifetimes (base-values.yaml.gotmpl): access token 5 minutes (accessTokenLifespan: 300), SSO Session Idle 60 minutes, SSO Session Max 10 hours. Admin realm stricter: SSO idle 30 minutes. Frontend session maxAge 10 hours.(Implementation ticket) |
| W-9 Sichere DB-Zugriffe (ORM/Prepared) | Done | Spring Data JPA with parameterized @Query annotations used throughout. All queries use :paramName binding — no raw SQL or string concatenation detected. Enforced through Backend Style Guide (section "Security"). |
| W-10 Vom Benutzer übergebene Objekte defensiv deserialisieren | Done | Jackson ObjectMapper is used without dangerous polymorphic typing (enableDefaultTyping()/activateDefaultTyping() are not used). Custom deserializers (e.g. UpstreamNodes.NodesDeserializer) validate JSON token types explicitly before deserializing. No XXE-vulnerable XML parsing. Mandated through the Backend Style Guide (section "Security"). |
| W-11 Clientseitige Eingabevalidierung | Done | Client-side validation implemented through React Hook Form with Zod schemas. Forms enforce email validation, length constraints (min/max), regex patterns, and required fields. Examples: UserInputDTO uses z.email(), z.string().min(2), phone validation with regex; DatasourceInputDTO uses z.string().trim().max(150). Mandated through Frontend Style Guide (section "Security Principles"). |
| W-12 Serverseitige Eingabevalidierung | Done | Server-side validation is enforced via Jakarta Bean Validation: @Validated on BaseController, @Valid @RequestBody on create/update endpoints, with constraint annotations (@NotBlank, @Email, @Size, slug patterns) on input DTOs such as DataSetInputDTO, DataSourceInputDTO, and NamedApiInputDTO. Mandated through the Backend Style Guide (section "Security"). |
| W-13 Limitierung Anzahl Ergebnisse Datenbankabfragen | Done | Pagination is enforced via @PageableDefault(size = 20, sort = "createdAt", direction = Sort.Direction.DESC) on BaseReadOnlyController.getAll(). BaseController extends BaseReadOnlyController, so every controller in the hierarchy inherits the paginated list endpoint; the concrete controllers that override getAll() re-declare the same defaults. List endpoints therefore return no unbounded result sets. |
| W-14 Standardzugangsdaten ändern | In Progress | Authentication is centralized through Keycloak. The Keycloak admin password is injected from a Kubernetes Secret (keycloak-admin-user) generated by the secrets component at installation time. Gaps: GeoServer runs on its factory-default administrator account (deployment#362), and FROST uses fixed default credentials (deployment#246). |
| W-15 Whitelisting für Eingabedaten | Done | Jackson ObjectMapper does not enable polymorphic default typing (enableDefaultTyping()/activateDefaultTyping() are not used), so JSON deserialization will not instantiate arbitrary classes from client-supplied type hints. Custom deserializers (e.g. UpstreamNodes.NodesDeserializer) validate JSON token types explicitly before deserializing. No XXE-vulnerable XML parsing. Mandated through the Backend Style Guide (section "Security"). See also W-10. |
| W-16 Whitelisting für URLs in Eingabedaten | In Progress | URL inputs are not restricted by an explicit scheme/host/port allowlist; MQTT connector and SQL connector DSN fields accept any syntactically valid URL. Tracked as a security finding via platform#1865. |
| W-17 Whitelisting für Allow-Origin | Not Applicable | Should be done at ingress and is thus responsibility of the operator. We recommend the following CORS settings: * Access-Control-Allow-Origin allows only specific domains, not . Access-Control-Allow-Methods contains only the HTTP verbs required by the use case (e.g. GET, POST). * Access-Control-Allow-Headers is defined explicitly, not through wildcards. * Access-Control-Allow-Credentials is only set when required by the use base. |
| W-18 Detaillierte Fehlermeldungen Produktivsystemen vermeiden | Done | This is mandated as part of the SSDLC (Section "3. Requirements you need to fulfill") |
| W-19 Minimale Auskunft über Komponenten | In Progress | Actuator endpoints are restricted to health, info, and prometheus (portal-backend/src/main/resources/application.yaml). No server header configured (Spring Boot default: no disclosure). Stack traces are not exposed (include-stacktrace: never in application.yaml; on_param appears only in the local development profile). AuthZ health endpoint: show-details: never. Mandated as part of SSDLC policy (section 3). Gap: the frontend does not disable the Next.js X-Powered-By response header — poweredByHeader: false is absent from portal-frontend/next.config.ts and the middleware does not remove it — so responses name the framework, though not its version; tracked via platform#1091. |
| W-20 Nicht benötigte HTTP-Verben deaktivieren | Done | HTTP verbs are restricted per endpoint at the OPA authorization layer: each backend path enumerates its allowed methods in the endpoint-mapping data (authz/rego/data/backends/portal_backend/data.json), and any method not listed for a path is denied as unknown_endpoint (fail-secure default-deny, authz/rego/policy/main.rego). Independently, the portal backend's Spring MVC controllers map only the verbs each endpoint uses, so any unmapped verb returns 405 by framework default. |
| W-21 Keine sensiblen Daten in URLs | Done | Tokens transmitted exclusively in Authorization headers (Bearer), never in URLs. Refresh tokens sent via POST body (application/x-www-form-urlencoded). Frontend proxy explicitly strips cookies from forwarded requests. API path parameters used only for UUIDs, never for tokens or passwords. This is mandated as part of the SSDLC (Section "3. Requirements you need to fulfill"). |
| W-22 Clientseitiges Caching und Autocomplete abschalten | In Progress | API requests from the portal-frontend carry Cache-Control: no-store, and credential inputs in the connector configuration (MQTT and SQL password fields) render with type="password" and autoComplete="new-password", suppressing browser autofill and credential storage. Mandated in the Frontend Style Guide (section "Security Principles"). Gap: authenticated HTML pages (platform#1668) and backend API responses (platform#1666) do not consistently set Cache-Control: no-store. |
| W-23 HSTS aktivieren | Not Applicable | This needs to be set at the egress and is thus responsibility of the Operator. |
| IoT-1 Zugang allen IoT-Ressourcen standardmäßig verweigern | Not Applicable | Not applicable, as there is no direct traffic from IoT devices to CIVITAS/CORE. |
| IoT-2 Einheitlicher Zugriffskontrollmechanismus | Not Applicable | Not applicable, as there is no direct traffic from IoT devices to CIVITAS/CORE. h Security Architecture Principles |
| IoT-3 Authentifizierung und Autorisierung für IoT-Geräte | Not Applicable | Not applicable, as there is no direct traffic from IoT devices to CIVITAS/CORE. |
| IoT-4 Standardzugangsdaten ändern | Not Applicable | Not applicable, as there is no direct traffic from IoT devices to CIVITAS/CORE. |
| IoT-5 Device Onboarding | Not Applicable | Not applicable, as there is no direct traffic from IoT devices to CIVITAS/CORE. |
| IoT-6 Generierung von Netzschlüsseln nach Zufallsprinzip | Not Applicable | Not applicable, as there is no direct traffic from IoT devices to CIVITAS/CORE. h Security Architecture Principles |
| IoT-7 Erneuerung Netzschlüssel | Not Applicable | Not applicable, as there is no direct traffic from IoT devices to CIVITAS/CORE. |
| IoT-8 Replay-Schutz | Not Applicable | Not applicable, as there is no direct traffic from IoT devices to CIVITAS/CORE. |
| IoT-9 Eindeutige Identifikation von IoT-Geräten | Not Applicable | Not applicable, as there is no direct traffic from IoT devices to CIVITAS/CORE. |
| VPN-1 VPN-Verschlüsselungsverfahren gemäß TR-02101-1 | Not Applicable | Not applicable, as VPN is assumed to be handled by the Operator at an infrastructure level. Document in a suitable "Product Scope" type document. |
| VPN-2 Sichere Konfiguration VPN-Gateway | Not Applicable | Not applicable, as VPN is assumed to be handled by the Operator at an infrastructure level. Document in a suitable "Product Scope" type document. |
| L-1 Logging von Sicherheitsereignissen | Done | Security event logging implemented across components: OPA decision logs enabled (JSON, console) with sensitive header masking via mask.rego. Keycloak logs 68 event types (LOGIN, LOGIN_ERROR, admin events with details, account changes, token events, MFA events; 30-day retention). Backend SecurityExceptionHandler logs auth failures (401), access denied (403), and JWT validation failures at WARN level. Config-adapter logs connector lifecycle events. Frontend logs proxy auth events via Pino (JSON in production). See concept ticket. |
| L-2 Logging von fehlgeschlagenen Authentisierungsvorgängen und allen Authorisierungvorgängen | Done | Failed authentication logged by: Keycloak LOGIN_ERROR + all *_ERROR event types (68 event types configured). Backend SecurityExceptionHandler catches 401/403 at WARN level. OPA decision logs capture authorization denials with reason codes (default_deny, permission_denied, unknown_endpoint). OPA mask.rego redacts tokens, authorization headers, and userinfo from decision logs. See concept ticket. |
| L-3 Schutz von Logs vor unbefugtem Zugriff | Done | All CIVITAS/CORE components write logs exclusively to stdout without storing them. OPA decision logs mask sensitive headers (tokens, authorization, userinfo) via mask.rego. Keycloak stores events in its database with 30-day retention; that database is only accessible by the Keycloak service. Log storage, integrity, and access control are the operator's responsibility; the hardening guide documents minimum expectations. |
| L-4 Sensible Informationen nicht loggen | In Progress | Sensitive data masking is implemented across components: OWASP Encode (Encode.forJava()) wraps log arguments in portal-backend (including the security exception handler), config-adapter, and the authz repository (UserContextService with UUID masking); maskSensitiveFields() masks connector secrets; the frontend logs only an allow-list of request headers; OPA mask.rego masks tokens, authorization headers, and userinfo in decision logs. Mandated by the Backend Style Guide (section "Logging"). Gaps: the frontend logger (pino) has no redaction configured, and Zod validation errors log the rejected payload; the portal backend logs user e-mail addresses at startup (fixes: platform!897, platform!898). OPA decision logs do not mask the cookie header (platform!890). The AuthZ decision log emits the full subject UUID unmasked (platform#1672); the frontend logs a raw Keycloak error JSON (platform#1669) and has console.* calls that bypass the logger (platform#1670). |
| DH-1 Verschlüsselung At-Rest | Not Applicable | Not applicable, as this is considered the responsibility of the operator. CIVITAS/CORE assumes encryption to be handled transparently at a volume level. |
| DH-2 Schutz geheimer Schlüssel | Done | As per SSDLC (section 9) and Deployment Standards (Section "Security"), secrets are stored through Kubernetes secrets or other secure means external to the application. Exception: Keycloak stores secrets (passwords, signing keys) in database. |
| DH-3 IPC-Schnittstellen müssen geschützt sein | In Progress | Internal service-to-service (IPC) calls are protected at the transport layer by mandatory Linkerd mTLS (defaultInboundPolicy: cluster-authenticated) and per-component NetworkPolicies; endpoints such as the AuthZ Repository's user-context endpoint rely on this mesh-and-network-policy boundary as the intended control. Gap: the Kafka message bus has no topic-level authorization — mTLS authenticates connections and NetworkPolicies gate reachability, but any pod able to reach the broker can produce to or consume from any topic. Tracked via platform#1294, platform#1637, and deployment#245. |
| DH-4 Data Governance-Vorgaben etablieren | Done | CIVITAS/CORE provides features through which organizations can implement data governance: * Fine-grained permission management * Separation of Data Sources and Data Sets * Data Sets, Data Sources, and Data Models can carry custom metadata tags * Data provenance tracking via Data Source as a first-class entity |
| ORG-6 Schwachstellenmanagement | Done | Vulnerability management follows the process agreed in platform#1473. Roles: the Security Architect triages findings, rates them by their impact on the product and hands them to the teams as prepared merge requests or tickets; the Security Engineer owns the automated guardrails; the Product Owner plans the work, accepts risks and decides in an emergency. Known vulnerabilities are found by nightly scans (GitLab SAST, Dependency Scanning, Secret Detection, Trivy container scanning) on security dashboards for the development and the release state; Renovate proposes dependency updates. The process sets internal targets by severity, an emergency path for vulnerabilities that are actively exploited, and a release gate on main. Accepted risks are recorded as dismissals with a reason in the GitLab Security Dashboard; accepting a High or Critical risk needs the Product Owner's approval. Third parties report vulnerabilities as described in Reporting Vulnerabilities. Not yet in place: the release-gate job and the enrichment with CISA KEV and EPSS data. |
| ORG-7 Tests nur auf dedizierter Umgebung | Not Applicable | Neither Civitas Connect nor any of the members of the project team provide a production environment. Therefore this requirement is not applicable. All tests are run on dedicated environments with synthetic data. The requirement for synthetic data is part of the Security Architecture Principles. |
| ORG-8 Penetrationstests | Done | A penetration test of CIVITAS/CORE v2 (version 2.0-beta) was carried out by an external provider. Its findings go through the vulnerability management process (see ORG-6). |
Generated on 01 October 2026, 06:49 UTC from https://gitlab.com/civitas-connect/civitas-core/requirements (labels: requirement-source::tr-03187, tr-level::1).