From c8eca6b95bad41a25d1523e235e2c323b5c2c10f Mon Sep 17 00:00:00 2001 From: Ole Lilienthal Date: Wed, 27 May 2020 00:15:34 +0200 Subject: [PATCH 01/18] Introduce new version 0.5.1-SNAPSHOT (#330) * Introduce new Version 0.5.0 * Creating new 0.5.1-SNAPSHOT --- common/persistence/pom.xml | 4 ++-- common/pom.xml | 4 ++-- common/protocols/pom.xml | 4 ++-- pom.xml | 2 +- services/distribution/pom.xml | 4 ++-- services/pom.xml | 6 +++--- services/submission/pom.xml | 4 ++-- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/common/persistence/pom.xml b/common/persistence/pom.xml index 02ad0514ae..fc30b59be5 100644 --- a/common/persistence/pom.xml +++ b/common/persistence/pom.xml @@ -12,7 +12,7 @@ org.opencwa persistence - 0.4.1-SNAPSHOT + 0.5.1-SNAPSHOT 11 @@ -27,7 +27,7 @@ org.opencwa protocols - 0.4.1-SNAPSHOT + 0.5.1-SNAPSHOT org.springframework.boot diff --git a/common/pom.xml b/common/pom.xml index 4a461d1dab..4f0686b675 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -6,7 +6,7 @@ server org.opencwa - 0.4.1-SNAPSHOT + 0.5.1-SNAPSHOT ../pom.xml @@ -17,7 +17,7 @@ 4.0.0 common - 0.4.1-SNAPSHOT + 0.5.1-SNAPSHOT pom diff --git a/common/protocols/pom.xml b/common/protocols/pom.xml index a6f52859a7..7bbedc904b 100644 --- a/common/protocols/pom.xml +++ b/common/protocols/pom.xml @@ -5,13 +5,13 @@ common org.opencwa - 0.4.1-SNAPSHOT + 0.5.1-SNAPSHOT ../pom.xml 4.0.0 protocols - 0.4.1-SNAPSHOT + 0.5.1-SNAPSHOT corona-warn-app_cwa-server_common_protocols diff --git a/pom.xml b/pom.xml index 92669f9efd..aae5277c8c 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ org.opencwa server - 0.4.1-SNAPSHOT + 0.5.1-SNAPSHOT server CWA Server https://www.coronawarn.app/ diff --git a/services/distribution/pom.xml b/services/distribution/pom.xml index 2da8e72339..dc40e5fb56 100644 --- a/services/distribution/pom.xml +++ b/services/distribution/pom.xml @@ -5,7 +5,7 @@ services org.opencwa - 0.4.1-SNAPSHOT + 0.5.1-SNAPSHOT ../pom.xml 4.0.0 @@ -52,7 +52,7 @@ distribution - 0.4.1-SNAPSHOT + 0.5.1-SNAPSHOT io.minio diff --git a/services/pom.xml b/services/pom.xml index 80b10ba422..92252ba62a 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -14,7 +14,7 @@ services org.opencwa - 0.4.1-SNAPSHOT + 0.5.1-SNAPSHOT pom @@ -35,12 +35,12 @@ org.opencwa persistence - 0.4.1-SNAPSHOT + 0.5.1-SNAPSHOT org.opencwa protocols - 0.4.1-SNAPSHOT + 0.5.1-SNAPSHOT org.springframework.boot diff --git a/services/submission/pom.xml b/services/submission/pom.xml index 1d8392814b..d9807d3613 100644 --- a/services/submission/pom.xml +++ b/services/submission/pom.xml @@ -5,13 +5,13 @@ services org.opencwa - 0.4.1-SNAPSHOT + 0.5.1-SNAPSHOT ../pom.xml 4.0.0 submission - 0.4.1-SNAPSHOT + 0.5.1-SNAPSHOT corona-warn-app_cwa-server_services_submission From f829f5d7e15c851580feffa37a4d37bc49feaf08 Mon Sep 17 00:00:00 2001 From: Ole Lilienthal Date: Wed, 27 May 2020 00:20:54 +0200 Subject: [PATCH 02/18] Adjusting sonar to skip tests (#331) --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 522683a5a6..2f319161ee 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -32,7 +32,7 @@ jobs: path: ~/test-results/junit - run: name: Analyze on SonarCloud - command: mvn --batch-mode verify sonar:sonar --fail-never + command: mvn --batch-mode verify -DskipTests sonar:sonar --fail-never workflows: main: From fbe5d23fa62062b54388776ef93e80f1dfe4151d Mon Sep 17 00:00:00 2001 From: Ole Lilienthal Date: Wed, 27 May 2020 00:26:19 +0200 Subject: [PATCH 03/18] Moving sonar target up to cache dependencies (#332) --- .circleci/config.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 2f319161ee..73719b0593 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -15,6 +15,9 @@ jobs: - restore_cache: key: cwa-server-{{ checksum "~/pom-checksum" }} - run: mvn --batch-mode dependency:go-offline + - run: + name: Analyze on SonarCloud + command: mvn --batch-mode verify -DskipTests sonar:sonar --fail-never - save_cache: paths: - ~/.m2 @@ -30,9 +33,6 @@ jobs: path: ~/test-results - store_artifacts: path: ~/test-results/junit - - run: - name: Analyze on SonarCloud - command: mvn --batch-mode verify -DskipTests sonar:sonar --fail-never workflows: main: From cfd5dde58582b6f49b8cca134b6f499a19eb93ba Mon Sep 17 00:00:00 2001 From: Ole Lilienthal Date: Wed, 27 May 2020 09:50:12 +0200 Subject: [PATCH 04/18] Revert f829f5d7 due to sonar test coverage feature (#335) --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 73719b0593..ae131f7cda 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -17,7 +17,7 @@ jobs: - run: mvn --batch-mode dependency:go-offline - run: name: Analyze on SonarCloud - command: mvn --batch-mode verify -DskipTests sonar:sonar --fail-never + command: mvn --batch-mode verify sonar:sonar --fail-never - save_cache: paths: - ~/.m2 From ed8d5b007798193e28abaef4ad445b262cf8133f Mon Sep 17 00:00:00 2001 From: Pit Humke Date: Wed, 27 May 2020 10:20:51 +0200 Subject: [PATCH 05/18] Enable SSL in cloud profile (#333) --- .../submission/src/main/resources/application-cloud.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/services/submission/src/main/resources/application-cloud.yaml b/services/submission/src/main/resources/application-cloud.yaml index 00eaeb11b6..7878484f92 100644 --- a/services/submission/src/main/resources/application-cloud.yaml +++ b/services/submission/src/main/resources/application-cloud.yaml @@ -12,3 +12,12 @@ services: submission: verification: base-url: ${VERIFICATION_BASE_URL} + +server: + ssl: + enabled: true + key-password: ${SSL_PASSWORD} + key-store: ${SSL_KEYSTORE_PATH} + key-store-password: ${SSL_PASSWORD} + key-store-provider: SUN + key-store-type: JKS \ No newline at end of file From 63dda0c2a5e1ff09fe998dd05637d76dea2e3833 Mon Sep 17 00:00:00 2001 From: Christopher Fenner <26137398+CCFenner@users.noreply.github.com> Date: Wed, 27 May 2020 10:28:22 +0200 Subject: [PATCH 06/18] fix remaining markdown issues (#328) --- README.md | 1 + SECURITY.md | 14 +++++++++----- codestyle/.markdownlint.yml | 1 - scripts/wait-for-it/README.md | 12 ++++++------ 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 5ce13a512b..1a8d8856bc 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +

Corona-Warn-App Server

diff --git a/SECURITY.md b/SECURITY.md index b150b8786d..deadf034d0 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,16 +1,20 @@ -# Reporting Security Vulnerabilities +# Security Vulnerabilities -The Corona-Warn-App is built with security and data privacy in mind to ensure your data is safe. We are grateful for security researchers and users reporting a vulnerability to us, first. To ensure that your request is handled in a timely manner and non-disclosure of vulnerabilities can be assured, please follow the below guideline. +The Corona-Warn-App is built with security and data privacy in mind to ensure your data is safe. + +## Reporting + +We are grateful for security researchers and users reporting a vulnerability to us, first. To ensure that your request is handled in a timely manner and non-disclosure of vulnerabilities can be assured, please follow the below guideline. **Please do not report security vulnerabilities directly on GitHub. GitHub Issues can be publicly seen and therefore would result in a direct disclosure.** * Please address questions about data privacy, security concepts, and other media requests to the corona-warn-app.opensource@sap.com mailbox. -* For reporting a vulnerability, please use the Vulnerability Report Form for Security Researchers on [SAP Trust Center](https://www.sap.com/about/trust-center/incident-management.html). +* For reporting a vulnerability, please use the Vulnerability Report Form for Security Researchers on [SAP Trust Center](https://www.sap.com/about/trust-center/incident-management.html). * Please select "Corona-Warn-App" in the _product_ list. * In the _versions_ field, either note the specific [release version](https://github.com/corona-warn-app/cwa-server/releases) or commit id of the master branch you investigated. - * The affected repository should be mentioned in the _vulnerability description_. + * The affected repository should be mentioned in the _vulnerability description_. * Please use this channel only for reporting vulnerabilities of the _cwa-server_ component and check the security of the respective repositories for other components. -# Disclosure Handling +## Disclosure Handling SAP is committed to timely review and respond to your request. The resolution of code defects will be handled by a dedicated group of security experts and prepared in a private GitHub repository. The project will inform the public about resolved security vulnerabilities. For more information on the disclosure guidelines, please consult [SAP security information page](https://www.sap.com/about/trust-center/security/incident-management.html). diff --git a/codestyle/.markdownlint.yml b/codestyle/.markdownlint.yml index e07d0d4ebe..3df53cf68d 100644 --- a/codestyle/.markdownlint.yml +++ b/codestyle/.markdownlint.yml @@ -2,4 +2,3 @@ default: true MD013: false #https://github.com/DavidAnson/markdownlint/blob/master/doc/Rules.md#md013 MD033: false #https://github.com/DavidAnson/markdownlint/blob/master/doc/Rules.md#md033 -MD034: false #https://github.com/DavidAnson/markdownlint/blob/master/doc/Rules.md#md034 diff --git a/scripts/wait-for-it/README.md b/scripts/wait-for-it/README.md index 4891736fd0..7bd323a636 100644 --- a/scripts/wait-for-it/README.md +++ b/scripts/wait-for-it/README.md @@ -1,10 +1,10 @@ -## wait-for-it +# wait-for-it `wait-for-it.sh` is a pure bash script that will wait on the availability of a host and TCP port. It is useful for synchronizing the spin-up of interdependent services, such as linked docker containers. Since it is a pure bash script, it does not have any external dependencies. ## Usage -``` +```sh wait-for-it.sh host:port [-s] [-t timeout] [-- command args] -h HOST | --host=HOST Host or IP under test -p PORT | --port=PORT TCP port under test @@ -20,7 +20,7 @@ wait-for-it.sh host:port [-s] [-t timeout] [-- command args] For example, let's test to see if we can access port 80 on www.google.com, and if it is available, echo the message `google is up`. -``` +```sh $ ./wait-for-it.sh www.google.com:80 -- echo "google is up" wait-for-it.sh: waiting 15 seconds for www.google.com:80 wait-for-it.sh: www.google.com:80 is available after 0 seconds @@ -29,7 +29,7 @@ google is up You can set your own timeout with the `-t` or `--timeout=` option. Setting the timeout value to 0 will disable the timeout: -``` +```sh $ ./wait-for-it.sh -t 0 www.google.com:80 -- echo "google is up" wait-for-it.sh: waiting for www.google.com:80 without a timeout wait-for-it.sh: www.google.com:80 is available after 0 seconds @@ -38,7 +38,7 @@ google is up The subcommand will be executed regardless if the service is up or not. If you wish to execute the subcommand only if the service is up, add the `--strict` argument. In this example, we will test port 81 on www.google.com which will fail: -``` +```sh $ ./wait-for-it.sh www.google.com:81 --timeout=1 --strict -- echo "google is up" wait-for-it.sh: waiting 1 seconds for www.google.com:81 wait-for-it.sh: timeout occurred after waiting 1 seconds for www.google.com:81 @@ -47,7 +47,7 @@ wait-for-it.sh: strict mode, refusing to execute subprocess If you don't want to execute a subcommand, leave off the `--` argument. This way, you can test the exit condition of `wait-for-it.sh` in your own scripts, and determine how to proceed: -``` +```sh $ ./wait-for-it.sh www.google.com:80 wait-for-it.sh: waiting 15 seconds for www.google.com:80 wait-for-it.sh: www.google.com:80 is available after 0 seconds From a86baa06c273f3a6460175d936495311abe03d92 Mon Sep 17 00:00:00 2001 From: Christopher Fenner <26137398+CCFenner@users.noreply.github.com> Date: Wed, 27 May 2020 11:37:22 +0200 Subject: [PATCH 07/18] reduce version definitions in POMs (rework) (#303) * flatten pom files * add flattening to services * add flatten plugin everywhere * remove as inherited from parent * add MojoHaus Flatten Maven Plugin license * update version --- .gitignore | 4 +++- .mvn/maven.config | 1 + THIRD-PARTY-NOTICES | 9 +++++++-- common/persistence/pom.xml | 26 ++++++++++++++++++++++++-- common/pom.xml | 3 +-- common/protocols/pom.xml | 5 ++--- pom.xml | 24 +++++++++++++++++++++++- services/distribution/pom.xml | 3 +-- services/pom.xml | 30 ++++++++++++++++++++++++++---- services/submission/pom.xml | 3 +-- 10 files changed, 89 insertions(+), 19 deletions(-) create mode 100644 .mvn/maven.config diff --git a/.gitignore b/.gitignore index f0c8b61d35..07b224c1fc 100644 --- a/.gitignore +++ b/.gitignore @@ -38,5 +38,7 @@ out/ .settings .project .classpath -pom.xml.versionsBackup .factorypath + +pom.xml.versionsBackup +**/.flattened-pom.xml diff --git a/.mvn/maven.config b/.mvn/maven.config new file mode 100644 index 0000000000..fdc22404ff --- /dev/null +++ b/.mvn/maven.config @@ -0,0 +1 @@ +-Drevision=0.5.1-SNAPSHOT diff --git a/THIRD-PARTY-NOTICES b/THIRD-PARTY-NOTICES index fb9642ef02..42dc7de02a 100644 --- a/THIRD-PARTY-NOTICES +++ b/THIRD-PARTY-NOTICES @@ -55,6 +55,11 @@ Licensor: MinIO Inc. Website: https://min.io/ License: Apache License 2.0 +Component: MojoHaus Flatten Maven Plugin +Licensor: MojoHaus +Website: https://www.mojohaus.org/flatten-maven-plugin/ +License: Apache License 2.0 + Component: PostgreSQL Licensor: PostgreSQL Website: https://www.postgresql.org/ @@ -87,7 +92,7 @@ License: Apache License 2.0 -------------------------------------------------------------------------------- Apache License 2.0 (Commons IO, Commons Math 3, flyway, JSON-Simple, -Maven, MinIO Object Storage, snakeyaml, Spring Boot, Zenko CloudServer) +Maven, MinIO Object Storage, MojoHaus Flatten Maven Plugin, snakeyaml, Spring Boot, Zenko CloudServer) Apache License Version 2.0, January 2004 @@ -715,4 +720,4 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------------------------------- \ No newline at end of file +-------------------------------------------------------------------------------- diff --git a/common/persistence/pom.xml b/common/persistence/pom.xml index fc30b59be5..31fc6c9d99 100644 --- a/common/persistence/pom.xml +++ b/common/persistence/pom.xml @@ -12,7 +12,7 @@ org.opencwa persistence - 0.5.1-SNAPSHOT + ${revision} 11 @@ -27,7 +27,7 @@ org.opencwa protocols - 0.5.1-SNAPSHOT + ${project.version} org.springframework.boot @@ -105,6 +105,28 @@ + + + org.codehaus.mojo + flatten-maven-plugin + 1.1.0 + + true + resolveCiFriendliesOnly + + + + flatten + process-resources + flatten + + + flatten-clean + clean + clean + + + org.apache.maven.plugins maven-checkstyle-plugin diff --git a/common/pom.xml b/common/pom.xml index 4f0686b675..94f5e7183c 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -6,7 +6,7 @@ server org.opencwa - 0.5.1-SNAPSHOT + ${revision} ../pom.xml @@ -17,7 +17,6 @@ 4.0.0 common - 0.5.1-SNAPSHOT pom diff --git a/common/protocols/pom.xml b/common/protocols/pom.xml index 7bbedc904b..82a84b165c 100644 --- a/common/protocols/pom.xml +++ b/common/protocols/pom.xml @@ -5,13 +5,12 @@ common org.opencwa - 0.5.1-SNAPSHOT + ${revision} ../pom.xml 4.0.0 protocols - 0.5.1-SNAPSHOT corona-warn-app_cwa-server_common_protocols @@ -53,4 +52,4 @@ - \ No newline at end of file + diff --git a/pom.xml b/pom.xml index aae5277c8c..b7004c9644 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@
org.opencwa server - 0.5.1-SNAPSHOT + ${revision} server CWA Server https://www.coronawarn.app/ @@ -37,6 +37,28 @@ + + + org.codehaus.mojo + flatten-maven-plugin + 1.1.0 + + true + resolveCiFriendliesOnly + + + + flatten + process-resources + flatten + + + flatten-clean + clean + clean + + + org.apache.maven.plugins maven-checkstyle-plugin diff --git a/services/distribution/pom.xml b/services/distribution/pom.xml index dc40e5fb56..2c8048e60c 100644 --- a/services/distribution/pom.xml +++ b/services/distribution/pom.xml @@ -5,7 +5,7 @@ services org.opencwa - 0.5.1-SNAPSHOT + ${revision} ../pom.xml 4.0.0 @@ -52,7 +52,6 @@ distribution - 0.5.1-SNAPSHOT io.minio diff --git a/services/pom.xml b/services/pom.xml index 92252ba62a..1ddf53bdab 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -14,7 +14,7 @@ services org.opencwa - 0.5.1-SNAPSHOT + ${revision} pom @@ -35,12 +35,12 @@ org.opencwa persistence - 0.5.1-SNAPSHOT + ${project.version} org.opencwa protocols - 0.5.1-SNAPSHOT + ${project.version} org.springframework.boot @@ -89,6 +89,28 @@ + + + org.codehaus.mojo + flatten-maven-plugin + 1.1.0 + + true + resolveCiFriendliesOnly + + + + flatten + process-resources + flatten + + + flatten-clean + clean + clean + + + org.springframework.boot spring-boot-maven-plugin @@ -124,4 +146,4 @@ - \ No newline at end of file + diff --git a/services/submission/pom.xml b/services/submission/pom.xml index d9807d3613..9b13d6991d 100644 --- a/services/submission/pom.xml +++ b/services/submission/pom.xml @@ -5,13 +5,12 @@ services org.opencwa - 0.5.1-SNAPSHOT + ${revision} ../pom.xml 4.0.0 submission - 0.5.1-SNAPSHOT corona-warn-app_cwa-server_services_submission From 0a70230a011dd7c626ad8b09ab08f9016cbd9111 Mon Sep 17 00:00:00 2001 From: Pit Humke Date: Wed, 27 May 2020 13:56:54 +0200 Subject: [PATCH 08/18] Fix Docker Compose on Windows Hyper-V (#337) --- README.md | 2 +- docker-compose.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1a8d8856bc..262d242ee6 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ The docker-compose contains the following services: Service | Description | Endpoint and Default Credentials ------------------|-------------|----------- -submission | The Corona-Warn-App submission service | `http://localhost:8000`
`http://localhost:8005` (for actuator endpoint) +submission | The Corona-Warn-App submission service | `http://localhost:8000`
`http://localhost:8006` (for actuator endpoint) distribution | The Corona-Warn-App distribution service | NO ENDPOINT postgres | A [postgres] database installation | `postgres:8001`
Username: postgres
Password: postgres pgadmin | A [pgadmin](https://www.pgadmin.org/) installation for the postgres database | `http://localhost:8002`
Username: user@domain.com
Password: password diff --git a/docker-compose.yml b/docker-compose.yml index 66361ade9c..ea2f9f9567 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,7 +8,7 @@ services: - postgres ports: - "8000:8080" - - "8005:8081" + - "8006:8081" environment: SPRING_PROFILES_ACTIVE: dev POSTGRESQL_SERVICE_PORT: '5432' From 7d1a5a4e293a83a388d126f32a1c229f13467437 Mon Sep 17 00:00:00 2001 From: Pit Humke Date: Wed, 27 May 2020 14:05:07 +0200 Subject: [PATCH 09/18] Fix docker-compose on windows (#338) --- docker-compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index ea2f9f9567..bd444e5733 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -84,10 +84,10 @@ services: environment: - AWS_ACCESS_KEY_ID=${OBJECTSTORE_ACCESSKEY} - AWS_SECRET_ACCESS_KEY=${OBJECTSTORE_SECRETKEY} - entrypoint: ["/root/scripts/wait-for-it/wait-for-it.sh", "objectstore:8000", "-t", "30", "--"] + #entrypoint: ["/root/scripts/wait-for-it/wait-for-it.sh", "objectstore:8000", "-t", "30", "--"] volumes: - ./scripts/wait-for-it:/root/scripts/wait-for-it - command: aws s3api create-bucket --bucket cwa --endpoint-url http://objectstore:8000 --acl public-read + command: s3api create-bucket --bucket cwa --endpoint-url http://objectstore:8000 --acl public-read depends_on: - objectstore verification-fake: From 56a05f9f26f383225a6d8cfc4f9d1bfc8b861470 Mon Sep 17 00:00:00 2001 From: Nils El-Himoud Date: Wed, 27 May 2020 14:42:07 +0200 Subject: [PATCH 10/18] Crypto provider load resources on startup (#306) (#307) --- .../assembly/component/CryptoProvider.java | 63 +++++++++---------- 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/component/CryptoProvider.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/component/CryptoProvider.java index e416995d14..f8ce20ed73 100644 --- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/component/CryptoProvider.java +++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/component/CryptoProvider.java @@ -46,38 +46,31 @@ @Component public class CryptoProvider { - private final String privateKeyPath; - - private final String certificatePath; - - private final ResourceLoader resourceLoader; - - private PrivateKey privateKey; - private Certificate certificate; + private final PrivateKey privateKey; + private final Certificate certificate; /** * Creates a CryptoProvider, using {@link BouncyCastleProvider}. */ CryptoProvider(ResourceLoader resourceLoader, DistributionServiceConfig distributionServiceConfig) { - this.resourceLoader = resourceLoader; - this.privateKeyPath = distributionServiceConfig.getPaths().getPrivateKey(); - this.certificatePath = distributionServiceConfig.getPaths().getCertificate(); + privateKey = loadPrivateKey(resourceLoader, distributionServiceConfig); + certificate = loadCertificate(resourceLoader, distributionServiceConfig); Security.addProvider(new BouncyCastleProvider()); } - private static PrivateKey getPrivateKeyFromStream(final InputStream privateKeyStream) throws IOException { + private static PrivateKey getPrivateKeyFromStream(InputStream privateKeyStream) throws IOException { InputStreamReader privateKeyStreamReader = new InputStreamReader(privateKeyStream); Object parsed = new PEMParser(privateKeyStreamReader).readObject(); KeyPair pair = new JcaPEMKeyConverter().getKeyPair((PEMKeyPair) parsed); return pair.getPrivate(); } - private static Certificate getCertificateFromStream(final InputStream certificateStream) + private static Certificate getCertificateFromStream(InputStream certificateStream) throws IOException, CertificateException { return getCertificateFromBytes(certificateStream.readAllBytes()); } - private static Certificate getCertificateFromBytes(final byte[] bytes) + private static Certificate getCertificateFromBytes(byte[] bytes) throws CertificateException { CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); InputStream certificateByteStream = new ByteArrayInputStream(bytes); @@ -85,32 +78,38 @@ private static Certificate getCertificateFromBytes(final byte[] bytes) } /** - * Reads and returns the {@link PrivateKey} configured in the application properties. + * Returns the {@link PrivateKey} configured in the application properties. */ public PrivateKey getPrivateKey() { - if (privateKey == null) { - Resource privateKeyResource = resourceLoader.getResource(privateKeyPath); - try (InputStream privateKeyStream = privateKeyResource.getInputStream()) { - privateKey = getPrivateKeyFromStream(privateKeyStream); - } catch (IOException e) { - throw new UncheckedIOException("Failed to load private key from " + privateKeyPath, e); - } - } return privateKey; } + private PrivateKey loadPrivateKey(ResourceLoader resourceLoader, + DistributionServiceConfig distributionServiceConfig) { + String path = distributionServiceConfig.getPaths().getPrivateKey(); + Resource privateKeyResource = resourceLoader.getResource(path); + try (InputStream privateKeyStream = privateKeyResource.getInputStream()) { + return getPrivateKeyFromStream(privateKeyStream); + } catch (IOException e) { + throw new UncheckedIOException("Failed to load private key from " + path, e); + } + } + /** - * Reads and returns the {@link Certificate} configured in the application properties. + * Returns the {@link Certificate} configured in the application properties. */ public Certificate getCertificate() { - if (this.certificate == null) { - Resource certResource = resourceLoader.getResource(certificatePath); - try (InputStream certStream = certResource.getInputStream()) { - this.certificate = getCertificateFromStream(certStream); - } catch (IOException | CertificateException e) { - throw new RuntimeException("Failed to load certificate from " + certificatePath, e); - } - } return certificate; } + + private Certificate loadCertificate(ResourceLoader resourceLoader, + DistributionServiceConfig distributionServiceConfig) { + String path = distributionServiceConfig.getPaths().getCertificate(); + Resource certResource = resourceLoader.getResource(path); + try (InputStream certStream = certResource.getInputStream()) { + return getCertificateFromStream(certStream); + } catch (IOException | CertificateException e) { + throw new RuntimeException("Failed to load certificate from " + path, e); + } + } } From 013e8500a03e0d6cc8aebc4eab5b751ea2a7d160 Mon Sep 17 00:00:00 2001 From: Steve BE <5719743+stevesap@users.noreply.github.com> Date: Wed, 27 May 2020 17:46:00 +0200 Subject: [PATCH 11/18] Fixed additional sonar issues (#321) --- .../structure/directory/AppConfigurationDirectory.java | 6 +++--- .../directory/DiagnosisKeysCountryDirectory.java | 2 +- .../structure/directory/DiagnosisKeysDateDirectory.java | 2 +- .../structure/file/TemporaryExposureKeyExportFile.java | 1 - .../assembly/diagnosiskeys/util/DateTime.java | 2 +- .../assembly/structure/archive/ArchiveOnDisk.java | 4 ---- .../distribution/assembly/structure/file/FileOnDisk.java | 8 +++----- .../distribution/config/DistributionServiceConfig.java | 1 - .../services/submission/verification/TanVerifier.java | 9 +++++---- 9 files changed, 14 insertions(+), 21 deletions(-) diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/appconfig/structure/directory/AppConfigurationDirectory.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/appconfig/structure/directory/AppConfigurationDirectory.java index 5b6708d4d5..d3da88d0b3 100644 --- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/appconfig/structure/directory/AppConfigurationDirectory.java +++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/appconfig/structure/directory/AppConfigurationDirectory.java @@ -65,7 +65,7 @@ public AppConfigurationDirectory(CryptoProvider cryptoProvider, DistributionServ this.distributionServiceConfig = distributionServiceConfig; countryDirectory = new IndexDirectoryOnDisk<>(distributionServiceConfig.getApi().getCountryPath(), - x -> Set.of(distributionServiceConfig.getApi().getCountryGermany()), Object::toString); + __ -> Set.of(distributionServiceConfig.getApi().getCountryGermany()), Object::toString); addApplicationConfigurationIfValid(); @@ -79,7 +79,7 @@ private void addApplicationConfigurationIfValid() { addArchiveIfMessageValid(distributionServiceConfig.getApi().getAppConfigFileName(), appConfig, validator); } catch (UnableToLoadFileException e) { - logger.error("Exposure configuration will not be published! Unable to read configuration file from disk."); + logger.error("Exposure configuration will not be published! Unable to read configuration file from disk.", e); } } @@ -97,7 +97,7 @@ private void addArchiveIfMessageValid(String archiveName, Message message, Confi ArchiveOnDisk appConfigurationFile = new ArchiveOnDisk(archiveName); appConfigurationFile.addWritable(new FileOnDisk("export.bin", message.toByteArray())); - countryDirectory.addWritableToAll(x -> new AppConfigurationSigningDecorator(appConfigurationFile, cryptoProvider, + countryDirectory.addWritableToAll(__ -> new AppConfigurationSigningDecorator(appConfigurationFile, cryptoProvider, distributionServiceConfig)); } } diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/DiagnosisKeysCountryDirectory.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/DiagnosisKeysCountryDirectory.java index 8fe599d007..4f88461f62 100644 --- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/DiagnosisKeysCountryDirectory.java +++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/DiagnosisKeysCountryDirectory.java @@ -47,7 +47,7 @@ public class DiagnosisKeysCountryDirectory extends IndexDirectoryOnDisk */ public DiagnosisKeysCountryDirectory(Collection diagnosisKeys, CryptoProvider cryptoProvider, DistributionServiceConfig distributionServiceConfig) { - super(distributionServiceConfig.getApi().getCountryPath(), x -> + super(distributionServiceConfig.getApi().getCountryPath(), __ -> Set.of(distributionServiceConfig.getApi().getCountryGermany()), Object::toString); this.diagnosisKeys = diagnosisKeys; this.cryptoProvider = cryptoProvider; diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/DiagnosisKeysDateDirectory.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/DiagnosisKeysDateDirectory.java index d5936eeb76..dc60756c1c 100644 --- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/DiagnosisKeysDateDirectory.java +++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/DiagnosisKeysDateDirectory.java @@ -49,7 +49,7 @@ public class DiagnosisKeysDateDirectory extends IndexDirectoryOnDisk */ public DiagnosisKeysDateDirectory(Collection diagnosisKeys, CryptoProvider cryptoProvider, DistributionServiceConfig distributionServiceConfig) { - super(distributionServiceConfig.getApi().getDatePath(), x -> DateTime.getDates(diagnosisKeys), ISO8601::format); + super(distributionServiceConfig.getApi().getDatePath(), __ -> DateTime.getDates(diagnosisKeys), ISO8601::format); this.cryptoProvider = cryptoProvider; this.diagnosisKeys = diagnosisKeys; this.distributionServiceConfig = distributionServiceConfig; diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/file/TemporaryExposureKeyExportFile.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/file/TemporaryExposureKeyExportFile.java index bf6785a27d..bfd399fb35 100644 --- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/file/TemporaryExposureKeyExportFile.java +++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/file/TemporaryExposureKeyExportFile.java @@ -103,7 +103,6 @@ private static Set getTemporaryExposureKeysFromDiagnosisKe return diagnosisKeys.stream().map(diagnosisKey -> TemporaryExposureKey.newBuilder() .setKeyData(ByteString.copyFrom(diagnosisKey.getKeyData())) .setTransmissionRiskLevel(diagnosisKey.getTransmissionRiskLevel()) - // TODO cwa-server/#233 Rolling start number and period should be int32 .setRollingStartIntervalNumber(diagnosisKey.getRollingStartIntervalNumber()) .setRollingPeriod(diagnosisKey.getRollingPeriod()) .build()) diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/util/DateTime.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/util/DateTime.java index 122a1749ba..01a0074af8 100644 --- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/util/DateTime.java +++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/util/DateTime.java @@ -33,7 +33,7 @@ */ public class DateTime { - private DateTime(){ + private DateTime() { } /** diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/archive/ArchiveOnDisk.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/archive/ArchiveOnDisk.java index ee0e543fde..43390bb802 100644 --- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/archive/ArchiveOnDisk.java +++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/archive/ArchiveOnDisk.java @@ -35,16 +35,12 @@ import java.util.Set; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * An {@link Archive} that can be written to disk as a ZIP archive. */ public class ArchiveOnDisk extends FileOnDisk implements Archive { - private static final Logger logger = LoggerFactory.getLogger(ArchiveOnDisk.class); - private DirectoryOnDisk tempDirectory; /** diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/file/FileOnDisk.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/file/FileOnDisk.java index 7979fe08d7..b2af9d6d56 100644 --- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/file/FileOnDisk.java +++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/structure/file/FileOnDisk.java @@ -37,8 +37,8 @@ public FileOnDisk(String name, byte[] bytes) { } /** - * Creates a {@link java.io.File} with name {@link Writable#getName} on disk and writes the - * {@link File#getBytes bytes} of this {@link File} into that {@link java.io.File}. + * Creates a {@link java.io.File} with name {@link Writable#getName} on disk and writes the {@link File#getBytes + * bytes} of this {@link File} into that {@link java.io.File}. */ @Override public void write() { @@ -61,8 +61,6 @@ public void setBytes(byte[] bytes) { */ @Override public void prepare(ImmutableStack indices) { - /* - Method override exists here to comply with the implementation rules for the Writable interface. - */ + // Method override exists here to comply with the implementation rules for the Writable interface. } } diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/config/DistributionServiceConfig.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/config/DistributionServiceConfig.java index abe31d1546..ec2035b818 100644 --- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/config/DistributionServiceConfig.java +++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/config/DistributionServiceConfig.java @@ -348,7 +348,6 @@ public void setSecurityProvider(String securityProvider) { * Returns the static {@link SignatureInfo} configured in the application properties. TODO Enter correct values. */ public SignatureInfo getSignatureInfo() { - // TODO cwa-server#183 cwa-server#207 cwa-server#238 return SignatureInfo.newBuilder() .setAppBundleId(this.getAppBundleId()) .setAndroidPackage(this.getAndroidPackage()) diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/TanVerifier.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/TanVerifier.java index f1a0078fc7..65539af9d5 100644 --- a/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/TanVerifier.java +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/TanVerifier.java @@ -49,12 +49,12 @@ public class TanVerifier { * This class can be used to verify a TAN against a configured verification service. * * @param submissionServiceConfig A submission service configuration - * @param restTemplateBuilder A rest template builder + * @param restTemplateBuilder A rest template builder */ @Autowired public TanVerifier(SubmissionServiceConfig submissionServiceConfig, RestTemplateBuilder restTemplateBuilder) { this.verificationServiceUrl = submissionServiceConfig.getVerificationBaseUrl() - + submissionServiceConfig.getVerificationPath(); + + submissionServiceConfig.getVerificationPath(); this.restTemplate = restTemplateBuilder.build(); this.requestHeader.setContentType(MediaType.APPLICATION_JSON); } @@ -88,6 +88,7 @@ private boolean checkTanSyntax(String tan) { UUID.fromString(tan); return true; } catch (IllegalArgumentException e) { + logger.debug("UUID creation failed for value: {}", tan, e); return false; } } @@ -107,8 +108,8 @@ private boolean verifyWithVerificationService(String tan) { ResponseEntity response = restTemplate.postForEntity(verificationServiceUrl, entity, String.class); return response.getStatusCode().is2xxSuccessful(); } catch (HttpClientErrorException.NotFound e) { - // The validation service returns http status 404 if the TAN is invalid - logger.debug("TAN validation failed for TAN: {}", tan); + // The verification service returns http status 404 if the TAN is invalid + logger.debug("TAN verification failed"); return false; } } From 8d8a0a5e16bc252364812017a85418b054d739f7 Mon Sep 17 00:00:00 2001 From: Pit Humke Date: Wed, 27 May 2020 18:17:48 +0200 Subject: [PATCH 12/18] Remove certificates from CryptoProvider and configs (#347) --- .env | 1 - docker-compose-test-secrets/certificate.crt | 18 ---- docker-compose-test-secrets/private.pem | 6 +- docker-compose.yml | 3 +- scripts/.gitignore | 2 +- scripts/generate_certificates.sh | 89 ------------------- scripts/generate_keys.sh | 68 ++++++++++++++ .../assembly/component/CryptoProvider.java | 39 +------- .../config/DistributionServiceConfig.java | 9 -- .../src/main/resources/application.yaml | 1 - .../component/CryptoProviderTest.java | 1 - .../signing/SigningDecoratorTest.java | 28 ++++-- .../src/test/resources/application.yaml | 3 +- .../certificates/chain/certificate.crt | 18 ---- .../resources/certificates/client/private.pem | 5 -- .../src/test/resources/keys/certificate.crt | 9 ++ .../src/test/resources/keys/private.pem | 5 ++ 17 files changed, 112 insertions(+), 193 deletions(-) delete mode 100644 docker-compose-test-secrets/certificate.crt delete mode 100755 scripts/generate_certificates.sh create mode 100644 scripts/generate_keys.sh delete mode 100644 services/distribution/src/test/resources/certificates/chain/certificate.crt delete mode 100644 services/distribution/src/test/resources/certificates/client/private.pem create mode 100644 services/distribution/src/test/resources/keys/certificate.crt create mode 100644 services/distribution/src/test/resources/keys/private.pem diff --git a/.env b/.env index 4a61e7ca47..a9398e2884 100644 --- a/.env +++ b/.env @@ -16,4 +16,3 @@ OBJECTSTORE_SECRETKEY=verySecretKey1 # Docker Compose Secrets settings SECRET_PRIVATE=file:/secrets/private.pem -SECRET_CERTIFICATE=file:/secrets/certificate.crt diff --git a/docker-compose-test-secrets/certificate.crt b/docker-compose-test-secrets/certificate.crt deleted file mode 100644 index dbb2a3a672..0000000000 --- a/docker-compose-test-secrets/certificate.crt +++ /dev/null @@ -1,18 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIBMjCB2AIBZDAKBggqhkjOPQQDAjAkMSIwIAYDVQQDDBlDV0EgVGVzdCBSb290 -IENlcnRpZmljYXRlMB4XDTIwMDUyMzExMzAzOFoXDTIxMDUyMzExMzAzOFowJjEk -MCIGA1UEAwwbQ1dBIFRlc3QgQ2xpZW50IENlcnRpZmljYXRlMFkwEwYHKoZIzj0C -AQYIKoZIzj0DAQcDQgAEYQJ+sReY1L8z851VFRpLu4PCusj/7Ruvi879KjrQJ12k -KKsfeRWytmrE65Jok1lsYqpFhRWcxG6VV5FX0yG+EjAKBggqhkjOPQQDAgNJADBG -AiEAwP/VKVIhOuiIczrPFg0o4ns39Wu1vBpIXu+/psI/3LECIQD0FAXo5chuUkQy -LeUtwsQaC8v2KA96ew3PfCoTilvU1Q== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIBQzCB6QIUN7Z6IofaE0qtz2X1xz/IUDCUXd0wCgYIKoZIzj0EAwIwJDEiMCAG -A1UEAwwZQ1dBIFRlc3QgUm9vdCBDZXJ0aWZpY2F0ZTAeFw0yMDA1MjMxMTMwMzha -Fw0yMTA1MjMxMTMwMzhaMCQxIjAgBgNVBAMMGUNXQSBUZXN0IFJvb3QgQ2VydGlm -aWNhdGUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATBH0/vPsD62wE9jk5JZZd+ -Yf4WXZ3sKZUGbdqJssc4lxjyZJvpPLCirRQT6XWwhmBhoL8mRL5tpZ/93o+jzULe -MAoGCCqGSM49BAMCA0kAMEYCIQCNPEtoC1QtgnncZzV/rgIoO6tktCiAkybBhHMB -1SISZQIhAPguoBWCscYwNHtEgTDx2sQ8UZ79KvWvpHFlwDEyiHAv ------END CERTIFICATE----- diff --git a/docker-compose-test-secrets/private.pem b/docker-compose-test-secrets/private.pem index 053142de82..6d8dd02eff 100644 --- a/docker-compose-test-secrets/private.pem +++ b/docker-compose-test-secrets/private.pem @@ -1,5 +1,5 @@ -----BEGIN EC PRIVATE KEY----- -MHcCAQEEILQRQFlGcfeTAclubtjQ1rBjtmIOB/d7PITZyDe1r81/oAoGCCqGSM49 -AwEHoUQDQgAEYQJ+sReY1L8z851VFRpLu4PCusj/7Ruvi879KjrQJ12kKKsfeRWy -tmrE65Jok1lsYqpFhRWcxG6VV5FX0yG+Eg== +MHcCAQEEINOQcM6jChvjAwf0B3C2ex7Ronsc2VH4qIZK91n1/tgJoAoGCCqGSM49 +AwEHoUQDQgAE9/HUs+ssvOdmv+BZPjubaUiYOWYTd5iRMopbdBzpEPXbyQBSmOFe +sVJ7y3GTU/1ql9FuIrqB7YBkhZZExPEqEw== -----END EC PRIVATE KEY----- diff --git a/docker-compose.yml b/docker-compose.yml index bd444e5733..92df204215 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -38,9 +38,8 @@ services: CWA_OBJECTSTORE_BUCKET: cwa CWA_OBJECTSTORE_PORT: 8000 services.distribution.paths.output: /tmp/distribution - # Settings for cryptographic artefacts + # Settings for cryptographic artifacts VAULT_FILESIGNING_SECRET: ${SECRET_PRIVATE} - VAULT_FILESIGNING_CERT: ${SECRET_CERTIFICATE} volumes: - ./docker-compose-test-secrets:/secrets postgres: diff --git a/scripts/.gitignore b/scripts/.gitignore index ba7dc224e6..81bbbae97b 100644 --- a/scripts/.gitignore +++ b/scripts/.gitignore @@ -1 +1 @@ -/certificates/ +keys/ diff --git a/scripts/generate_certificates.sh b/scripts/generate_certificates.sh deleted file mode 100755 index 3490256a8f..0000000000 --- a/scripts/generate_certificates.sh +++ /dev/null @@ -1,89 +0,0 @@ -#!/bin/bash - -# Generates a new self-signed X.509 root certificate and a client certificate. Only to be used for -# testing purposes. This script requires openssl to be installed, see here: -## https://www.openssl.org/source/ - -pushd "$(dirname "${BASH_SOURCE[0]}")" > /dev/null || exit - -rm -rf certificates -mkdir certificates -pushd certificates > /dev/null || exit - -# Generate a private key -# $1 = OUT Private key file -generate_private_key() -{ - openssl ecparam \ - -name prime256v1 \ - -genkey \ - -noout \ - -out "$1" -} - -# Generate certificate signing request -# $1 = IN New certificate private key -# $2 = IN "Subject" of the certificate -# $3 = OUT Certificate signing request file -generate_certificate_signing_request() -{ - openssl req \ - -new \ - -key "$1" \ - -subj "$2" \ - -out "$3" -} - -# Self-sign a certificate request -# $1 = IN Certificate signing request file -# $2 = IN Certificate authority private key file -# $3 = OUT New certificate file -self_sign_certificate_request() -{ - openssl x509 \ - -req \ - -days 365 \ - -in "$1" \ - -signkey "$2" \ - -out "$3" -} - -# Sign a certificate request using a CA certificate + private key -# $1 = IN Certificate signing request file -# $2 = IN Certificate authority certificate file -# $3 = IN Certificate authority private key file -# $4 = OUT New certificate file -ca_sign_certificate_request() -{ - openssl x509 \ - -req \ - -days 365 \ - -set_serial 100 \ - -in "$1" \ - -CA "$2" \ - -CAkey "$3" \ - -out "$4" -} - -# Self-signed root certificate -mkdir root -generate_private_key root/private.pem -generate_certificate_signing_request root/private.pem '/CN=CWA Test Root Certificate' root/request.csr -self_sign_certificate_request root/request.csr root/private.pem root/certificate.crt - -# Client certificate signed by root certificate -mkdir client -generate_private_key client/private.pem -generate_certificate_signing_request client/private.pem '/CN=CWA Test Client Certificate' client/request.csr -ca_sign_certificate_request client/request.csr root/certificate.crt root/private.pem client/certificate.crt - -# Concatenate the root certificate and the client certificate. -# This way, we have the whole certificate chain in a single file. -mkdir chain -cat client/certificate.crt root/certificate.crt >> chain/certificate.crt - -# Final verification of the certificate chain -openssl verify -CAfile root/certificate.crt chain/certificate.crt - -popd > /dev/null || exit -popd > /dev/null || exit \ No newline at end of file diff --git a/scripts/generate_keys.sh b/scripts/generate_keys.sh new file mode 100644 index 0000000000..1f4b73ed31 --- /dev/null +++ b/scripts/generate_keys.sh @@ -0,0 +1,68 @@ +#!/bin/bash + +# Generates a prime256v1 EC private key and extracts the respective public key. +# This script requires openssl to be installed, see here: +## https://www.openssl.org/source/ + +pushd "$(dirname "${BASH_SOURCE[0]}")" > /dev/null || exit + +rm -rf keys +mkdir keys +pushd keys > /dev/null || exit + +# Generate a prime256v1 EC private key +# $1 = OUT Private key file +generate_private_key() +{ + openssl ecparam \ + -name prime256v1 \ + -genkey \ + -out "$1" \ + -noout +} + +# Extract the public key from the private key +# $1 = IN Private key +# $2 = OUT Public key +extract_public_key() +{ + openssl ec \ + -in "$1" \ + -pubout \ + -out "$2" +} + +# Generate certificate signing request +# $1 = IN New certificate private key +# $2 = IN "Subject" of the certificate +# $3 = OUT Certificate signing request file +generate_certificate_signing_request() +{ + openssl req \ + -new \ + -key "$1" \ + -subj "$2" \ + -out "$3" +} + +# Self-sign a certificate request +# $1 = IN Certificate signing request file +# $2 = IN Certificate authority private key file +# $3 = OUT New certificate file +self_sign_certificate_request() +{ + openssl x509 \ + -req \ + -days 365 \ + -in "$1" \ + -signkey "$2" \ + -out "$3" +} + +generate_private_key private.pem +extract_public_key private.pem public.pem +generate_certificate_signing_request private.pem '/CN=CWA Test Certificate' request.csr +self_sign_certificate_request request.csr private.pem certificate.crt + +popd > /dev/null || exit +popd > /dev/null || exit \ No newline at end of file diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/component/CryptoProvider.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/component/CryptoProvider.java index f8ce20ed73..680c56ac83 100644 --- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/component/CryptoProvider.java +++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/component/CryptoProvider.java @@ -20,7 +20,6 @@ package app.coronawarn.server.services.distribution.assembly.component; import app.coronawarn.server.services.distribution.config.DistributionServiceConfig; -import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -28,9 +27,6 @@ import java.security.KeyPair; import java.security.PrivateKey; import java.security.Security; -import java.security.cert.Certificate; -import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openssl.PEMKeyPair; import org.bouncycastle.openssl.PEMParser; @@ -40,21 +36,18 @@ import org.springframework.stereotype.Component; /** - * Wrapper component for a {@link CryptoProvider#getPrivateKey() private key} and a {@link - * CryptoProvider#getCertificate()} certificate} from the application properties. + * Wrapper component for a {@link CryptoProvider#getPrivateKey() private key} from the application properties. */ @Component public class CryptoProvider { private final PrivateKey privateKey; - private final Certificate certificate; /** * Creates a CryptoProvider, using {@link BouncyCastleProvider}. */ CryptoProvider(ResourceLoader resourceLoader, DistributionServiceConfig distributionServiceConfig) { privateKey = loadPrivateKey(resourceLoader, distributionServiceConfig); - certificate = loadCertificate(resourceLoader, distributionServiceConfig); Security.addProvider(new BouncyCastleProvider()); } @@ -65,18 +58,6 @@ private static PrivateKey getPrivateKeyFromStream(InputStream privateKeyStream) return pair.getPrivate(); } - private static Certificate getCertificateFromStream(InputStream certificateStream) - throws IOException, CertificateException { - return getCertificateFromBytes(certificateStream.readAllBytes()); - } - - private static Certificate getCertificateFromBytes(byte[] bytes) - throws CertificateException { - CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); - InputStream certificateByteStream = new ByteArrayInputStream(bytes); - return certificateFactory.generateCertificate(certificateByteStream); - } - /** * Returns the {@link PrivateKey} configured in the application properties. */ @@ -94,22 +75,4 @@ private PrivateKey loadPrivateKey(ResourceLoader resourceLoader, throw new UncheckedIOException("Failed to load private key from " + path, e); } } - - /** - * Returns the {@link Certificate} configured in the application properties. - */ - public Certificate getCertificate() { - return certificate; - } - - private Certificate loadCertificate(ResourceLoader resourceLoader, - DistributionServiceConfig distributionServiceConfig) { - String path = distributionServiceConfig.getPaths().getCertificate(); - Resource certResource = resourceLoader.getResource(path); - try (InputStream certStream = certResource.getInputStream()) { - return getCertificateFromStream(certStream); - } catch (IOException | CertificateException e) { - throw new RuntimeException("Failed to load certificate from " + path, e); - } - } } diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/config/DistributionServiceConfig.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/config/DistributionServiceConfig.java index ec2035b818..385dab901b 100644 --- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/config/DistributionServiceConfig.java +++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/config/DistributionServiceConfig.java @@ -156,7 +156,6 @@ public void setExposuresPerHour(Integer exposuresPerHour) { public static class Paths { private String privateKey; - private String certificate; private String output; public String getPrivateKey() { @@ -167,14 +166,6 @@ public void setPrivateKey(String privateKey) { this.privateKey = privateKey; } - public String getCertificate() { - return certificate; - } - - public void setCertificate(String certificate) { - this.certificate = certificate; - } - public String getOutput() { return output; } diff --git a/services/distribution/src/main/resources/application.yaml b/services/distribution/src/main/resources/application.yaml index 169965787b..09fce01d28 100644 --- a/services/distribution/src/main/resources/application.yaml +++ b/services/distribution/src/main/resources/application.yaml @@ -16,7 +16,6 @@ services: paths: output: out privatekey: ${VAULT_FILESIGNING_SECRET} - certificate: ${VAULT_FILESIGNING_CERT} tek-export: file-name: export.bin file-header: EK Export v1 diff --git a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/component/CryptoProviderTest.java b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/component/CryptoProviderTest.java index 5e8f24a3be..84f722f028 100644 --- a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/component/CryptoProviderTest.java +++ b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/component/CryptoProviderTest.java @@ -42,6 +42,5 @@ class CryptoProviderTest { @Test void constructorInitializesCryptoArtifacts() { assertThat(cryptoProvider.getPrivateKey()).isNotNull(); - assertThat(cryptoProvider.getCertificate()).isNotNull(); } } diff --git a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/structure/archive/decorator/signing/SigningDecoratorTest.java b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/structure/archive/decorator/signing/SigningDecoratorTest.java index 7a4f45e5b8..edb5b0c8ef 100644 --- a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/structure/archive/decorator/signing/SigningDecoratorTest.java +++ b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/structure/archive/decorator/signing/SigningDecoratorTest.java @@ -15,12 +15,17 @@ import app.coronawarn.server.services.distribution.assembly.structure.file.FileOnDisk; import app.coronawarn.server.services.distribution.assembly.structure.util.ImmutableStack; import app.coronawarn.server.services.distribution.config.DistributionServiceConfig; +import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.Signature; import java.security.SignatureException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; import java.util.List; import org.junit.Rule; import org.junit.jupiter.api.BeforeEach; @@ -30,6 +35,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.test.context.ConfigFileApplicationContextInitializer; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; @@ -42,6 +49,9 @@ class SigningDecoratorTest { @Autowired CryptoProvider cryptoProvider; + @Autowired + ResourceLoader resourceLoader; + @Autowired DistributionServiceConfig distributionServiceConfig; @@ -103,14 +113,22 @@ void checkSignatureInfo() { @Test void checkSignature() - throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeyException, SignatureException { + throws NoSuchProviderException, NoSuchAlgorithmException, SignatureException, IOException, InvalidKeyException, CertificateException { byte[] fileBytes = fileToSign.getBytes(); byte[] signatureBytes = signatureList.getSignaturesList().get(0).getSignature().toByteArray(); - Signature payloadSignature = Signature.getInstance("SHA256withECDSA", "BC"); - payloadSignature.initVerify(cryptoProvider.getCertificate()); - payloadSignature.update(fileBytes); - assertThat(payloadSignature.verify(signatureBytes)).isTrue(); + Resource certResource = resourceLoader.getResource("classpath:keys/certificate.crt"); + try (InputStream certStream = certResource.getInputStream()) { + byte[] bytes = certStream.readAllBytes(); + CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); + InputStream certificateByteStream = new ByteArrayInputStream(bytes); + Certificate certificate = certificateFactory.generateCertificate(certificateByteStream); + + Signature payloadSignature = Signature.getInstance("SHA256withECDSA", "BC"); + payloadSignature.initVerify(certificate); + payloadSignature.update(fileBytes); + assertThat(payloadSignature.verify(signatureBytes)).isTrue(); + } } private static class TestSigningDecorator extends SigningDecoratorOnDisk { diff --git a/services/distribution/src/test/resources/application.yaml b/services/distribution/src/test/resources/application.yaml index 95a624d9f1..128636aae3 100644 --- a/services/distribution/src/test/resources/application.yaml +++ b/services/distribution/src/test/resources/application.yaml @@ -13,8 +13,7 @@ services: include-incomplete-hours: false paths: output: out - privatekey: classpath:certificates/client/private.pem - certificate: classpath:certificates/chain/certificate.crt + privatekey: classpath:keys/private.pem tek-export: file-name: export.bin file-header: EK Export v1 diff --git a/services/distribution/src/test/resources/certificates/chain/certificate.crt b/services/distribution/src/test/resources/certificates/chain/certificate.crt deleted file mode 100644 index dbb2a3a672..0000000000 --- a/services/distribution/src/test/resources/certificates/chain/certificate.crt +++ /dev/null @@ -1,18 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIBMjCB2AIBZDAKBggqhkjOPQQDAjAkMSIwIAYDVQQDDBlDV0EgVGVzdCBSb290 -IENlcnRpZmljYXRlMB4XDTIwMDUyMzExMzAzOFoXDTIxMDUyMzExMzAzOFowJjEk -MCIGA1UEAwwbQ1dBIFRlc3QgQ2xpZW50IENlcnRpZmljYXRlMFkwEwYHKoZIzj0C -AQYIKoZIzj0DAQcDQgAEYQJ+sReY1L8z851VFRpLu4PCusj/7Ruvi879KjrQJ12k -KKsfeRWytmrE65Jok1lsYqpFhRWcxG6VV5FX0yG+EjAKBggqhkjOPQQDAgNJADBG -AiEAwP/VKVIhOuiIczrPFg0o4ns39Wu1vBpIXu+/psI/3LECIQD0FAXo5chuUkQy -LeUtwsQaC8v2KA96ew3PfCoTilvU1Q== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIBQzCB6QIUN7Z6IofaE0qtz2X1xz/IUDCUXd0wCgYIKoZIzj0EAwIwJDEiMCAG -A1UEAwwZQ1dBIFRlc3QgUm9vdCBDZXJ0aWZpY2F0ZTAeFw0yMDA1MjMxMTMwMzha -Fw0yMTA1MjMxMTMwMzhaMCQxIjAgBgNVBAMMGUNXQSBUZXN0IFJvb3QgQ2VydGlm -aWNhdGUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATBH0/vPsD62wE9jk5JZZd+ -Yf4WXZ3sKZUGbdqJssc4lxjyZJvpPLCirRQT6XWwhmBhoL8mRL5tpZ/93o+jzULe -MAoGCCqGSM49BAMCA0kAMEYCIQCNPEtoC1QtgnncZzV/rgIoO6tktCiAkybBhHMB -1SISZQIhAPguoBWCscYwNHtEgTDx2sQ8UZ79KvWvpHFlwDEyiHAv ------END CERTIFICATE----- diff --git a/services/distribution/src/test/resources/certificates/client/private.pem b/services/distribution/src/test/resources/certificates/client/private.pem deleted file mode 100644 index 053142de82..0000000000 --- a/services/distribution/src/test/resources/certificates/client/private.pem +++ /dev/null @@ -1,5 +0,0 @@ ------BEGIN EC PRIVATE KEY----- -MHcCAQEEILQRQFlGcfeTAclubtjQ1rBjtmIOB/d7PITZyDe1r81/oAoGCCqGSM49 -AwEHoUQDQgAEYQJ+sReY1L8z851VFRpLu4PCusj/7Ruvi879KjrQJ12kKKsfeRWy -tmrE65Jok1lsYqpFhRWcxG6VV5FX0yG+Eg== ------END EC PRIVATE KEY----- diff --git a/services/distribution/src/test/resources/keys/certificate.crt b/services/distribution/src/test/resources/keys/certificate.crt new file mode 100644 index 0000000000..53ce1c0470 --- /dev/null +++ b/services/distribution/src/test/resources/keys/certificate.crt @@ -0,0 +1,9 @@ +-----BEGIN CERTIFICATE----- +MIIBNzCB3wIUFNO+5pQ8sXzK1SlHWDWdJyDqNPgwCgYIKoZIzj0EAwIwHzEdMBsG +A1UEAwwUQ1dBIFRlc3QgQ2VydGlmaWNhdGUwHhcNMjAwNTI3MTUxNDIxWhcNMjEw +NTI3MTUxNDIxWjAfMR0wGwYDVQQDDBRDV0EgVGVzdCBDZXJ0aWZpY2F0ZTBZMBMG +ByqGSM49AgEGCCqGSM49AwEHA0IABCrLdy73GUzX9IxppsdInYwhv/oVvHIARx2X +TdS+VzuAlH46wKb2l63z1uvZ9GE6iua+y3z6tu/SmAZw8R9rnIswCgYIKoZIzj0E +AwIDRwAwRAIgfyhreHW5Z7TI6hRh2hXF4RnzWY/9OLlJrUveGkV7tZQCIEf1BEXJ +AVEMhsjYJLBd1Ktb8v623WvY9gDFZrr+/whl +-----END CERTIFICATE----- diff --git a/services/distribution/src/test/resources/keys/private.pem b/services/distribution/src/test/resources/keys/private.pem new file mode 100644 index 0000000000..e330a3d3c0 --- /dev/null +++ b/services/distribution/src/test/resources/keys/private.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIMA+2YS0MAGyxH3gzHcR7DPzc4uC54X5+FAr4NWFeomwoAoGCCqGSM49 +AwEHoUQDQgAEKst3LvcZTNf0jGmmx0idjCG/+hW8cgBHHZdN1L5XO4CUfjrApvaX +rfPW69n0YTqK5r7LfPq279KYBnDxH2uciw== +-----END EC PRIVATE KEY----- From ac3e3041f61f4800903f116e00e490804cf6583d Mon Sep 17 00:00:00 2001 From: Michael Burwig Date: Wed, 27 May 2020 22:51:04 +0200 Subject: [PATCH 13/18] Reject risk score classifications with blank URLs (#350) --- .../validation/RiskScoreClassificationValidator.java | 10 ++++------ .../RiskScoreClassificationValidatorTest.java | 12 +++++------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/appconfig/validation/RiskScoreClassificationValidator.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/appconfig/validation/RiskScoreClassificationValidator.java index 66ef3cee7d..f04184a992 100644 --- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/appconfig/validation/RiskScoreClassificationValidator.java +++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/appconfig/validation/RiskScoreClassificationValidator.java @@ -87,12 +87,10 @@ private void validateRiskScoreValueBounds(int value) { } private void validateUrl(String url) { - if (!url.isBlank()) { - try { - new URL(url); - } catch (MalformedURLException e) { - errors.add(new RiskScoreClassificationValidationError("url", url, INVALID_URL)); - } + try { + new URL(url.trim()); + } catch (MalformedURLException e) { + errors.add(new RiskScoreClassificationValidationError("url", url, INVALID_URL)); } } diff --git a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/appconfig/validation/RiskScoreClassificationValidatorTest.java b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/appconfig/validation/RiskScoreClassificationValidatorTest.java index 87f3187ca7..da6f5829fb 100644 --- a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/appconfig/validation/RiskScoreClassificationValidatorTest.java +++ b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/appconfig/validation/RiskScoreClassificationValidatorTest.java @@ -39,19 +39,19 @@ class RiskScoreClassificationValidatorTest { private final static int MAX_SCORE = ParameterSpec.RISK_SCORE_MAX; private final static String VALID_LABEL = "myLabel"; - private final static String VALID_URL = ""; + private final static String VALID_URL = "https://www.my.url"; @ParameterizedTest @ValueSource(strings = {"", " "}) void failsForBlankLabels(String invalidLabel) { - var validator = buildValidator(buildRiskClass(invalidLabel, 0, MAX_SCORE, "")); + var validator = buildValidator(buildRiskClass(invalidLabel, 0, MAX_SCORE, VALID_URL)); var expectedResult = buildExpectedResult(buildError("label", invalidLabel, ErrorType.BLANK_LABEL)); assertThat(validator.validate()).isEqualTo(expectedResult); } @ParameterizedTest - @ValueSource(strings = {"invalid.Url", "invalid-url", "$$$://invalid.url"}) + @ValueSource(strings = {"invalid.Url", "invalid-url", "$$$://invalid.url", "", " "}) void failsForInvalidUrl(String invalidUrl) { var validator = buildValidator(buildRiskClass(VALID_LABEL, 0, MAX_SCORE, invalidUrl)); var expectedResult = buildExpectedResult(buildError("url", invalidUrl, ErrorType.INVALID_URL)); @@ -133,10 +133,8 @@ void doesNotFailForValidClassification(RiskScoreClassification validClassificati private static Stream createValidClassifications() { return Stream.of( - // blank url - buildClassification(buildRiskClass(VALID_LABEL, 0, MAX_SCORE, "")), - // legit url - buildClassification(buildRiskClass(VALID_LABEL, 0, MAX_SCORE, "http://www.sap.com")), + // valid url + buildClassification(buildRiskClass(VALID_LABEL, 0, MAX_SCORE, VALID_URL)), // [0:MAX_SCORE/2][MAX_SCORE/2:MAX_SCORE] buildClassification( buildRiskClass(VALID_LABEL, 0, MAX_SCORE / 2, VALID_URL), From 3c6da45d831f65c8ca5945bd4748ac970622fa19 Mon Sep 17 00:00:00 2001 From: Pit Humke Date: Thu, 28 May 2020 13:31:55 +0200 Subject: [PATCH 14/18] Update verification key info and add dev app bundle id (#355) * Update verification key info and add ...-dev app bundle id to dev profile * UPdate readme --- README.md | 5 ++--- .../distribution/src/main/resources/application-dev.yaml | 5 +++++ services/distribution/src/main/resources/application.yaml | 6 +++--- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 262d242ee6..42d309588b 100644 --- a/README.md +++ b/README.md @@ -103,9 +103,8 @@ After you made sure that the specified dependencies are running, configure them * Configure the Postgres connection in the [submission config](./services/submission/src/main/resources/application.yaml) and in the [distribution config](./services/distribution/src/main/resources/application.yaml) * Configure the S3 compatible object storage in the [distribution config](./services/distribution/src/main/resources/application.yaml) -* Configure the certificate and private key for the distribution service, the paths need to be prefixed with `file:` +* Configure the private key for the distribution service, the path need to be prefixed with `file:` * `VAULT_FILESIGNING_SECRET` should be the path to the private key, example available in `/docker-compose-test-secrets/private.pem` - * `VAULT_FILESIGNING_CERT` should be the path to the certificate, example available in `/docker-compose-test-secrets/certificate.cert` #### Build @@ -147,7 +146,7 @@ Distribution Service | [services/distribution/api_v1.json)](https://github. Profile | Effect -------------|------------- -`dev` | Turns the log level to `DEBUG`. +`dev` | Turns the log level to `DEBUG` and sets the app package ID in the export packages' signature info to `de.rki.coronawarnapp-dev` so that test certificates (instead of production certificates) will be used for client-side validation. `cloud` | Removes default values for the `datasource` and `objectstore` configurations. `demo` | Includes incomplete days and hours into the distribution run, thus creating aggregates for the current day and the current hour (and including both in the respective indices). When running multiple distributions in one hour with this profile, the date aggregate for today and the hours aggregate for the current hour will be updated and overwritten. `testdata` | Causes test data to be inserted into the database before each distribution run. By default, around 1000 random diagnosis keys will be generated per hour. If there are no diagnosis keys in the database yet, random keys will be generated for every hour from the beginning of the retention period (14 days ago at 00:00 UTC) until one hour before the present hour. If there are already keys in the database, the random keys will be generated for every hour from the latest diagnosis key in the database (by submission timestamp) until one hour before the present hour (or none at all, if the latest diagnosis key in the database was submitted one hour ago or later). diff --git a/services/distribution/src/main/resources/application-dev.yaml b/services/distribution/src/main/resources/application-dev.yaml index e437ef440a..40fae9b692 100644 --- a/services/distribution/src/main/resources/application-dev.yaml +++ b/services/distribution/src/main/resources/application-dev.yaml @@ -6,3 +6,8 @@ logging: web: DEBUG app: coronawarn: DEBUG + +services: + distribution: + signature: + app-bundle-id: de.rki.coronawarnapp-dev \ No newline at end of file diff --git a/services/distribution/src/main/resources/application.yaml b/services/distribution/src/main/resources/application.yaml index 09fce01d28..040f31953f 100644 --- a/services/distribution/src/main/resources/application.yaml +++ b/services/distribution/src/main/resources/application.yaml @@ -32,9 +32,9 @@ services: app-config-file-name: app_config signature: app-bundle-id: de.rki.coronawarnapp - android-package: de.rki.coronawarnapp - verification-key-id: - verification-key-version: + android-package: + verification-key-id: 262 + verification-key-version: v1 algorithm-oid: 1.2.840.10045.4.3.2 algorithm-name: SHA256withECDSA file-name: export.sig From 92f3c9bd98420cd26c4216cace3e481874be6140 Mon Sep 17 00:00:00 2001 From: Pit Humke Date: Thu, 28 May 2020 14:01:11 +0200 Subject: [PATCH 15/18] Remove android package from proto (#356) --- .../temporary_exposure_key_export.proto | 8 +++----- .../distribution/config/DistributionServiceConfig.java | 10 ---------- .../distribution/src/main/resources/application.yaml | 1 - .../decorator/signing/SigningDecoratorTest.java | 5 ++--- .../distribution/src/test/resources/application.yaml | 5 ++--- 5 files changed, 7 insertions(+), 22 deletions(-) diff --git a/common/protocols/src/main/proto/app/coronawarn/server/common/protocols/external/exposurenotification/temporary_exposure_key_export.proto b/common/protocols/src/main/proto/app/coronawarn/server/common/protocols/external/exposurenotification/temporary_exposure_key_export.proto index 075614b7ed..38f7e3fd98 100644 --- a/common/protocols/src/main/proto/app/coronawarn/server/common/protocols/external/exposurenotification/temporary_exposure_key_export.proto +++ b/common/protocols/src/main/proto/app/coronawarn/server/common/protocols/external/exposurenotification/temporary_exposure_key_export.proto @@ -21,17 +21,15 @@ message TemporaryExposureKeyExport { message SignatureInfo { // Apple App Store Application Bundle ID optional string app_bundle_id = 1; - // Android App package name - optional string android_package = 2; // Key version for rollovers // Must be in character class [a-zA-Z0-9_] - optional string verification_key_version = 3; + optional string verification_key_version = 2; // Alias with which to identify public key to be used for verification // Must be in character class [a-zA-Z0-9_] - optional string verification_key_id = 4; + optional string verification_key_id = 3; // ASN.1 OID for Algorithm Identifier. Supported algorithms are // either 1.2.840.10045.4.3.2 or 1.2.840.10045.4.3.4 - optional string signature_algorithm = 5; + optional string signature_algorithm = 4; } message TemporaryExposureKey { // Key of infected user diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/config/DistributionServiceConfig.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/config/DistributionServiceConfig.java index 385dab901b..d0a4882bc6 100644 --- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/config/DistributionServiceConfig.java +++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/config/DistributionServiceConfig.java @@ -263,7 +263,6 @@ public void setAppConfigFileName(String appConfigFileName) { public static class Signature { private String appBundleId; - private String androidPackage; private String verificationKeyId; private String verificationKeyVersion; private String algorithmOid; @@ -279,14 +278,6 @@ public void setAppBundleId(String appBundleId) { this.appBundleId = appBundleId; } - public String getAndroidPackage() { - return androidPackage; - } - - public void setAndroidPackage(String androidPackage) { - this.androidPackage = androidPackage; - } - public String getVerificationKeyId() { return verificationKeyId; } @@ -341,7 +332,6 @@ public void setSecurityProvider(String securityProvider) { public SignatureInfo getSignatureInfo() { return SignatureInfo.newBuilder() .setAppBundleId(this.getAppBundleId()) - .setAndroidPackage(this.getAndroidPackage()) .setVerificationKeyVersion(this.getVerificationKeyVersion()) .setVerificationKeyId(this.getVerificationKeyId()) .setSignatureAlgorithm(this.getAlgorithmOid()) diff --git a/services/distribution/src/main/resources/application.yaml b/services/distribution/src/main/resources/application.yaml index 040f31953f..d5211b3c73 100644 --- a/services/distribution/src/main/resources/application.yaml +++ b/services/distribution/src/main/resources/application.yaml @@ -32,7 +32,6 @@ services: app-config-file-name: app_config signature: app-bundle-id: de.rki.coronawarnapp - android-package: verification-key-id: 262 verification-key-version: v1 algorithm-oid: 1.2.840.10045.4.3.2 diff --git a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/structure/archive/decorator/signing/SigningDecoratorTest.java b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/structure/archive/decorator/signing/SigningDecoratorTest.java index edb5b0c8ef..7b2fa45e56 100644 --- a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/structure/archive/decorator/signing/SigningDecoratorTest.java +++ b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/structure/archive/decorator/signing/SigningDecoratorTest.java @@ -105,10 +105,9 @@ void checkSignatureInfo() { SignatureInfo signatureInfo = signatureList.getSignaturesList().get(0).getSignatureInfo(); assertThat(signatureInfo.getAppBundleId()).isEqualTo("de.rki.coronawarnapp"); - assertThat(signatureInfo.getAndroidPackage()).isEqualTo("de.rki.coronawarnapp"); assertThat(signatureInfo.getSignatureAlgorithm()).isEqualTo("1.2.840.10045.4.3.2"); - assertThat(signatureInfo.getVerificationKeyId()).isEmpty(); - assertThat(signatureInfo.getVerificationKeyVersion()).isEmpty(); + assertThat(signatureInfo.getVerificationKeyId()).isEqualTo("262"); + assertThat(signatureInfo.getVerificationKeyVersion()).isEqualTo("v1"); } @Test diff --git a/services/distribution/src/test/resources/application.yaml b/services/distribution/src/test/resources/application.yaml index 128636aae3..fc0cfae272 100644 --- a/services/distribution/src/test/resources/application.yaml +++ b/services/distribution/src/test/resources/application.yaml @@ -30,9 +30,8 @@ services: app-config-file-name: app_config signature: app-bundle-id: de.rki.coronawarnapp - android-package: de.rki.coronawarnapp - verification-key-id: - verification-key-version: + verification-key-id: 262 + verification-key-version: v1 algorithm-oid: 1.2.840.10045.4.3.2 algorithm-name: SHA256withECDSA file-name: export.sig From ef4edb4c92fb9bf65544e22e2b4f2e8f8a55fa97 Mon Sep 17 00:00:00 2001 From: Timon G Date: Thu, 28 May 2020 14:43:37 +0200 Subject: [PATCH 16/18] Add S3 Retention Period (#346) * apply s3 retention policy (doesn't do anything yet) * Actually deleting files * working * revert apply s3 retention policy (can't be used with our setup) * checkstyle * remove tmp javadoc. * formatting * add testing for S3 retention policy * rewrite stream * update tests * Refactor S3Retention * add new test * throw UncheckedIOException instead of RuntimeException Co-authored-by: Michael Burwig --- .../objectstore/ObjectStoreAccess.java | 8 +- .../objectstore/S3RetentionPolicy.java | 90 ++++++++++++++++++ .../distribution/runner/RetentionPolicy.java | 10 +- .../objectstore/S3RetentionPolicyTest.java | 92 +++++++++++++++++++ 4 files changed, 194 insertions(+), 6 deletions(-) create mode 100644 services/distribution/src/main/java/app/coronawarn/server/services/distribution/objectstore/S3RetentionPolicy.java create mode 100644 services/distribution/src/test/java/app/coronawarn/server/services/distribution/objectstore/S3RetentionPolicyTest.java diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/objectstore/ObjectStoreAccess.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/objectstore/ObjectStoreAccess.java index adec54bb0f..44b7d05f61 100644 --- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/objectstore/ObjectStoreAccess.java +++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/objectstore/ObjectStoreAccess.java @@ -129,7 +129,7 @@ public void putObject(LocalFile localFile) * * @param prefix the prefix, e.g. my/folder/ */ - public List deleteObjectsWithPrefix(String prefix) + public void deleteObjectsWithPrefix(String prefix) throws MinioException, GeneralSecurityException, IOException { List toDelete = getObjectsWithPrefix(prefix) .stream() @@ -144,9 +144,9 @@ public List deleteObjectsWithPrefix(String prefix) errors.add(deleteErrorResult.get()); } - logger.info("Deletion result: {}", errors.size()); - - return errors; + if (!errors.isEmpty()) { + throw new MinioException("Can't delete files, number of errors: " + errors.size()); + } } /** diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/objectstore/S3RetentionPolicy.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/objectstore/S3RetentionPolicy.java new file mode 100644 index 0000000000..0ff308600c --- /dev/null +++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/objectstore/S3RetentionPolicy.java @@ -0,0 +1,90 @@ +/* + * Corona-Warn-App + * + * SAP SE and all other contributors / + * copyright owners license this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this + * file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package app.coronawarn.server.services.distribution.objectstore; + +import app.coronawarn.server.services.distribution.config.DistributionServiceConfig; +import app.coronawarn.server.services.distribution.config.DistributionServiceConfig.Api; +import io.minio.errors.MinioException; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.security.GeneralSecurityException; +import java.time.LocalDate; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Creates an S3RetentionPolicy object, which applies the retention policy to the S3 compatible storage. + */ +@Component +public class S3RetentionPolicy { + + private final ObjectStoreAccess objectStoreAccess; + private final Api api; + + @Autowired + public S3RetentionPolicy(ObjectStoreAccess objectStoreAccess, DistributionServiceConfig distributionServiceConfig) { + this.objectStoreAccess = objectStoreAccess; + this.api = distributionServiceConfig.getApi(); + } + + /** + * Deletes all diagnosis-key files from S3 that are older than retentionDays. + * + * @param retentionDays the number of days, that files should be retained on S3. + */ + public void applyRetentionPolicy(int retentionDays) throws MinioException, GeneralSecurityException, IOException { + List diagnosisKeysObjects = objectStoreAccess.getObjectsWithPrefix("version/v1/" + + api.getDiagnosisKeysPath() + "/" + + api.getCountryPath() + "/" + + api.getCountryGermany() + "/" + + api.getDatePath() + "/"); + final String regex = ".*([0-9]{4}-[0-9]{2}-[0-9]{2}).*"; + final Pattern pattern = Pattern.compile(regex); + + final LocalDate cutOffDate = LocalDate.now(ZoneOffset.UTC).minusDays(retentionDays); + + diagnosisKeysObjects.stream() + .filter(diagnosisKeysObject -> { + Matcher matcher = pattern.matcher(diagnosisKeysObject.getObjectName()); + return matcher.matches() && LocalDate.parse(matcher.group(1), DateTimeFormatter.ISO_LOCAL_DATE) + .isBefore(cutOffDate); + }) + .forEach(this::deleteDiagnosisKey); + } + + /** + * Java stream do not support checked exceptions within streams. This helper method rethrows them as unchecked + * expressions, so they can be passed up to the Retention Policy. + * + * @param diagnosisKey the diagnosis key, that should be deleted. + */ + public void deleteDiagnosisKey(S3Object diagnosisKey) { + try { + objectStoreAccess.deleteObjectsWithPrefix(diagnosisKey.getObjectName()); + } catch (Exception e) { + throw new UncheckedIOException(new IOException(e)); + } + } +} diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/runner/RetentionPolicy.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/runner/RetentionPolicy.java index f9eae32d4a..b81fe3a03c 100644 --- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/runner/RetentionPolicy.java +++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/runner/RetentionPolicy.java @@ -22,6 +22,7 @@ import app.coronawarn.server.common.persistence.service.DiagnosisKeyService; import app.coronawarn.server.services.distribution.Application; import app.coronawarn.server.services.distribution.config.DistributionServiceConfig; +import app.coronawarn.server.services.distribution.objectstore.S3RetentionPolicy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.ApplicationArguments; @@ -46,27 +47,32 @@ public class RetentionPolicy implements ApplicationRunner { private final Integer retentionDays; + private final S3RetentionPolicy s3RetentionPolicy; + /** * Creates a new RetentionPolicy. */ RetentionPolicy(DiagnosisKeyService diagnosisKeyService, ApplicationContext applicationContext, - DistributionServiceConfig distributionServiceConfig) { + DistributionServiceConfig distributionServiceConfig, + S3RetentionPolicy s3RetentionPolicy) { this.diagnosisKeyService = diagnosisKeyService; this.applicationContext = applicationContext; this.retentionDays = distributionServiceConfig.getRetentionDays(); + this.s3RetentionPolicy = s3RetentionPolicy; } @Override public void run(ApplicationArguments args) { try { diagnosisKeyService.applyRetentionPolicy(retentionDays); + s3RetentionPolicy.applyRetentionPolicy(retentionDays); } catch (Exception e) { logger.error("Application of retention policy failed.", e); Application.killApplication(applicationContext); } - logger.debug("Retention policy applied successfully. Deleted all entries older that {} days.", + logger.debug("Retention policy applied successfully. Deleted all entries older than {} days.", retentionDays); } } diff --git a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/objectstore/S3RetentionPolicyTest.java b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/objectstore/S3RetentionPolicyTest.java new file mode 100644 index 0000000000..4cf5a94424 --- /dev/null +++ b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/objectstore/S3RetentionPolicyTest.java @@ -0,0 +1,92 @@ +/* + * Corona-Warn-App + * + * SAP SE and all other contributors / + * copyright owners license this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this + * file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package app.coronawarn.server.services.distribution.objectstore; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import app.coronawarn.server.services.distribution.config.DistributionServiceConfig; +import app.coronawarn.server.services.distribution.config.DistributionServiceConfig.ObjectStore; +import io.minio.errors.MinioException; +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.time.LocalDate; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.context.ConfigFileApplicationContextInitializer; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +@EnableConfigurationProperties(value = DistributionServiceConfig.class) +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = {S3RetentionPolicy.class, ObjectStore.class}, + initializers = ConfigFileApplicationContextInitializer.class) +class S3RetentionPolicyTest { + + @MockBean + private ObjectStoreAccess objectStoreAccess; + + @Autowired + private S3RetentionPolicy s3RetentionPolicy; + + @Autowired DistributionServiceConfig distributionServiceConfig; + + @Test + void shouldDeleteOldFiles() throws IOException, GeneralSecurityException, MinioException { + String expectedFileToBeDeleted = generateFileName(LocalDate.now().minusDays(2)); + + when(objectStoreAccess.getObjectsWithPrefix(any())).thenReturn(List.of( + new S3Object(expectedFileToBeDeleted), + new S3Object(generateFileName(LocalDate.now())), + new S3Object("version/v1/configuration/country/DE/app_config"))); + + s3RetentionPolicy.applyRetentionPolicy(1); + + verify(objectStoreAccess, atLeastOnce()).deleteObjectsWithPrefix(eq(expectedFileToBeDeleted)); + } + + @Test + void shouldNotDeleteFilesIfAllAreValid() throws IOException, GeneralSecurityException, MinioException { + when(objectStoreAccess.getObjectsWithPrefix(any())).thenReturn(List.of( + new S3Object(generateFileName(LocalDate.now().minusDays(1))), + new S3Object(generateFileName(LocalDate.now().plusDays(1))), + new S3Object(generateFileName(LocalDate.now())), + new S3Object("version/v1/configuration/country/DE/app_config"))); + + s3RetentionPolicy.applyRetentionPolicy(1); + + verify(objectStoreAccess, never()).deleteObjectsWithPrefix(any()); + } + + private String generateFileName(LocalDate date) { + var api = distributionServiceConfig.getApi(); + + return "version/v1/" + api.getDiagnosisKeysPath() + "/" + api.getCountryPath() + "/" + + api.getCountryGermany() + "/" + api.getDatePath() + "/" + date.toString() + "/hour/0"; + } +} From 911f8d8087e0327dc24da8c3e8a77827d5d56f2b Mon Sep 17 00:00:00 2001 From: Michael Burwig Date: Thu, 28 May 2020 15:04:02 +0200 Subject: [PATCH 17/18] Add cache control headers for object store writes (#349) * Extract object store client instantiation * Set cache-control header for object store writes --- .../objectstore/ObjectStoreAccess.java | 61 ++++------ .../objectstore/ObjectStoreClientConfig.java | 66 ++++++++++ .../ObjectStoreAccessUnitTest.java | 113 ++++++++++++++++++ 3 files changed, 205 insertions(+), 35 deletions(-) create mode 100644 services/distribution/src/main/java/app/coronawarn/server/services/distribution/objectstore/ObjectStoreClientConfig.java create mode 100644 services/distribution/src/test/java/app/coronawarn/server/services/distribution/objectstore/ObjectStoreAccessUnitTest.java diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/objectstore/ObjectStoreAccess.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/objectstore/ObjectStoreAccess.java index 44b7d05f61..7a58fc15bb 100644 --- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/objectstore/ObjectStoreAccess.java +++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/objectstore/ObjectStoreAccess.java @@ -20,19 +20,17 @@ package app.coronawarn.server.services.distribution.objectstore; import app.coronawarn.server.services.distribution.config.DistributionServiceConfig; -import app.coronawarn.server.services.distribution.config.DistributionServiceConfig.ObjectStore; import app.coronawarn.server.services.distribution.objectstore.publish.LocalFile; import io.minio.MinioClient; import io.minio.PutObjectOptions; import io.minio.Result; -import io.minio.errors.InvalidEndpointException; -import io.minio.errors.InvalidPortException; import io.minio.errors.MinioException; import io.minio.messages.DeleteError; import io.minio.messages.Item; import java.io.IOException; import java.security.GeneralSecurityException; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -58,7 +56,11 @@ public class ObjectStoreAccess { private static final Logger logger = LoggerFactory.getLogger(ObjectStoreAccess.class); - private static final String DEFAULT_REGION = "eu-west-1"; + /** + * Specifies the default maximum amount of time in seconds that a published resource can be considered "fresh" when + * held in a cache. + */ + public static final int DEFAULT_MAX_CACHE_AGE = 300; private final boolean isSetPublicReadAclOnPutObject; @@ -71,14 +73,14 @@ public class ObjectStoreAccess { * bucket. * * @param distributionServiceConfig The config properties + * @param minioClient The client used for interaction with the object store * @throws IOException When there were problems creating the S3 client * @throws GeneralSecurityException When there were problems creating the S3 client * @throws MinioException When there were problems creating the S3 client */ - ObjectStoreAccess(DistributionServiceConfig distributionServiceConfig) + ObjectStoreAccess(DistributionServiceConfig distributionServiceConfig, MinioClient minioClient) throws IOException, GeneralSecurityException, MinioException { - this.client = createClient(distributionServiceConfig.getObjectStore()); - + this.client = minioClient; this.bucket = distributionServiceConfig.getObjectStore().getBucket(); this.isSetPublicReadAclOnPutObject = distributionServiceConfig.getObjectStore().isSetPublicReadAclOnPutObject(); @@ -87,38 +89,25 @@ public class ObjectStoreAccess { } } - private MinioClient createClient(ObjectStore objectStore) - throws InvalidPortException, InvalidEndpointException { - if (isSsl(objectStore)) { - return new MinioClient( - objectStore.getEndpoint(), - objectStore.getPort(), - objectStore.getAccessKey(), objectStore.getSecretKey(), - DEFAULT_REGION, - true - ); - } else { - return new MinioClient( - objectStore.getEndpoint(), - objectStore.getPort(), - objectStore.getAccessKey(), objectStore.getSecretKey() - ); - } - } - - private boolean isSsl(ObjectStore objectStore) { - return objectStore.getEndpoint().startsWith("https://"); + /** + * Stores the target file on the S3 and sets cache control headers according to the default maximum age value. + * + * @param localFile The file to be published. + */ + public void putObject(LocalFile localFile) throws IOException, GeneralSecurityException, MinioException { + putObject(localFile, DEFAULT_MAX_CACHE_AGE); } /** - * Stores the target file on the S3. + * Stores the target file on the S3 and sets cache control headers according to the specified maximum age value. * - * @param localFile the file to be published + * @param localFile The file to be published. + * @param maxAge A cache control parameter that specifies the maximum amount of time in seconds that a resource can + * be considered "fresh" when held in a cache. */ - public void putObject(LocalFile localFile) - throws IOException, GeneralSecurityException, MinioException { + public void putObject(LocalFile localFile, int maxAge) throws IOException, GeneralSecurityException, MinioException { String s3Key = localFile.getS3Key(); - PutObjectOptions options = createOptionsFor(localFile); + PutObjectOptions options = createOptionsFor(localFile, maxAge); logger.info("... uploading {}", s3Key); this.client.putObject(bucket, s3Key, localFile.getFile().toString(), options); @@ -167,12 +156,14 @@ public List getObjectsWithPrefix(String prefix) return list; } - private PutObjectOptions createOptionsFor(LocalFile file) { + private PutObjectOptions createOptionsFor(LocalFile file, int maxAge) { var options = new PutObjectOptions(file.getFile().toFile().length(), -1); + Map headers = new HashMap<>(Map.of("cache-control", "public,max-age=" + maxAge)); if (this.isSetPublicReadAclOnPutObject) { - options.setHeaders(Map.of("x-amz-acl", "public-read")); + headers.put("x-amz-acl", "public-read"); } + options.setHeaders(headers); return options; } diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/objectstore/ObjectStoreClientConfig.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/objectstore/ObjectStoreClientConfig.java new file mode 100644 index 0000000000..e623d94723 --- /dev/null +++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/objectstore/ObjectStoreClientConfig.java @@ -0,0 +1,66 @@ +/* + * Corona-Warn-App + * + * SAP SE and all other contributors / + * copyright owners license this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this + * file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package app.coronawarn.server.services.distribution.objectstore; + +import app.coronawarn.server.services.distribution.config.DistributionServiceConfig; +import app.coronawarn.server.services.distribution.config.DistributionServiceConfig.ObjectStore; +import io.minio.MinioClient; +import io.minio.errors.InvalidEndpointException; +import io.minio.errors.InvalidPortException; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Manages the instantiation of the {@link MinioClient} bean. + */ +@Configuration +public class ObjectStoreClientConfig { + + private static final String DEFAULT_REGION = "eu-west-1"; + + @Bean + public MinioClient createMinioClient(DistributionServiceConfig distributionServiceConfig) + throws InvalidPortException, InvalidEndpointException { + return createClient(distributionServiceConfig.getObjectStore()); + } + + private MinioClient createClient(ObjectStore objectStore) + throws InvalidPortException, InvalidEndpointException { + if (isSsl(objectStore)) { + return new MinioClient( + objectStore.getEndpoint(), + objectStore.getPort(), + objectStore.getAccessKey(), objectStore.getSecretKey(), + DEFAULT_REGION, + true + ); + } else { + return new MinioClient( + objectStore.getEndpoint(), + objectStore.getPort(), + objectStore.getAccessKey(), objectStore.getSecretKey() + ); + } + } + + private boolean isSsl(ObjectStore objectStore) { + return objectStore.getEndpoint().startsWith("https://"); + } +} diff --git a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/objectstore/ObjectStoreAccessUnitTest.java b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/objectstore/ObjectStoreAccessUnitTest.java new file mode 100644 index 0000000000..ab187dfc66 --- /dev/null +++ b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/objectstore/ObjectStoreAccessUnitTest.java @@ -0,0 +1,113 @@ +/* + * Corona-Warn-App + * + * SAP SE and all other contributors / + * copyright owners license this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this + * file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package app.coronawarn.server.services.distribution.objectstore; + +import static java.util.Map.entry; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import app.coronawarn.server.services.distribution.config.DistributionServiceConfig; +import app.coronawarn.server.services.distribution.objectstore.publish.LocalFile; +import io.minio.MinioClient; +import io.minio.PutObjectOptions; +import java.io.File; +import java.nio.file.Path; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.context.ConfigFileApplicationContextInitializer; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +@EnableConfigurationProperties(value = DistributionServiceConfig.class) +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = {MinioClient.class}, initializers = ConfigFileApplicationContextInitializer.class) +class ObjectStoreAccessUnitTest { + + private static final String EXP_S3_KEY = "fooS3Key"; + private static final String EXP_FILE_CONTENT = "barFileContent"; + + private final DistributionServiceConfig distributionServiceConfig; + private final String expBucketName; + private LocalFile testLocalFile; + private ObjectStoreAccess objectStoreAccess; + + @MockBean + private MinioClient minioClient; + + @Autowired + public ObjectStoreAccessUnitTest(DistributionServiceConfig distributionServiceConfig) { + this.distributionServiceConfig = distributionServiceConfig; + this.expBucketName = distributionServiceConfig.getObjectStore().getBucket(); + } + + @BeforeEach + public void setUpMocks() throws Exception { + when(minioClient.bucketExists(any())).thenReturn(true); + this.objectStoreAccess = new ObjectStoreAccess(distributionServiceConfig, minioClient); + this.testLocalFile = setUpLocalFileMock(); + } + + private LocalFile setUpLocalFileMock() { + var testLocalFile = mock(LocalFile.class); + var testPath = mock(Path.class); + + when(testLocalFile.getS3Key()).thenReturn(EXP_S3_KEY); + when(testLocalFile.getFile()).thenReturn(testPath); + when(testPath.toFile()).thenReturn(mock(File.class)); + when(testPath.toString()).thenReturn(EXP_FILE_CONTENT); + + return testLocalFile; + } + + @Test + void testPutObjectSetsDefaultCacheControlHeader() throws Exception { + ArgumentCaptor options = ArgumentCaptor.forClass(PutObjectOptions.class); + var expHeader = entry("cache-control", "public,max-age=" + ObjectStoreAccess.DEFAULT_MAX_CACHE_AGE); + + objectStoreAccess.putObject(testLocalFile); + + verify(minioClient, atLeastOnce()) + .putObject(eq(expBucketName), eq(EXP_S3_KEY), eq(EXP_FILE_CONTENT), options.capture()); + Assertions.assertThat(options.getValue().headers()).contains(expHeader); + } + + @Test + void testPutObjectSetsSpecifiedCacheControlHeader() throws Exception { + ArgumentCaptor options = ArgumentCaptor.forClass(PutObjectOptions.class); + var expMaxAge = 1337; + var expHeader = entry("cache-control", "public,max-age=" + expMaxAge); + + objectStoreAccess.putObject(testLocalFile, expMaxAge); + + verify(minioClient, atLeastOnce()) + .putObject(eq(expBucketName), eq(EXP_S3_KEY), eq(EXP_FILE_CONTENT), options.capture()); + Assertions.assertThat(options.getValue().headers()).contains(expHeader); + } +} From 8b46784f7eb5345c10a62013e64c245c70b367b4 Mon Sep 17 00:00:00 2001 From: Ole Lilienthal Date: Thu, 28 May 2020 15:07:25 +0200 Subject: [PATCH 18/18] Release new version 0.5.1 --- .mvn/maven.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.mvn/maven.config b/.mvn/maven.config index fdc22404ff..152676d49f 100644 --- a/.mvn/maven.config +++ b/.mvn/maven.config @@ -1 +1 @@ --Drevision=0.5.1-SNAPSHOT +-Drevision=0.5.1