diff --git a/.gitignore b/.gitignore index 4ae22a7..2be9b02 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,4 @@ out/ ### VS Code ### .vscode/ /dev/gpas* +/deploy/.env diff --git a/README.md b/README.md index 12acf52..908b1de 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Siehe hierzu auch: https://github.com/CCC-MF/kafka-to-bwhc ## Pseudonymisierung der Patienten-ID -Wenn eine URI zu einer gPAS-Instanz angegeben ist, wird diese verwendet. +Wenn eine URI zu einer gPAS-Instanz (Version >= 2023.1.0) angegeben ist, wird diese verwendet. Ist diese nicht gesetzt. wird intern eine Anonymisierung der Patienten-ID vorgenommen. * `APP_PSEUDONYMIZE_PREFIX`: Standortbezogenes Prefix - `UNKNOWN`, wenn nicht gesetzt @@ -42,7 +42,8 @@ als Patienten-Pseudonym verwendet. Wurde die Verwendung von gPAS konfiguriert, so sind weitere Angaben zu konfigurieren. -* `APP_PSEUDONYMIZE_GPAS_URI`: URI der gPAS-Instanz inklusive Endpoint (z.B. `http://localhost:8080/ttp-fhir/fhir/gpas/$pseudonymizeAllowCreate`) +* `APP_PSEUDONYMIZE_GPAS_URI`: URI der gPAS-Instanz inklusive Endpoint ( + z.B. `http://localhost:8080/ttp-fhir/fhir/gpas/$$pseudonymizeAllowCreate`) * `APP_PSEUDONYMIZE_GPAS_TARGET`: gPas Domänenname * `APP_PSEUDONYMIZE_GPAS_USERNAME`: gPas Basic-Auth Benutzername * `APP_PSEUDONYMIZE_GPAS_PASSWORD`: gPas Basic-Auth Passwort @@ -120,6 +121,25 @@ ein Consent-Widerspruch erfolgte. Diese Anwendung ist auch als Docker-Image verfügbar: https://github.com/CCC-MF/etl-processor/pkgs/container/etl-processor +### Images lokal bauen + +```bash +./gradlew bootBuildImage +``` + +## Deployment +*Ausführen als Docker Conatiner:* + +```bash +cd ./deploy +cp env-sample.env .env +``` +Wenn gewünscht, Änderungen in der `.env` vornehmen. + +```bash +docker compose up -d +``` + ## Entwicklungssetup Zum Starten einer lokalen Entwicklungs- und Testumgebung kann die beiliegende Datei `dev-compose.yml` verwendet werden. diff --git a/build.gradle.kts b/build.gradle.kts index c074b3b..36b7b29 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -60,6 +60,8 @@ dependencies { implementation("org.springframework.boot:spring-boot-starter-data-jdbc") implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation("org.springframework.kafka:spring-kafka") + // fix CVE-2022-1471 + implementation("org.yaml:snakeyaml:2.1") implementation("org.flywaydb:flyway-mysql") implementation("commons-codec:commons-codec") implementation("io.projectreactor.kotlin:reactor-kotlin-extensions") diff --git a/deploy/docker-compose.yaml b/deploy/docker-compose.yaml new file mode 100644 index 0000000..d575d09 --- /dev/null +++ b/deploy/docker-compose.yaml @@ -0,0 +1,55 @@ + + +services: + dnpm-etl-processor: + image: ghcr.io/ccc-mf/etl-processor:latest + environment: + LOGGING_LEVEL_DEV: ${DNPM_LOG_LEVEL:-INFO} + SPRING_KAFKA_SECURITY_PROTOCOL: ${DNPM_KAFKA_SECURITY_PROTOCOL:-SSL} + SPRING_KAFKA_SSL_TRUST-STORE-TYPE: PKCS12 + SPRING_KAFKA_SSL_TRUST-STORE-LOCATION: /opt/dnpm-processor/ssl/truststore.jks + SPRING_KAFKA_SSL_TRUST-STORE-PASSWORD: ${KAFKA_TRUST_STORE_PASSWORD} + SPRING_KAFKA_SSL_KEY-STORE-TYPE: PKCS12 + SPRING_KAFKA_SSL_KEY-STORE-LOCATION: /opt/dnpm-processor/ssl/keystore.jks + SPRING_KAFKA_SSL_KEY-STORE-PASSWORD: ${DNPM_PROCESSOR_KEY_STORE_PASSWORD} + SPRING_KAFKA_PRODUCER_COMPRESSION-TYPE: gzip + APP_KAFKA_TOPIC: ${DNPM_KAFKA_TOPIC} + APP_KAFKA_SERVERS: ${KAFKA_BROKERS} + APP_KAFKA_GROUP_ID: ${DNPM_KAFKA_GROUP_ID} + APP_KAFKA_RESPONSE_TOPIC: ${DNPM_KAFKA_RESPONSE_TOPIC} + APP_REST_URI: ${DNPM_BWHC_REST_URI} + SPRING_DATASOURCE_URL: ${DNPM_DATASOURCE_URL} + SPRING_DATASOURCE_PASSWORD: ${DNPM_MARIADB_USER_PW} + SPRING_DATASOURCE_USERNAME: ${DNPM_MARIADB_DB} + APP_PSEUDONYMIZE_GPAS_SSLCALOCATION: /workspace/opt/dnpm-processor/ssl/mosaic.crt + APP_PSEUDONYMIZE_GPAS_PASSWORD: ${DNPM_PSEUDONYMIZE_GPAS_PASSWORD} + APP_PSEUDONYMIZE_GPAS_USERNAME: ${DNPM_PSEUDONYMIZE_GPAS_USERNAME} + APP_PSEUDONYMIZE_GPAS_TARGET: ${DNPM_PSEUDONYMIZE_GPAS_TARGET} + APP_PSEUDONYMIZE_GPAS_URI: ${DNPM_PSEUDONYMIZE_GPAS_URI} + APP_PSEUDONYMIZE_PREFIX: ${DNPM_APP_PSEUDONYMIZE_PREFIX} + APP_PSEUDONYMIZER: ${DNPM_PSEUDONYMIZE_GENERATOR} + volumes: + - /etc/localtime:/etc/localtime:ro + - /etc/timezone:/etc/timezone:ro + #- ${DNPM_TO_SSL_KEYSTORE_LOCATION}:/workspace/opt/dnpm-processor/ssl/keystore.jks:ro + #- ${KAFKA_TRUST_STORE_LOCATION}:/workspace/opt/dnpm-processor/ssl/truststore.jks:ro + #- ${DNPM_PSEUDONYMIZE_GPAS_SSLCALOCATION}:/workspace/opt/dnpm-processor/ssl/mosaic.crt + + depends_on: + - dnpm-monitor-db + ports: + - "${DNPM_MONITORING_HTTP_PORT:-8080}:8080" + + # todo add volume + dnpm-monitor-db: + image: mariadb:10 + environment: + MARIADB_DATABASE: ${DNPM_MARIADB_DB} + MARIADB_USER: ${DNPM_MARIADB_USER} + MARIADB_PASSWORD: ${DNPM_MARIADB_USER_PW} + MARIADB_ROOT_PASSWORD: ${DNPM_MARIADB_ROOT_PW} + expose: + - "3306" + + + diff --git a/deploy/env-sample.env b/deploy/env-sample.env new file mode 100644 index 0000000..998400a --- /dev/null +++ b/deploy/env-sample.env @@ -0,0 +1,40 @@ +# monitoring access port +DNPM_MONITORING_HTTP_PORT=8088 +DNPM_LOG_LEVEL=INFO + +# GPAS or BUILDIN +DNPM_PSEUDONYMIZE_GENERATOR=BUILDIN +DNPM_APP_PSEUDONYMIZE_PREFIX=ANONYM +DNPM_PSEUDONYMIZE_GPAS_URI= +DNPM_PSEUDONYMIZE_GPAS_TARGET= +DNPM_PSEUDONYMIZE_GPAS_USERNAME= +DNPM_PSEUDONYMIZE_GPAS_PASSWORD= + +# path to ca root cert if needed +DNPM_PSEUDONYMIZE_GPAS_SSLCALOCATION= + +DNPM_MARIADB_DB=dnpm_monitoring +DNPM_MARIADB_USER=$DNPM_MARIADB_DB +DNPM_MARIADB_USER_PW=MySuperSecurePassword111 +DNPM_MARIADB_ROOT_PW=MySuperDuperSecurePassword111 + +# monitoring data db +DNPM_DATASOURCE_URL=jdbc:mariadb://dnpm-monitor-db:3306/$DNPM_MARIADB_DB + +## TARGET SYSTEMS CONFIG +# in case of direct access to bwhc enter endpoint url here +DNPM_BWHC_REST_URI= + +# produce mtb files to this topic - values 'false' disabling kafka processing +DNPM_KAFKA_TOPIC=false +KAFKA_BROKERS=false +DNPM_KAFKA_SECURITY_PROTOCOL=PLAINTEXT + +# here we receive responses from bwhc +DNPM_KAFKA_RESPONSE_TOPIC=dnpm-response +DNPM_KAFKA_GROUP_ID=dnpm + +# SSL or PLAINTEXT +DNPM_PROCESSOR_KEY_STORE_PASSWORD= +DNPM_TO_SSL_KEYSTORE_LOCATION= + diff --git a/dev-compose.yml b/dev-compose.yml index ee84d7c..08db2e3 100644 --- a/dev-compose.yml +++ b/dev-compose.yml @@ -1,5 +1,4 @@ services: - # Note: Make sure, hostname "kafka" points to 127.0.0.1 # otherwise connection will not be available kafka: @@ -20,6 +19,21 @@ services: KAFKA_CFG_CONTROLLER_QUORUM_VOTERS: 0@kafka:9093 KAFKA_CFG_CONTROLLER_LISTENER_NAMES: CONTROLLER + akhq: + image: tchiotludo/akhq:0.21.0 + environment: + AKHQ_CONFIGURATION: | + akhq: + connections: + docker-kafka-server: + properties: + bootstrap.servers: "kafka:9092" + connect: + - name: "kafka-connect" + url: "http://kafka-connect:8083" + ports: + - "8084:8080" + mariadb: image: mariadb:10 ports: @@ -37,4 +51,4 @@ services: # environment: # POSTGRES_DB: dev # POSTGRES_USER: dev -# POSTGRES_PASSWORD: dev +# POSTGRES_PASSWORD: dev \ No newline at end of file diff --git a/src/main/java/dev/dnpm/etl/processor/pseudonym/GpasPseudonymGenerator.java b/src/main/java/dev/dnpm/etl/processor/pseudonym/GpasPseudonymGenerator.java index 91e465b..7b631d8 100644 --- a/src/main/java/dev/dnpm/etl/processor/pseudonym/GpasPseudonymGenerator.java +++ b/src/main/java/dev/dnpm/etl/processor/pseudonym/GpasPseudonymGenerator.java @@ -22,6 +22,21 @@ package dev.dnpm.etl.processor.pseudonym; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.parser.IParser; import dev.dnpm.etl.processor.config.GPasConfigProperties; +import java.io.BufferedInputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.net.ConnectException; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Base64; +import java.util.HashMap; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; import org.apache.commons.lang3.StringUtils; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.HttpClients; @@ -39,7 +54,11 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.http.*; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.retry.RetryCallback; import org.springframework.retry.RetryContext; @@ -51,22 +70,6 @@ import org.springframework.retry.support.RetryTemplate; import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestTemplate; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManagerFactory; -import java.io.BufferedInputStream; -import java.io.FileInputStream; -import java.io.IOException; -import java.net.ConnectException; -import java.security.KeyManagementException; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; -import java.util.Base64; -import java.util.HashMap; - public class GpasPseudonymGenerator implements Generator { private final static FhirContext r4Context = FhirContext.forR4(); @@ -88,12 +91,16 @@ public class GpasPseudonymGenerator implements Generator { try { if (StringUtils.isNotBlank(gpasCfg.getSslCaLocation())) { customSslContext = getSslContext(gpasCfg.getSslCaLocation()); + log.debug(String.format("%s has been initialized with SSL certificate %s", + this.getClass().getName(), gpasCfg.getSslCaLocation())); } } catch (IOException | KeyManagementException | KeyStoreException | CertificateException | NoSuchAlgorithmException e) { throw new RuntimeException(e); } + log.debug(String.format("%s has been initialized", this.getClass().getName())); + } @Override @@ -115,9 +122,9 @@ public class GpasPseudonymGenerator implements Generator { } final var identifier = (Identifier) parameters.get().getPart().stream() - .filter(a -> a.getName().equals("pseudonym")) - .findFirst() - .orElseGet(ParametersParameterComponent::new).getValue(); + .filter(a -> a.getName().equals("pseudonym")) + .findFirst() + .orElseGet(ParametersParameterComponent::new).getValue(); // pseudonym return identifier.getSystem() + "|" + identifier.getValue();