diff --git a/README.md b/README.md index 7251fe065..a491828fb 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -# Arrowhead Framework 4.4.0 +# Arrowhead Framework 4.3.0 [Arrowhead](http://www.arrowhead.eu/) (and its continuation, [Productive4.0](https://productive40.eu/)) is an ambitious holistic innovation project, meant to open the doors to the potentials of Digital Industry and to maintain a leadership position of the industries in Europe. All partners involved will work on creating the capability to efficiently design and integrate hardware and software of Internet of Things (IoT) devices. Linking the real with the digital world takes more than just adding software to the hardware. @@ -114,12 +114,13 @@ Please be aware, that versions starting from 4.1.3 are __NOT__ backwards compati * [Onboarding](#systemregistry_endpoints_onboarding) * [Client](#systemregistry_endpoints_client) 12. [Choreographer](#choreographer) - * [System Design Description Overview](#choreographer_sdd) - * [Services and Use Cases](#choreographer_usecases) - * [Endpoints](#choreographer_endpoints) - * [Client](#choreographer_endpoints_client) - * [Management](#choreographer_endpoints_mgmt) + * [System Design Description Overview](#choreographer_sdd) + * [Services and Use Cases](#choreographer_usecases) + * [Endpoints](#choreographer_endpoints) + * [Client](#choreographer_endpoints_client) + * [Management](#choreographer_endpoints_mgmt) 13. [Plant Description Engine](#plant-description-engine) + 14. [HawkBit Configuration Manager](#hawkbit_configuration_manager) @@ -8405,3 +8406,10 @@ The PDE consumes the following services: [Service Discovery]:README.md#serviceregistry_usecases [Service Registry]:README.md#serviceregistry + + +# HawkBit Configuration Manager + + +The HawkBit configuration manager is responsible for managing software rollouts and configuration updates to the actual hardware devices. +This implementation is based on [Eclipse HawkBit](https://www.eclipse.org/hawkbit/) and is explained in more detail [here](./hawkbit-configuration-manager/README.md). diff --git a/certificate-authority/.project b/certificate-authority/.project index 9b4a8b591..ce9b44060 100644 --- a/certificate-authority/.project +++ b/certificate-authority/.project @@ -10,6 +10,11 @@ + + org.springframework.ide.eclipse.boot.validation.springbootbuilder + + + org.eclipse.m2e.core.maven2Builder diff --git a/certificates/testcloud1/hawkbitconfigurationmanager.p12 b/certificates/testcloud1/hawkbitconfigurationmanager.p12 new file mode 100644 index 000000000..d262c535d Binary files /dev/null and b/certificates/testcloud1/hawkbitconfigurationmanager.p12 differ diff --git a/certificates/testcloud2/hawkbitconfigurationmanager.p12 b/certificates/testcloud2/hawkbitconfigurationmanager.p12 new file mode 100644 index 000000000..d7560b726 Binary files /dev/null and b/certificates/testcloud2/hawkbitconfigurationmanager.p12 differ diff --git a/configuration-manager/.gitignore b/configuration-manager/.gitignore new file mode 100644 index 000000000..17be33ce5 --- /dev/null +++ b/configuration-manager/.gitignore @@ -0,0 +1,10 @@ +# Ignore Maven build output directory +target + +# Custom files +.settings +.classpath +bin +.project +lombok.config +.vscode \ No newline at end of file diff --git a/configuration-manager/Dockerfile b/configuration-manager/Dockerfile new file mode 100644 index 000000000..0f03e1a85 --- /dev/null +++ b/configuration-manager/Dockerfile @@ -0,0 +1,21 @@ +#******************************************************************************* +# Copyright (c) 2020 Bosch.IO GmbH[ and others] + +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License 2.0 which is available at +# http://www.eclipse.org/legal/epl-2.0. + +# SPDX-License-Identifier: EPL-2.0 +#******************************************************************************* + +# Use jre 11 from openjdk +FROM openjdk:11-jre-slim + +# Execute application under a non root user for improved security +RUN adduser --system --group spring +USER spring:spring + +# Include the fat-jar and run it in the docker container +ARG JAR_FILE=target/arrowhead-configuration-manager-4.3.0.jar +COPY --chown=spring:spring ${JAR_FILE} /home/spring/app.jar +ENTRYPOINT ["java","-jar","/home/spring/app.jar"] \ No newline at end of file diff --git a/configuration-manager/README.md b/configuration-manager/README.md new file mode 100644 index 000000000..131329958 --- /dev/null +++ b/configuration-manager/README.md @@ -0,0 +1,69 @@ +# Configuration Manager +## Introduction +We identified [Eclipse hawkBit](https://www.eclipse.org/hawkbit/) to be a very good fit for an integration with the Arrowhead framework. +The wrapper code in this directory enables to integrate Eclipse hawkBit with the Arrowhead mandatory core systems and function as a configuration manager. +Eclipse hawkBit allows administrators of distributed systems, consisting of constrained, but also more powerful devices, such as contollers or gateways, to manage rollouts of configuration changes and software updates. +The featureset comprises the creation of cascading rollouts of consecutive device groups and the definition of error thresholds triggering emergency shutdowns of a rollout. + +Eclipse hawkBit consists of the following APIs and interfaces: +- Direct Device Integration (DDI): The DDI is a REST-based API that allows devices to frequently request hawkBit for configuration/software updates (polling). +- Device Management Federation (DMF): As the DDI only supports HTTP, it is necessary to enable developers to integrate new protocol implementations, as not every device includes an HTTP stack. The DDI therefore enables proprietary protocols and most importantly allows the integration of publish/subscribe based patterns, which are necessary to guarantee a faster rollout of configuration files and are also more efficient. +- Management UI: The Management UI allows users to manage their devices, plan and create new software/configuration updates, and deal with potential problems during the rollout +- Management API: the Management API allows the integration of other business solutions to integrate with the management interface of hawkBit + +This is the first version of the Eclipse hawkBit wrapper, which does support the basic hawkBit functionality, but should not be considered stable. +‚‚‚ +## Architecture‚‚ +The system consists of a Spring Boot application, which is responsible for managing connections with +- the Arrowhead systems, +- the [hawkBit DMF API](https://www.eclipse.org/hawkbit/apis/dmf_api/), +- and the devices. + +Currently, the configuration manager wrapper only supports devices that are able to communicate via WebSocket connection. +However, future implementations might integrate other protocols by adding other implementations for the Sping Boot service which interfaces the devices. + +## Authentication +The configuration manager uses a client certificate to connect to the Arrowhead Authorization system in order to retrieve the public key that is used to sign JWTs. +Therefore the configuration manager needs its own client certificate, as desribed [here](https://github.com/eclipse-arrowhead/core-java-spring#certificates). +To generate new credentials you can use the `generate-certificates.sh` script in the `certificates` directory. +You will need to modify the following properties: +- `CA_KEYSTORE` - the path to the keystore containing the cloud certificate/keys +- `CA_KEYSTORE_PASS` - password for the keystore +- `CLIENT_ID` - this is the ID/name of the core system (should comply with the Arrowhead CN naming conventions, e.g.: `configuration`) +- `CLOUD_DOMAIN` - the CN of the cloud certificate (results in the CN of the client certificate, when combined with the `CLIENT_ID`, e.g.: `example.corp.arrowhead.eu`) +- `CLIENT_KEYSTORE_PASS` - the password of the keystore +Also make sure to modify the `openssl.cnf` file and enter the correct subject alternative names in the `[alt_names]` section (e.g.: `IP.1 = 127.0.0.1` or `DNS.1 = example.com`). + +In order to work properly, you need to acquire the cloud certificate. +Change the file name in the `generate-certificates.sh` script using the `CA_KEYSTORE` variable and the corresponding password using the `CA_KEYSTORE_PASS` variable. +The resulting PKCS12 keystore will be named `${CLIENT_ID}.full.p12`. +The CN of the generated certificates is the combined value of `CLIENT_ID.CLOUD_DOMAIN`, e.g: configuration.example.corp.arrowhead.eu. +Don't forget to save the corresponding password, which will be printed after successful generation. +**Do not push any certificates!** + +There also exists a truststore, which is used by the configuration manager wrapper to validate the certificates of the Arrowhead core systems. +This truststore (file name `truststore.p12`) must be generated by using the cloud certificate, and then be placed in the `certificates` directory. +However, you can also modify the path in the `application.yml` +Arrowhead uses PKCS12 files to store certificates and keys. +For more information on how to generate the trust store see the [certificate section](https://github.com/arrowhead-f/core-java-spring#certificates) of the Arrowhead Framework documentation. + +## Compatibility +Currently, the following functionality of the hawkBit DMF API (version 0.3.0M6) is implemented and available to the user via WebSockets: + +| DMF API message type | supported | current version | +| --------------------------------- | ------------- | --------------- | +| THING_CREATED | ☑ | ☑ +| THING_REMOVED | ☑ | ☑ +| UPDATE_ATTRIBUTES | | +| UPDATE_ACTION_STATUS | ☑ | ☑ +| PING | | +| CANCEL_DOWNLOAD | ☑ | ☑ +| DOWNLOAD_AND_INSTALL or DOWNLOAD | ☑ | ☑ +| MULTI_ACTION | | +| THING_DELETED | ☑ | ☑ +| PING_RESPONSE | | + +## Deployment +To use the configuration manager, you need a running instance of Eclipse hawkBit. +You can use the [hawkBit image from Dockerhub](https://hub.docker.com/r/hawkbit/hawkbit-update-server) to deploy an instance and then wire up the configuration manager wrapper with it. +As part of this repository, there exists a `Dockerfile` that can be used to either build and run a container with the wrapper locally, or build and push the image into a private container registry. \ No newline at end of file diff --git a/configuration-manager/certificates/.gitignore b/configuration-manager/certificates/.gitignore new file mode 100644 index 000000000..003240794 --- /dev/null +++ b/configuration-manager/certificates/.gitignore @@ -0,0 +1,3 @@ +*.key +*.pem +*.csr \ No newline at end of file diff --git a/configuration-manager/certificates/example.corp.arrowhead.eu.p12 b/configuration-manager/certificates/example.corp.arrowhead.eu.p12 new file mode 100644 index 000000000..a3bf1ff6e Binary files /dev/null and b/configuration-manager/certificates/example.corp.arrowhead.eu.p12 differ diff --git a/configuration-manager/certificates/generate-certificates.sh b/configuration-manager/certificates/generate-certificates.sh new file mode 100755 index 000000000..24b608b58 --- /dev/null +++ b/configuration-manager/certificates/generate-certificates.sh @@ -0,0 +1,52 @@ +#!/bin/bash +#******************************************************************************* +# Copyright (c) 2020 Bosch.IO GmbH[ and others] + +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License 2.0 which is available at +# http://www.eclipse.org/legal/epl-2.0. + +# SPDX-License-Identifier: EPL-2.0 +#******************************************************************************* + +CLOUD_DOMAIN="example.corp.arrowhead.eu" +CLIENT_ID="configuration" +CLIENT_SERIAL=1 +CA_KEYSTORE="example.corp.arrowhead.eu.p12" +CA_KEYSTORE_PASS="aeloim9do8hoe7AaWo7johpe" +CA_CERT_NAME="ca-cert.pem" +CA_KEY_NAME="ca-key.key" +VALIDITY=3650 + +# Use the password from application.yaml +CLIENT_KEYSTORE_PASS="conf-test-pw" +KEY_PASS=${CLIENT_KEYSTORE_PASS} + + +# Generate rsa key +openssl genrsa -aes256 -passout pass:${KEY_PASS} -out ${CLIENT_ID}.pass.key 2048 +openssl rsa -passin pass:${KEY_PASS} -in ${CLIENT_ID}.pass.key -out ${CLIENT_ID}.key +rm ${CLIENT_ID}.pass.key + +# generate CSR +openssl req -new -key ${CLIENT_ID}.key -out ${CLIENT_ID}.csr -subj "/C=DE/ST=Berlin/L=Berlin/O=Arrowhead/OU=AHT/CN=${CLIENT_ID}.${CLOUD_DOMAIN}" -config openssl.cnf + +# Extract private key and certificate from CA +openssl pkcs12 -password pass:${CA_KEYSTORE_PASS} -in ${CA_KEYSTORE} -nodes -nocerts -out ${CA_KEY_NAME} +openssl pkcs12 -password pass:${CA_KEYSTORE_PASS} -in ${CA_KEYSTORE} -nodes -clcerts -out ${CA_CERT_NAME} + +# sign new certificate using CSR +openssl x509 -req -days ${VALIDITY} -in ${CLIENT_ID}.csr -CA ${CA_CERT_NAME} -CAkey ${CA_KEY_NAME} -set_serial ${CLIENT_SERIAL} -extfile openssl.cnf -extensions req_ext -out ${CLIENT_ID}.pem + +# combine to create PKCS +openssl pkcs12 -export -out ${CLIENT_ID}.full.p12 -name "${CLIENT_ID}.${CLOUD_DOMAIN}" -inkey ${CLIENT_ID}.key -in ${CLIENT_ID}.pem -certfile ${CA_CERT_NAME} -password pass:${CLIENT_KEYSTORE_PASS} + +echo "Successfully generated client keystore, use the following password to open:" +echo ${CLIENT_KEYSTORE_PASS} + +# delete byproducts +rm ${CA_KEY_NAME} +rm ${CA_CERT_NAME} +rm ${CLIENT_ID}.pem +rm ${CLIENT_ID}.csr +rm ${CLIENT_ID}.key \ No newline at end of file diff --git a/configuration-manager/certificates/openssl.cnf b/configuration-manager/certificates/openssl.cnf new file mode 100644 index 000000000..1c5865bd7 --- /dev/null +++ b/configuration-manager/certificates/openssl.cnf @@ -0,0 +1,13 @@ +[req] +req_extensions = req_ext +distinguished_name = req_distinguished_name + +[req_distinguished_name] + +[ req_ext ] +basicConstraints = CA:FALSE +keyUsage = digitalSignature, keyEncipherment +subjectAltName = @alt_names + +[alt_names] +IP.1 = \ No newline at end of file diff --git a/configuration-manager/pom.xml b/configuration-manager/pom.xml new file mode 100644 index 000000000..ddb402192 --- /dev/null +++ b/configuration-manager/pom.xml @@ -0,0 +1,204 @@ + + + + 4.0.0 + + eu.arrowhead + 4.3.0 + + arrowhead-configuration-manager + jar + Arrowhead Configuration Manager + An Arrowhead configuration manager system based on Eclipse HawkBit + + + 2.4.0 + 11 + 11 + 11 + + + + + org.projectlombok + lombok + 1.18.16 + provided + + + + org.springframework.boot + ${spring.boot.version} + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-logging + + + + + org.springframework.boot + ${spring.boot.version} + spring-boot-starter-webflux + + + org.springframework.boot + ${spring.boot.version} + spring-boot-starter-websocket + + + org.springframework.boot + ${spring.boot.version} + spring-boot-starter-security + + + org.springframework.boot + ${spring.boot.version} + spring-boot-starter-log4j2 + + + org.springframework.boot + ${spring.boot.version} + spring-boot-starter-cache + + + org.springframework.boot + ${spring.boot.version} + spring-boot-starter-validation + + + + javax.cache + cache-api + 1.1.1 + + + javax.annotation + javax.annotation-api + 1.3.2 + + + com.rabbitmq + amqp-client + 5.9.0 + + + org.bitbucket.b_c + jose4j + 0.6.5 + + + org.yaml + snakeyaml + 1.27 + + + + org.springframework.boot + ${spring.boot.version} + spring-boot-starter-test + test + + + org.springframework.security + spring-security-test + 5.3.3.RELEASE + test + + + org.mockito + mockito-core + 3.5.11 + test + + + org.mock-server + mockserver-netty + 5.11.1 + test + + + org.mock-server + mockserver-junit-jupiter + 5.11.1 + test + + + io.projectreactor + reactor-test + test + 3.4.2 + + + org.junit.jupiter + junit-jupiter + 5.6.2 + test + + + com.squareup.okhttp3 + mockwebserver + 4.7.2 + test + + + com.squareup.okhttp3 + okhttp + 4.7.2 + test + + + com.github.fridujo + rabbitmq-mock + 1.1.0 + test + + + org.awaitility + awaitility + 4.0.3 + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + + repackage + + + + eu.arrowhead.core.confmgr.ConfigurationSystemApplication + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.2 + + + *.java + + + + + + \ No newline at end of file diff --git a/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/ConfigurationSystemApplication.java b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/ConfigurationSystemApplication.java new file mode 100644 index 000000000..dada7b635 --- /dev/null +++ b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/ConfigurationSystemApplication.java @@ -0,0 +1,23 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class ConfigurationSystemApplication { + + public static void main(String[] args) { + SpringApplication.run(ConfigurationSystemApplication.class); + } + +} \ No newline at end of file diff --git a/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/Initialization.java b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/Initialization.java new file mode 100644 index 000000000..af5f5a054 --- /dev/null +++ b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/Initialization.java @@ -0,0 +1,98 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr; + +import java.io.IOException; +import java.security.PublicKey; +import java.util.Base64; +import java.util.Collections; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.client.WebClientResponseException; + +import eu.arrowhead.core.confmgr.arrowhead.ArrowheadServiceRegistryClient; +import eu.arrowhead.core.confmgr.arrowhead.model.request.ServiceRegistryRequestDTO; +import eu.arrowhead.core.confmgr.arrowhead.model.request.SystemRequestDTO; +import eu.arrowhead.core.confmgr.hawkbit.HawkbitDmfConsumer; +import eu.arrowhead.core.confmgr.properties.SystemProperties; +import lombok.extern.log4j.Log4j2; + +/** + * This class runs during the startup of the configuration system. It contains + * all logic for initialization, like the registration of the configuration + * system in Arrowhead. + */ +@Log4j2 +@Component +public class Initialization implements ApplicationRunner { + private final HawkbitDmfConsumer hawkbitDmfConsumer; + private final ArrowheadServiceRegistryClient arrowheadServiceRegistryClient; + private final SystemProperties systemProperties; + private final PublicKey publicKey; + + @Autowired + public Initialization(ArrowheadServiceRegistryClient arrowheadServiceRegistryClient, HawkbitDmfConsumer hawkbitDmfConsumer, SystemProperties systemProperties, PublicKey publicKey) { + this.arrowheadServiceRegistryClient = arrowheadServiceRegistryClient; + this.hawkbitDmfConsumer = hawkbitDmfConsumer; + this.systemProperties = systemProperties; + this.publicKey = publicKey; + } + + @Override + public void run(ApplicationArguments args) throws Exception { + registerOwnSystemInArrowhead(); + connectToHawkBit(); + } + + private void registerOwnSystemInArrowhead() { + String publicKeyString = Base64.getEncoder().encodeToString(this.publicKey.getEncoded()); + + ServiceRegistryRequestDTO requestDTO = ServiceRegistryRequestDTO.builder() + .serviceDefinition(this.systemProperties.getProvidedServiceDefinition()) + .providerSystem(SystemRequestDTO.builder() + .systemName(this.systemProperties.getName()) + .address(this.systemProperties.getAddress()) + .port(this.systemProperties.getPort()) + .authenticationInfo(publicKeyString) + .build()) + .serviceUri(this.systemProperties.getProvidedServiceUri()) + .secure(ServiceRegistryRequestDTO.SecurityLevel.TOKEN) + .version(this.systemProperties.getProvidedServiceVersion()) + .interfaces(Collections.singletonList(this.systemProperties.getProvidedServiceInterface())) + .build(); + + try { + log.info("Registering own system in Arrowhead"); + this.arrowheadServiceRegistryClient.registerService(requestDTO); + } catch (WebClientResponseException e) { + if (HttpStatus.BAD_REQUEST.equals(e.getStatusCode())) { + log.warn("Own system is already registered in Arrowhead"); + } else { + log.error("Error during registration of own system in Arrowhead", e); + } + } catch (Exception e) { + log.error("Error during registration of own system in Arrowhead", e); + } + } + + private void connectToHawkBit() { + try { + this.hawkbitDmfConsumer.subscribeToDownloadEvents(); + } catch (IOException e) { + log.error("Could not subscribe to Hawkbit DMF API"); + } + } + +} diff --git a/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/arrowhead/ArrowheadAuthorizationSystemClient.java b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/arrowhead/ArrowheadAuthorizationSystemClient.java new file mode 100644 index 000000000..368614fa7 --- /dev/null +++ b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/arrowhead/ArrowheadAuthorizationSystemClient.java @@ -0,0 +1,85 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.arrowhead; + +import org.springframework.http.client.reactive.ReactorClientHttpConnector; +import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.reactive.function.client.WebClientResponseException; + +import io.netty.handler.ssl.SslContext; +import lombok.extern.log4j.Log4j2; +import reactor.netty.http.client.HttpClient; + + +/** + * Client class to directly interact with the arrowhead authorization system. + * Used solely to request its public key for JWT validation. + */ +@Log4j2 +public class ArrowheadAuthorizationSystemClient { + + private static final String URI_PUBLIC_KEY = "/authorization/publickey"; + + private final WebClient webClient; + private final String baseUrl; + + /** + * Initializes a new rest client to communicate with the Arrowhead Authorization System. + * Insecure (HTTP) and secure (HTTPS) communication is supported, depending on the baseUrl. + * + * @param baseUrl specifies the base url where arrowhead is available + */ + public ArrowheadAuthorizationSystemClient(String baseUrl) { + log.debug("Initialize ArrowheadAuthorizationSystemClient with baseUrl {}", baseUrl); + this.webClient = WebClient.create(baseUrl); + this.baseUrl = baseUrl; + } + + /** + * Initializes a new rest client to communicate with the Arrowhead Authorization System with customized SSL + * handling. Insecure (HTTP) and secure (HTTPS) communication is supported, depending on the baseUrl. + * + * @param baseUrl specifies the base url where arrowhead is available + * @param sslContext specifies the ssl context the web client should use + */ + public ArrowheadAuthorizationSystemClient(String baseUrl, SslContext sslContext) { + log.debug("Initialize ArrowheadAuthorizationSystemClient with baseUrl {} and customized sslContext", baseUrl); + HttpClient httpClient = HttpClient.create() + .secure(sslContextSpec -> sslContextSpec.sslContext(sslContext)); + this.webClient = WebClient.builder() + .baseUrl(baseUrl) + .clientConnector(new ReactorClientHttpConnector(httpClient)) + .build(); + this.baseUrl = baseUrl; + } + + /** + * Returns the public key of the Authorization core service as a (Base64 encoded) text. + *

This service is necessary for providers if they want to utilize the token based security. + * A more detailed description of this REST endpoint can be found in Github under the + * client endpoint description for + * getting the public key. + * + * @return a string containing the (Base64 encoded) public key + * @throws WebClientResponseException if the status code is 4xx or 5xx + */ + public String getPublicKey() throws WebClientResponseException { + log.debug("Start HTTP GET request against {} on uri: {}", baseUrl, URI_PUBLIC_KEY); + return this.webClient + .get() + .uri(URI_PUBLIC_KEY) + .retrieve() + .bodyToMono(String.class) + .doOnNext(response -> log.debug("Finished HTTP GET request with response: {}", response)) + .block(); + } + +} diff --git a/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/arrowhead/ArrowheadServiceRegistryClient.java b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/arrowhead/ArrowheadServiceRegistryClient.java new file mode 100644 index 000000000..aaf427307 --- /dev/null +++ b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/arrowhead/ArrowheadServiceRegistryClient.java @@ -0,0 +1,106 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.arrowhead; + +import java.util.Set; + +import javax.validation.ConstraintViolation; +import javax.validation.ConstraintViolationException; +import javax.validation.Validation; +import javax.validation.Validator; + +import org.springframework.http.MediaType; +import org.springframework.http.client.reactive.ReactorClientHttpConnector; +import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.reactive.function.client.WebClientResponseException; + +import eu.arrowhead.core.confmgr.arrowhead.model.request.ServiceRegistryRequestDTO; +import eu.arrowhead.core.confmgr.arrowhead.model.response.ServiceRegistryResponseDTO; +import io.netty.handler.ssl.SslContext; +import lombok.extern.log4j.Log4j2; +import reactor.netty.http.client.HttpClient; + + +/** + * Client class to establish a connection with the Arrowhead Service Registry REST API and transmit requests. + */ +@Log4j2 +public class ArrowheadServiceRegistryClient { + + private static final String URI_REGISTER_SERVICE = "/serviceregistry/register"; + + private final WebClient webClient; + private final String baseUrl; + + /** + * Initializes a new rest client to communicate with the Arrowhead Service Registry. + * Insecure (HTTP) and secure (HTTPS) communication is supported, depending on the baseUrl. + * + * @param baseUrl specifies the base url where arrowhead is available + */ + public ArrowheadServiceRegistryClient(String baseUrl) { + log.debug("Initialize ArrowheadServiceRegistryClient with baseUrl {}", baseUrl); + this.webClient = WebClient.create(baseUrl); + this.baseUrl = baseUrl; + } + + /** + * Initializes a new rest client to communicate with the Arrowhead Service Registry with customized SSL handling. + * Insecure (HTTP) and secure (HTTPS) communication is supported, depending on the baseUrl. + * + * @param baseUrl specifies the base url where arrowhead is available + * @param sslContext specifies the ssl context the web client should use + */ + public ArrowheadServiceRegistryClient(String baseUrl, SslContext sslContext) { + log.debug("Initialize ArrowheadServiceRegistryClient with baseUrl {} and customized sslContext", baseUrl); + HttpClient httpClient = HttpClient.create() + .secure(sslContextSpec -> sslContextSpec.sslContext(sslContext)); + this.webClient = WebClient.builder() + .baseUrl(baseUrl) + .clientConnector(new ReactorClientHttpConnector(httpClient)) + .build(); + this.baseUrl = baseUrl; + } + + /** + * Registers a service. A provider is allowed to register only its own services. + * It means that provider system name and certificate common name must match for successful registration. + * A more detailed description of this REST endpoint can be found in Github under the + * client endpoint description for + * registering a service. + * + * @param serviceRegistryRequestDTO contains the information about the service that should be registered + * @return a string containing the full response body + * @throws ConstraintViolationException if the serviceRegistryRequestDTO is not valid + * @throws WebClientResponseException if the status code is 4xx or 5xx + */ + public ServiceRegistryResponseDTO registerService(ServiceRegistryRequestDTO serviceRegistryRequestDTO) + throws ConstraintViolationException, WebClientResponseException { + + Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); + Set> violations = validator.validate(serviceRegistryRequestDTO); + if (!violations.isEmpty()) { + throw new ConstraintViolationException(violations); + } + + log.debug("Start HTTP POST request against {} on uri: {}", baseUrl, URI_REGISTER_SERVICE); + return this.webClient + .post() + .uri(URI_REGISTER_SERVICE) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(serviceRegistryRequestDTO) + .retrieve() + .bodyToMono(ServiceRegistryResponseDTO.class) + .doOnNext(response -> log.debug("Finished HTTP POST request with response: {}", response)) + .block(); + } + +} diff --git a/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/arrowhead/model/request/ServiceRegistryRequestDTO.java b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/arrowhead/model/request/ServiceRegistryRequestDTO.java new file mode 100644 index 000000000..b262d5abf --- /dev/null +++ b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/arrowhead/model/request/ServiceRegistryRequestDTO.java @@ -0,0 +1,62 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.arrowhead.model.request; + +import java.util.List; +import java.util.Map; + +import javax.validation.Valid; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + + +/** + * DTO class for Service Registry requests. Carries Arrowhead-specific information, + * necessary for the registration of the configuration system wrapper in the Service Registry. + */ +@AllArgsConstructor +@NoArgsConstructor +@Data +@Builder +public class ServiceRegistryRequestDTO { + + @NotNull + private String serviceDefinition; + + @Valid + @NotNull + private SystemRequestDTO providerSystem; + + @NotNull + private String serviceUri; + + private String endOfValidity; + + private Enum secure; + + private Map metadata; + + private Integer version; + + @NotEmpty + private List interfaces; + + + public enum SecurityLevel { + UNSECURE, TOKEN, CERTIFICATE + } + +} diff --git a/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/arrowhead/model/request/SystemRequestDTO.java b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/arrowhead/model/request/SystemRequestDTO.java new file mode 100644 index 000000000..21f017dfe --- /dev/null +++ b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/arrowhead/model/request/SystemRequestDTO.java @@ -0,0 +1,38 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.arrowhead.model.request; + +import javax.validation.constraints.NotNull; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +@Data +@Builder +public class SystemRequestDTO { + + @NotNull + private String systemName; + + @NotNull + private String address; + + @NotNull + private Integer port; + + @NotNull + private String authenticationInfo; + +} diff --git a/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/arrowhead/model/response/ServiceDefinitionResponseDTO.java b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/arrowhead/model/response/ServiceDefinitionResponseDTO.java new file mode 100644 index 000000000..a89e95bc0 --- /dev/null +++ b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/arrowhead/model/response/ServiceDefinitionResponseDTO.java @@ -0,0 +1,29 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.arrowhead.model.response; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +@Data +@Builder +public class ServiceDefinitionResponseDTO { + + private Long id; + private String serviceDefinition; + private String createdAt; + private String updatedAt; + +} diff --git a/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/arrowhead/model/response/ServiceInterfaceResponseDTO.java b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/arrowhead/model/response/ServiceInterfaceResponseDTO.java new file mode 100644 index 000000000..7443d325d --- /dev/null +++ b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/arrowhead/model/response/ServiceInterfaceResponseDTO.java @@ -0,0 +1,29 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.arrowhead.model.response; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +@Data +@Builder +public class ServiceInterfaceResponseDTO { + + private Long id; + private String interfaceName; + private String createdAt; + private String updatedAt; + +} diff --git a/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/arrowhead/model/response/ServiceRegistryResponseDTO.java b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/arrowhead/model/response/ServiceRegistryResponseDTO.java new file mode 100644 index 000000000..c546342d0 --- /dev/null +++ b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/arrowhead/model/response/ServiceRegistryResponseDTO.java @@ -0,0 +1,44 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.arrowhead.model.response; + +import java.util.List; +import java.util.Map; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + + +/** + * DTO class for the Service Registry responses. Carries necessary information about the successful registration + * of the configuration system in the Service Registry. + */ +@AllArgsConstructor +@NoArgsConstructor +@Data +@Builder +public class ServiceRegistryResponseDTO { + + private Long id; + private ServiceDefinitionResponseDTO serviceDefinition; + private SystemResponseDTO provider; + private String serviceUri; + private String endOfValidity; + private String secure; + private Map metadata; + private Integer version; + private List interfaces; + private String createdAt; + private String updatedAt; + +} diff --git a/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/arrowhead/model/response/SystemResponseDTO.java b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/arrowhead/model/response/SystemResponseDTO.java new file mode 100644 index 000000000..9243d5425 --- /dev/null +++ b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/arrowhead/model/response/SystemResponseDTO.java @@ -0,0 +1,32 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.arrowhead.model.response; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +@Data +@Builder +public class SystemResponseDTO { + + private Long id; + private String systemName; + private String address; + private Integer port; + private String authenticationInfo; + private String createdAt; + private String updatedAt; + +} diff --git a/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/config/ArrowheadConfig.java b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/config/ArrowheadConfig.java new file mode 100644 index 000000000..7ba68685d --- /dev/null +++ b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/config/ArrowheadConfig.java @@ -0,0 +1,96 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.config; + +import java.io.IOException; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLException; +import javax.net.ssl.TrustManagerFactory; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import eu.arrowhead.core.confmgr.arrowhead.ArrowheadAuthorizationSystemClient; +import eu.arrowhead.core.confmgr.arrowhead.ArrowheadServiceRegistryClient; +import io.netty.handler.ssl.ClientAuth; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslContextBuilder; +import lombok.extern.log4j.Log4j2; + +/** + * This class provides the clients for interaction with Arrowhead via beans managed by the Spring container. + */ +@Log4j2 +@Configuration +public class ArrowheadConfig { + + @Value("${arrowheadAuthorizationSystem.protocol}") + private String authorizationSystemProtocol; + + @Value("${arrowheadAuthorizationSystem.address}") + private String authorizationSystemAddress; + + @Value("${arrowheadAuthorizationSystem.port}") + private Integer authorizationSystemPort; + + @Value("${arrowheadServiceRegistry.protocol}") + private String serviceRegistryProtocol; + + @Value("${arrowheadServiceRegistry.address}") + private String serviceRegistryAddress; + + @Value("${arrowheadServiceRegistry.port}") + private String serviceRegistryPort; + + @Bean + public ArrowheadAuthorizationSystemClient arrowheadAuthorizationSystemClient( + KeyManagerFactory keyManagerFactory, + TrustManagerFactory trustManagerFactory) throws IOException { + String baseUrl = this.authorizationSystemProtocol + this.authorizationSystemAddress + ":" + this.authorizationSystemPort; + log.debug("Registering bean for ArrowheadAuthorizationSystemClient with baseUrl {} and custom ssl context", baseUrl); + + SslContext sslContext = loadSslContext(keyManagerFactory, trustManagerFactory); + return new ArrowheadAuthorizationSystemClient(baseUrl, sslContext); + } + + @Bean + public ArrowheadServiceRegistryClient arrowheadServiceRegistryClient( + KeyManagerFactory keyManagerFactory, + TrustManagerFactory trustManagerFactory) throws IOException { + String baseUrl = this.serviceRegistryProtocol + this.serviceRegistryAddress + ":" + this.serviceRegistryPort; + log.debug("Registering bean for ArrowheadServiceRegistryClient with baseUrl {} and custom ssl context", baseUrl); + + SslContext sslContext = loadSslContext(keyManagerFactory, trustManagerFactory); + return new ArrowheadServiceRegistryClient(baseUrl, sslContext); + } + + /** + * Load the ssl context with a defined client certificate and a defined server certificate. + * This allows to use self signed certificates. + * + * @param keyManagerFactory contains the client private and public certificates + * @param trustManagerFactory contains the server public certificate + * @return a ssl context for a {@link reactor.netty.http.client.HttpClient HttpClient} + * @throws SSLException if the ssl context could not be loaded correctly + */ + private SslContext loadSslContext(KeyManagerFactory keyManagerFactory, TrustManagerFactory trustManagerFactory) + throws IOException { + return SslContextBuilder + .forClient() + .clientAuth(ClientAuth.REQUIRE) + .keyManager(keyManagerFactory) + .trustManager(trustManagerFactory) + .build(); + } + +} diff --git a/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/config/HawkbitConfig.java b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/config/HawkbitConfig.java new file mode 100644 index 000000000..90081e6aa --- /dev/null +++ b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/config/HawkbitConfig.java @@ -0,0 +1,66 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.config; + +import java.io.IOException; +import java.util.concurrent.TimeoutException; + +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionFactory; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Scope; + +/** + * This class provides the clients for interaction with hawkBit via beans managed by the Spring container. + */ +@Configuration +public class HawkbitConfig { + + @Value("${hawkbit.host}") + private String host; + + @Value("${hawkbit.port}") + private Integer port; + + @Value("${hawkbit.username}") + private String username; + + @Value("${hawkbit.password}") + private String password; + + @Bean + public ConnectionFactory connectionFactory() { + ConnectionFactory connectionFactory = new ConnectionFactory(); + connectionFactory.setUsername(this.username); + connectionFactory.setPassword(this.password); + connectionFactory.setHost(this.host); + connectionFactory.setPort(this.port); + connectionFactory.setAutomaticRecoveryEnabled(true); + return connectionFactory; + } + + @Bean + public Connection getConnection(ConnectionFactory connectionFactory) throws IOException, TimeoutException { + return connectionFactory.newConnection(); + } + + @Bean + @Scope("prototype") + @Autowired + public Channel createChannel(Connection connection) throws IOException { + return connection.createChannel(); + } +} diff --git a/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/config/KeyStoreConfig.java b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/config/KeyStoreConfig.java new file mode 100644 index 000000000..23d5b5f1b --- /dev/null +++ b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/config/KeyStoreConfig.java @@ -0,0 +1,76 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.config; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; + +import javax.net.ssl.KeyManagerFactory; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * This class provides the key store of the configuration system in different formats via beans managed by the Spring + * container. The key store contains the private and the public key of the configuration system itself. + */ +@Configuration +public class KeyStoreConfig { + + private static final String KEY_STORE_TYPE = "pkcs12"; + + @Value("${keyStore.path}") + private String keyStorePath; + + @Value("${keyStore.password}") + private String keyStorePassword; + + @Value("${keyStore.alias}") + private String keyStoreAlias; + + @Bean + public KeyManagerFactory configurationSystemKeyManagerFactory() throws KeyStoreException, NoSuchAlgorithmException, + UnrecoverableKeyException, IOException, CertificateException { + KeyStore keyStore = loadKeyStore(); + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + keyManagerFactory.init(keyStore, keyStorePassword.toCharArray()); + return keyManagerFactory; + } + + @Bean + public PrivateKey configurationSystemPrivateKey() throws UnrecoverableKeyException, NoSuchAlgorithmException, + KeyStoreException, IOException, CertificateException { + KeyStore keyStore = loadKeyStore(); + return (PrivateKey) keyStore.getKey(keyStoreAlias, keyStorePassword.toCharArray()); + } + + @Bean + public PublicKey configurationSystemPublicKey() throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException { + KeyStore keyStore = loadKeyStore(); + return keyStore.getCertificate(keyStoreAlias).getPublicKey(); + } + + private KeyStore loadKeyStore() throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException { + KeyStore keyStore = KeyStore.getInstance(KEY_STORE_TYPE); + keyStore.load(new FileInputStream(new File(keyStorePath)), keyStorePassword.toCharArray()); + return keyStore; + } + +} diff --git a/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/config/PropertiesConfig.java b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/config/PropertiesConfig.java new file mode 100644 index 000000000..d6165781d --- /dev/null +++ b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/config/PropertiesConfig.java @@ -0,0 +1,39 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import eu.arrowhead.core.confmgr.properties.SystemProperties; + +/** + * This class instantiates properties classes, populates them from Spring properties files (like application.yml) + * and provides them as beans managed by the Spring container. + */ +@Configuration +public class PropertiesConfig { + + /** + * This makes use of Spring properties files (like application.yml), loads all properties under the + * prefix "system" into the newly instantiated class {@link SystemProperties} and provides it as a bean + * managed by the Spring container. + * + * @return a populated and validated instance of {@link SystemProperties} + */ + @Bean + @ConfigurationProperties(prefix = "system") + public SystemProperties systemProperties() { + return new SystemProperties(); + } + +} diff --git a/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/config/TrustStoreConfig.java b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/config/TrustStoreConfig.java new file mode 100644 index 000000000..c8a77971d --- /dev/null +++ b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/config/TrustStoreConfig.java @@ -0,0 +1,57 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.config; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; + +import javax.net.ssl.TrustManagerFactory; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * This class provides the trust store of the configuration system in different formats via beans managed by the Spring + * container. The trust store contains the public keys that are trusted. + */ +@Configuration +public class TrustStoreConfig { + + private static final String KEY_STORE_TYPE = "pkcs12"; + + @Value("${trustStore.path}") + private String trustStorePath; + + @Value("${trustStore.password}") + private String trustStorePassword; + + @Bean + public TrustManagerFactory trustManagerFactory() throws KeyStoreException, IOException, CertificateException, + NoSuchAlgorithmException { + KeyStore trustStore = loadTrustStore(); + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(trustStore); + return trustManagerFactory; + } + + private KeyStore loadTrustStore() throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException { + KeyStore trustStore = KeyStore.getInstance(KEY_STORE_TYPE); + trustStore.load(new FileInputStream(new File(trustStorePath)), trustStorePassword.toCharArray()); + return trustStore; + } + +} diff --git a/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/config/WebSocketConfig.java b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/config/WebSocketConfig.java new file mode 100644 index 000000000..463b182e6 --- /dev/null +++ b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/config/WebSocketConfig.java @@ -0,0 +1,28 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.config; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Scope; +import org.springframework.web.socket.WebSocketSession; + +@Configuration +public class WebSocketConfig { + @Bean + @Scope("singleton") + public Map getWebSocketSessionMap() { + return new ConcurrentHashMap<>(); + } +} diff --git a/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/config/WebSocketSecurityConfig.java b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/config/WebSocketSecurityConfig.java new file mode 100644 index 000000000..144e79848 --- /dev/null +++ b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/config/WebSocketSecurityConfig.java @@ -0,0 +1,86 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.config; + +import java.security.PrivateKey; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; +import org.springframework.web.socket.config.annotation.EnableWebSocket; +import org.springframework.web.socket.config.annotation.WebSocketConfigurer; +import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; + +import eu.arrowhead.core.confmgr.security.JwtAuthenticationFilter; +import eu.arrowhead.core.confmgr.service.ArrowheadService; +import eu.arrowhead.core.confmgr.websocket.WebSocketController; + +/** + * This is the configuration for web sockets. + *

Web socket support can be enabled/disabled with the annotation {@link EnableWebSocket @EnableWebSocket}

+ *

Web socket security can be enabled/disabled with the annotation {@link EnableWebSecurity @EnableWebSecurity}

+ */ +@Configuration +@EnableWebSecurity +@EnableWebSocket +public class WebSocketSecurityConfig extends WebSecurityConfigurerAdapter implements WebSocketConfigurer { + + private final ArrowheadService arrowheadService; + private final PrivateKey privateKey; + private final WebSocketController webSocketController; + + @Autowired + public WebSocketSecurityConfig(ArrowheadService arrowheadService, + PrivateKey privateKey, + WebSocketController webSocketController) { + this.arrowheadService = arrowheadService; + this.privateKey = privateKey; + this.webSocketController = webSocketController; + } + + /** + * Registers the web socket controller as web socket handler. + */ + @Override + public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { + registry.addHandler(this.webSocketController, "/"); + } + + /** + * Configuration of the HTTP security for the whole configuration system, including web socket connections. + * Many default security settings are disabled as the clients are connecting non-interactive to the configuration + * system and don't support all default security settings. + */ + @Override + protected void configure(HttpSecurity http) throws Exception { + http + // Disable form login, as only non-interactive clients connect + .formLogin().disable() + // Disable HTTP basic authentication, as only JWT authentication is supported + .httpBasic().disable() + // CORS protection is not required, as only non-interactive clients connect + .cors().disable() + // CSRF protection is not required, as only non-interactive clients connect + .csrf(AbstractHttpConfigurer::disable) + // Register custom JWT authentication filter, so JWT authentication is supported + .addFilterBefore(new JwtAuthenticationFilter(this.arrowheadService, this.privateKey), + BasicAuthenticationFilter.class) + // Disable Spring Sessions, as sessions should not be supported + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + // Require full authentication on every request + .authorizeRequests().anyRequest().fullyAuthenticated(); + } +} diff --git a/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/hawkbit/HawkbitDmfConstants.java b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/hawkbit/HawkbitDmfConstants.java new file mode 100644 index 000000000..d9ace4b86 --- /dev/null +++ b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/hawkbit/HawkbitDmfConstants.java @@ -0,0 +1,40 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.hawkbit; + +public class HawkbitDmfConstants { + + /** + * The exchange name for all messages sent to hawkBit. It is predefined from hawkBit and can not be changed. + */ + public static final String SENDING_EXCHANGE = "dmf.exchange"; + + /** + * The routing key for all messages sent to hawkBit. It is predefined from hawkBit and can not be changed. + */ + public static final String SENDING_ROUTING_KEY = ""; + + /** + * The exchange name for all messages sent from hawkBit to the configuration system. It can be modified. + */ + public static final String RECEIVING_EXCHANGE = "configuration_system.direct.exchange"; + + /** + * The queue name for all messages sent from hawkBit to the configuration system. It can be modified. + */ + public static final String RECEIVING_QUEUE = "configuration_system_direct_queue"; + + /** + * The routing key for all messages sent from hawkBit to the configuration system. It can be modified. + */ + public static final String RECEIVING_ROUTING_KEY = ""; + +} diff --git a/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/hawkbit/HawkbitDmfConsumer.java b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/hawkbit/HawkbitDmfConsumer.java new file mode 100644 index 000000000..d7a851fe8 --- /dev/null +++ b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/hawkbit/HawkbitDmfConsumer.java @@ -0,0 +1,334 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.hawkbit; + +import java.io.IOException; +import java.util.Map; + +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.BuiltinExchangeType; +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.DefaultConsumer; +import com.rabbitmq.client.Envelope; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import eu.arrowhead.core.confmgr.hawkbit.model.EventType; +import eu.arrowhead.core.confmgr.hawkbit.model.MessageHeader; +import eu.arrowhead.core.confmgr.hawkbit.model.inbound.CancelDownloadInboundMessage; +import eu.arrowhead.core.confmgr.hawkbit.model.inbound.DownloadRequestInboundMessage; +import eu.arrowhead.core.confmgr.hawkbit.model.inbound.MessageTypeInbound; +import eu.arrowhead.core.confmgr.hawkbit.model.inbound.ThingDeletedInboundMessage; +import eu.arrowhead.core.confmgr.service.WebSocketService; +import eu.arrowhead.core.confmgr.websocket.DeviceNotConnectedException; +import lombok.extern.log4j.Log4j2; + +/** + * Handles incoming messages on the connected HawkBit (RabbitMQ) queue. + * Responsible for further message routing, depending on the message type and + * topic. + */ +@Log4j2 +@Component +public class HawkbitDmfConsumer extends DefaultConsumer { + + private final Channel channel; + private final ObjectMapper objectMapper; + + private final WebSocketService wsService; + + /** + * Initializes a new hawkBit consumer that operates on the hawkBit device + * management federation (DMF) API. It is used for downstream communication + * (receiving messages from hawkBit). Further information to the downstream DMF + * API can be found in the DMF API documentation + * under "Messages sent by hawkBit (hawkBit -> Client)" + * + * @param channel used for communication with hawkBit + */ + @Autowired + public HawkbitDmfConsumer(Channel channel, WebSocketService wsService) { + super(channel); + this.channel = channel; + this.objectMapper = new ObjectMapper(); + this.wsService = wsService; + } + + /** + * Subscribe to download event messages from hawkBit. + *

+ * Important: The handler is responsible to acknowledge + * the successful retrieval and processing of the message via the method + * {@link #acknowledgeMessage(long)}. + * + * @throws IOException if there is a connection problem with hawkBit + */ + public void subscribeToDownloadEvents() throws IOException { + this.initializeQueueOnHawkbit(); + this.consumeFromQueueOnHawkbit(); + } + + /** + * Initialize the queue on hawkBit DMF API that is used from the configuration + * system to receive messages. + * + * @throws IOException if there is a connection problem with hawkBit + */ + private void initializeQueueOnHawkbit() throws IOException { + this.channel.queueDeclare(HawkbitDmfConstants.RECEIVING_QUEUE, true, false, false, null); + this.channel.exchangeDeclare(HawkbitDmfConstants.RECEIVING_EXCHANGE, BuiltinExchangeType.DIRECT, true, false, + null); + this.channel.queueBind(HawkbitDmfConstants.RECEIVING_QUEUE, HawkbitDmfConstants.RECEIVING_EXCHANGE, + HawkbitDmfConstants.RECEIVING_ROUTING_KEY); + } + + /** + * Start consuming from the configuration system queue on the hawkBit DMF API. + * + * @throws IOException if there is a connection problem with hawkBit + */ + private void consumeFromQueueOnHawkbit() throws IOException { + this.channel.basicConsume(HawkbitDmfConstants.RECEIVING_QUEUE, false, this); + } + + /** + * Overrides the method + * {@link DefaultConsumer#handleDelivery(String, Envelope, AMQP.BasicProperties, byte[])} + * with an implementation to handle hawkBit DMF API messages. + */ + @Override + public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) { + try { + log.info("Received message"); + handleMessage(envelope, properties, body); + } catch (IOException e) { + log.atError().withThrowable(e).log( + "Handling of message delivery failed. Message has the " + "following properties: {} and body: {}", + properties, body); + } + } + + /** + * Handle a hawkBit message. + * + * @param envelope envelope of the AMQP message + * @param properties properties (including the headers) of the AMQP message + * @param body body of the AMQP message + * @throws IOException if a problem occurs with the message itself, like a + * missing header or a malformed body + */ + private void handleMessage(Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { + Map headers = properties.getHeaders(); + if (headers == null || !headers.containsKey(MessageHeader.TYPE.toString())) { + throw new IOException("Message doesn't contain header " + MessageHeader.TYPE.toString()); + } + + MessageTypeInbound messageType = MessageTypeInbound + .valueOf(String.valueOf(headers.get(MessageHeader.TYPE.toString()))); + switch (messageType) { + case EVENT: + handleEvent(envelope, properties, body); + break; + case THING_DELETED: + handleThingDeleted(envelope, properties); + break; + case PING_RESPONSE: + default: + handleUnsupportedMessageType(envelope, messageType); + break; + } + } + + /** + * Handle a hawkBit message from type {@link MessageTypeInbound#EVENT}. + * + * @param envelope envelope of the AMQP message + * @param properties properties (including the headers) of the AMQP message + * @param body body of the AMQP message + * @throws IOException if a problem occurs with the message itself, like a + * missing header or a malformed body + */ + private void handleEvent(Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { + Map headers = properties.getHeaders(); + if (headers == null || !headers.containsKey(MessageHeader.TOPIC.toString())) { + throw new IOException("Message doesn't contain header " + MessageHeader.TOPIC.toString()); + } + + EventType eventType = EventType.valueOf(String.valueOf(headers.get(MessageHeader.TOPIC.toString()))); + switch (eventType) { + case DOWNLOAD: + case DOWNLOAD_AND_INSTALL: + handleEventWithTopicDownload(envelope, properties, body); + break; + case CANCEL_DOWNLOAD: + handleEventWithTopicCancelDownload(envelope, properties, body); + break; + case MULTI_ACTION: + case REQUEST_ATTRIBUTES_UPDATE: + default: + handleEventWithUnsupportedTopic(envelope, eventType); + break; + } + } + + /** + * Handle a hawkBit download request message. + * + * @param envelope envelope of the AMQP message + * @param properties properties (including the headers) of the AMQP message + * @param body body of the AMQP message + * @throws IOException if a problem occurs during mapping the body to + * {@link DownloadRequestInboundMessage.Body} with a json + * object mapper + */ + private void handleEventWithTopicDownload(Envelope envelope, AMQP.BasicProperties properties, byte[] body) { + Map headers = properties.getHeaders(); + DownloadRequestInboundMessage message; + try { + message = DownloadRequestInboundMessage.builder() + .headers(DownloadRequestInboundMessage.Headers.builder() + .type(headers.get(MessageHeader.TYPE.toString()).toString()) + .thingId(headers.get(MessageHeader.THING_ID.toString()).toString()) + .topic(headers.get(MessageHeader.TOPIC.toString()).toString()) + .tenant(headers.get(MessageHeader.TENANT.toString()).toString()).build()) + .body(this.objectMapper.readValue(body, DownloadRequestInboundMessage.Body.class)) + .deliveryTag(envelope.getDeliveryTag()).build(); + + wsService.sendDownloadEventMessage(message); + + acknowledgeMessageDelivery(envelope.getDeliveryTag()); + } catch (JsonParseException e) { + log.error("Could not parse inbound message: {}", e); + } catch (JsonMappingException e) { + log.error("Could not map inbound message: {}", e); + } catch (IOException e) { + log.error("Could not send message to device {}, as it was not reachable.", headers.get(MessageHeader.THING_ID.toString()).toString()); + } catch (DeviceNotConnectedException e) { + log.error("Device {} is currently not connected to HawkBit.", headers.get(MessageHeader.THING_ID.toString()).toString()); + } + } + + /** + * Handle CANCEL_DOWNLOAD messages. Send ACK only if parsing of the message and further + * transport via WebSocket was successful. + * + * @param envelope AMQP envelope + * @param properties AMQP properties + * @param body the actual amqp message payload + */ + private void handleEventWithTopicCancelDownload(Envelope envelope, AMQP.BasicProperties properties, byte[] body) { + Map headers = properties.getHeaders(); + CancelDownloadInboundMessage message; + + try { + message = CancelDownloadInboundMessage.builder() + .headers( + CancelDownloadInboundMessage.Headers.builder() + .type(headers.get(MessageHeader.TYPE.toString()).toString()) + .thingId(headers.get(MessageHeader.THING_ID.toString()).toString()) + .topic(headers.get(MessageHeader.TOPIC.toString()).toString()) + .tenant(headers.get(MessageHeader.TENANT.toString()).toString()) + .build() + ) + .body(this.objectMapper.readValue(body, CancelDownloadInboundMessage.Body.class)).build(); + + this.wsService.sendCancelDownloadMessage(message); + + acknowledgeMessageDelivery(envelope.getDeliveryTag()); + } catch (JsonParseException e) { + log.error("Could not parse inbound message: {}", e); + } catch (JsonMappingException e) { + log.error("Could not map inbound message: {}", e); + } catch (IOException e) { + log.error("Could not send message to device {}, as it was not reachable.", headers.get(MessageHeader.THING_ID.toString()).toString()); + } catch (DeviceNotConnectedException e) { + log.error("Device {} is currently not connected to HawkBit.", headers.get(MessageHeader.THING_ID.toString()).toString()); + } + } + + /** + * Handle THING_DELETED messages. Send ACK only if parsing of the message and further + * transport via Websocket was successful + * + * @param envelope AMQP envelope + * @param properties AMQP properties + * @param body the actual AMQP message payload + */ + private void handleThingDeleted(Envelope envelope, AMQP.BasicProperties properties) { + Map headers = properties.getHeaders(); + + ThingDeletedInboundMessage message; + try { + message = ThingDeletedInboundMessage.builder() + .headers( + ThingDeletedInboundMessage.Headers.builder() + .thingId(headers.get(MessageHeader.THING_ID.toString()).toString()) + .build() + ) + .build(); + + this.wsService.sendThingDeletedMessage(message); + + acknowledgeMessageDelivery(envelope.getDeliveryTag()); + } catch (JsonParseException e) { + log.error("Could not parse inbound message: {}", e); + } catch (JsonMappingException e) { + log.error("Could not map inbound message: {}", e); + } catch (IOException e) { + log.error("Could not send message to device {}, as it was not reachable.", headers.get(MessageHeader.THING_ID.toString()).toString()); + } catch (DeviceNotConnectedException e) { + log.error("Device {} is currently not connected to HawkBit.", headers.get(MessageHeader.THING_ID.toString()).toString()); + } + } + + + /** + * Handle a hawkBit message with unsupported message type. Send a negative acknowledgement to hawkBit for the + * message with no requeuing. + * + * @param envelope envelope of the AMQP message + * @param messageType message type of the AMQP message + * @throws IOException if there is a connection problem with hawkBit + */ + private void handleUnsupportedMessageType(Envelope envelope, MessageTypeInbound messageType) throws IOException { + log.error("Message with unsupported message type {} was received, responding with negative " + + "acknowledgement and no requeuing", messageType.toString()); + super.getChannel().basicNack(envelope.getDeliveryTag(), false, false); + } + + /** + * Handle a hawkBit message from message type {@link MessageTypeInbound#EVENT} and with unsupported event type. Send a + * negative acknowledgement to hawkBit for the message with no requeuing. + * + * @param envelope envelope of the AMQP message + * @param eventType event type of the AMQP message + * @throws IOException if there is a connection problem with hawkBit + */ + private void handleEventWithUnsupportedTopic(Envelope envelope, EventType eventType) throws IOException { + log.error("Message with message type {} and unsupported event topic {} was received, responding " + + "with negative acknowledgement and no requeuing", MessageTypeInbound.EVENT.toString(), eventType.toString()); + super.getChannel().basicNack(envelope.getDeliveryTag(), false, false); + } + + private void acknowledgeMessageDelivery(Long deliveryTag) { + try { + super.getChannel().basicAck(deliveryTag, false); + } catch (IOException e) { + log.error("Could not acknowledge message delivery to HawkBit."); + } + } + +} diff --git a/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/hawkbit/HawkbitDmfOutboundClient.java b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/hawkbit/HawkbitDmfOutboundClient.java new file mode 100644 index 000000000..931c1ee71 --- /dev/null +++ b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/hawkbit/HawkbitDmfOutboundClient.java @@ -0,0 +1,136 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.hawkbit; + +import java.io.IOException; +import java.util.Set; + +import javax.validation.ConstraintViolation; +import javax.validation.ConstraintViolationException; +import javax.validation.Validation; +import javax.validation.Validator; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.Channel; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import eu.arrowhead.core.confmgr.hawkbit.model.outbound.ThingCreatedOutboundMessage; +import eu.arrowhead.core.confmgr.hawkbit.model.outbound.ThingRemovedOutboundMessage; +import eu.arrowhead.core.confmgr.hawkbit.model.outbound.UpdateActionStatusOutboundMessage; +import lombok.extern.log4j.Log4j2; + + +/** + * Handles all outbound interaction with Eclipse HawkBit via its DMF API. + */ +@Log4j2 +@Component +public class HawkbitDmfOutboundClient { + + private final Channel channel; + private final ObjectMapper objectMapper; + private final Validator validator; + + /** + * Initialize a new hawkBit client that operates on the hawkBit device management federation (DMF) API. It is + * used for upstream communication (sending messages to hawkBit). Further information to the upstream DMF API can + * be found in the DMF API documentation under + * "Messages sent to hawkBit (Client -> hawkBit)" + * + * @param channel used for communication with hawkBit + */ + @Autowired + public HawkbitDmfOutboundClient(Channel channel) { + this.channel = channel; + this.objectMapper = new ObjectMapper(); + this.validator = Validation.buildDefaultValidatorFactory().getValidator(); + } + + /** + * Send a message to hawkBit on the device management federation (DMF) API to create a new thing. A description of + * this API can be found in the DMF API documentation + * under "Messages sent to hawkBit (Client -> hawkBit)" -> "THING_CREATED". + * + * @param message the message to publish with body and headers + * @throws ConstraintViolationException if message is not valid + * @throws IOException if there is a connection problem with hawkBit + */ + public void createThing(ThingCreatedOutboundMessage message) throws ConstraintViolationException, IOException { + log.debug("Validating ThingCreatedOutboundMessage"); + Set> violations = validator.validate(message); + if (!violations.isEmpty()) { + throw new ConstraintViolationException(violations); + } + + byte[] byteBody = this.objectMapper.writeValueAsBytes(message.getBody()); + AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder() + .contentType("application/json") + .headers(message.getHeaders().asMap()) + .replyTo(HawkbitDmfConstants.RECEIVING_EXCHANGE) + .build(); + log.debug("Sending ThingCreatedOutboundMessage to exchange {}", HawkbitDmfConstants.SENDING_EXCHANGE); + this.channel.basicPublish(HawkbitDmfConstants.SENDING_EXCHANGE, HawkbitDmfConstants.SENDING_ROUTING_KEY, + properties, byteBody); + } + + /** + * Send a message to hawkBit on the device management federation (DMF) API to create a new thing. A description of + * this API can be found in the DMF API documentation + * under "Messages sent to hawkBit (Client -> hawkBit)" -> "UPDATE_ACTION_STATUS". + * + * @param message the message to publish with body and headers + * @throws ConstraintViolationException if message is not valid + * @throws IOException if there is a connection problem with hawkBit + */ + public void updateActionStatus(UpdateActionStatusOutboundMessage message) throws IOException { + log.debug("Validating UpdateActionStatusOutboundMessage"); + Set> violations = validator.validate(message); + if (!violations.isEmpty()) { + throw new ConstraintViolationException(violations); + } + + byte[] byteBody = this.objectMapper.writeValueAsBytes(message.getBody()); + AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder() + .contentType("application/json") + .headers(message.getHeaders().asMap()) + .build(); + log.debug("Sending UpdateActionStatusOutboundMessage to exchange {}", HawkbitDmfConstants.SENDING_EXCHANGE); + this.channel.basicPublish(HawkbitDmfConstants.SENDING_EXCHANGE, HawkbitDmfConstants.SENDING_ROUTING_KEY, + properties, byteBody); + } + + /** + * Send a message to hawkBit via the device management federation (DMF) API to remove a thing. A description of + * this API can be found in the DMF API documentation + * under "Messages sent to hawkBit (Client -> hawkBit)" -> "THING_REMOVED". + * @param message the actual message to be published + * @throws IOException + */ + public void removeThing(ThingRemovedOutboundMessage message) throws IOException { + Set> violations = validator.validate(message); + if (!violations.isEmpty()) { + throw new ConstraintViolationException(violations); + } + + byte[] byteBody = "".getBytes(); + AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder() + .contentType("application/json") + .headers(message.getHeaders().asMap()) + .build(); + + this.channel.basicPublish(HawkbitDmfConstants.SENDING_EXCHANGE, HawkbitDmfConstants.SENDING_ROUTING_KEY, + properties, byteBody); + } + +} diff --git a/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/hawkbit/model/ActionStatus.java b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/hawkbit/model/ActionStatus.java new file mode 100644 index 000000000..8e1423694 --- /dev/null +++ b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/hawkbit/model/ActionStatus.java @@ -0,0 +1,39 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.hawkbit.model; + + +/** + * Enum for outbound UPDATE_ACTION_STATUS type classification. + */ +public enum ActionStatus { + DOWNLOAD("DOWNLOAD"), + RETRIEVED("RETRIEVED"), + RUNNING("RUNNING"), + FINISHED("FINISHED"), + ERROR("ERROR"), + WARNING("WARNING"), + CANCELED("CANCELED"), + CANCEL_REJECTED("CANCEL_REJECTED"), + DOWNLOADED("DOWNLOADED"); + + private final String stringValue; + + ActionStatus(String stringValue) { + this.stringValue = stringValue; + } + + @Override + public String toString() { + return this.stringValue; + } + +} diff --git a/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/hawkbit/model/EventType.java b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/hawkbit/model/EventType.java new file mode 100644 index 000000000..166efee1b --- /dev/null +++ b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/hawkbit/model/EventType.java @@ -0,0 +1,32 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.hawkbit.model; + +public enum EventType { + CANCEL_DOWNLOAD("CANCEL_DOWNLOAD"), + DOWNLOAD("DOWNLOAD"), + DOWNLOAD_AND_INSTALL("DOWNLOAD_AND_INSTALL"), + MULTI_ACTION("MULTI_ACTION"), + REQUEST_ATTRIBUTES_UPDATE("REQUEST_ATTRIBUTES_UPDATE"), + UPDATE_ACTION_STATUS("UPDATE_ACTION_STATUS"); + + private final String stringValue; + + EventType(String stringValue) { + this.stringValue = stringValue; + } + + @Override + public String toString() { + return this.stringValue; + } + +} diff --git a/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/hawkbit/model/MessageHeader.java b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/hawkbit/model/MessageHeader.java new file mode 100644 index 000000000..0124d20fc --- /dev/null +++ b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/hawkbit/model/MessageHeader.java @@ -0,0 +1,35 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.hawkbit.model; + + +/** + * Enum for outbound header type classification. + */ +public enum MessageHeader { + SENDER("sender"), + TENANT("tenant"), + THING_ID("thingId"), + TOPIC("topic"), + TYPE("type"); + + private final String stringValue; + + MessageHeader(String stringValue) { + this.stringValue = stringValue; + } + + @Override + public String toString() { + return this.stringValue; + } + +} \ No newline at end of file diff --git a/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/hawkbit/model/inbound/CancelDownloadInboundMessage.java b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/hawkbit/model/inbound/CancelDownloadInboundMessage.java new file mode 100644 index 000000000..497dafe20 --- /dev/null +++ b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/hawkbit/model/inbound/CancelDownloadInboundMessage.java @@ -0,0 +1,44 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.hawkbit.model.inbound; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + + +/** + * Transfer object, encapsulating Cancel Download requests, received by the HawkBit consumer. + */ +@Builder +@Data +public class CancelDownloadInboundMessage implements InboundMessage { + private Headers headers; + private Body body; + + @Builder + @Data + public static class Headers { + private String type; + private String thingId; + private String topic; + private String tenant; + } + + @AllArgsConstructor + @NoArgsConstructor + @Builder + @Data + public static class Body { + private Long actionId; + } +} diff --git a/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/hawkbit/model/inbound/DownloadRequestInboundMessage.java b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/hawkbit/model/inbound/DownloadRequestInboundMessage.java new file mode 100644 index 000000000..485ae3508 --- /dev/null +++ b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/hawkbit/model/inbound/DownloadRequestInboundMessage.java @@ -0,0 +1,115 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.hawkbit.model.inbound; + +import java.util.Date; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + + +/** + * Transfer object, encapsulating Download Requests, received by the HawkBit consumer. + */ +@AllArgsConstructor +@NoArgsConstructor +@Data +@Builder +public class DownloadRequestInboundMessage implements InboundMessage { + + private Body body; + + private Headers headers; + + private Long deliveryTag; + + @AllArgsConstructor + @NoArgsConstructor + @Data + @Builder + public static class Body { + private Long actionId; + private String targetSecurityToken; + private List softwareModules; + + @AllArgsConstructor + @NoArgsConstructor + @Data + @Builder + public static class SoftwareModule { + private Long moduleId; + private String moduleType; + private String moduleVersion; + private List artifacts; + private List metadata; + } + + @AllArgsConstructor + @NoArgsConstructor + @Data + @Builder + public static class Artifact { + private String filename; + private Urls urls; + private Hashes hashes; + private Long size; + private Date lastModified; + } + + @AllArgsConstructor + @NoArgsConstructor + @Data + @Builder + public static class Urls { + @JsonProperty("COAP") + private String COAP; + @JsonProperty("HTTP") + private String HTTP; + @JsonProperty("HTTPS") + private String HTTPS; + } + + @AllArgsConstructor + @NoArgsConstructor + @Data + @Builder + public static class Hashes { + private String md5; + private String sha1; + } + + @AllArgsConstructor + @NoArgsConstructor + @Data + @Builder + public static class Metadata { + private String key; + private String value; + } + } + + @AllArgsConstructor + @NoArgsConstructor + @Data + @Builder + public static class Headers { + private String type; + private String thingId; + private String topic; + private String tenant; + } + +} diff --git a/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/hawkbit/model/inbound/InboundMessage.java b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/hawkbit/model/inbound/InboundMessage.java new file mode 100644 index 000000000..eacfa56f1 --- /dev/null +++ b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/hawkbit/model/inbound/InboundMessage.java @@ -0,0 +1,19 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.hawkbit.model.inbound; + + +/** + * Interface for all incoming AMQP message types. + */ +public interface InboundMessage { + +} diff --git a/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/hawkbit/model/inbound/MessageTypeInbound.java b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/hawkbit/model/inbound/MessageTypeInbound.java new file mode 100644 index 000000000..edb8406a4 --- /dev/null +++ b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/hawkbit/model/inbound/MessageTypeInbound.java @@ -0,0 +1,33 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.hawkbit.model.inbound; + + +/** + * Enum for inbound message type classification. + */ +public enum MessageTypeInbound { + EVENT("EVENT"), + THING_DELETED("THING_DELETED"), + PING_RESPONSE("PING_RESPONSE"); + + private final String stringValue; + + MessageTypeInbound(String stringValue) { + this.stringValue = stringValue; + } + + @Override + public String toString() { + return this.stringValue; + } + +} diff --git a/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/hawkbit/model/inbound/ThingDeletedInboundMessage.java b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/hawkbit/model/inbound/ThingDeletedInboundMessage.java new file mode 100644 index 000000000..67f4e9462 --- /dev/null +++ b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/hawkbit/model/inbound/ThingDeletedInboundMessage.java @@ -0,0 +1,30 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.hawkbit.model.inbound; + +import lombok.Builder; +import lombok.Data; + +/** + * Transfer object, encapsulating Thing Deleted messages, received by the HawkBit consumer. + */ +@Builder +@Data +public class ThingDeletedInboundMessage implements InboundMessage { + private Headers headers; + + + @Builder + @Data + public static class Headers { + private String thingId; + } +} diff --git a/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/hawkbit/model/outbound/MessageTypeOutbound.java b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/hawkbit/model/outbound/MessageTypeOutbound.java new file mode 100644 index 000000000..43c1d29c9 --- /dev/null +++ b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/hawkbit/model/outbound/MessageTypeOutbound.java @@ -0,0 +1,33 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.hawkbit.model.outbound; + + +/** + * Enum for outbound message type classification. + */ +public enum MessageTypeOutbound { + EVENT("EVENT"), + THING_CREATED("THING_CREATED"), + THING_REMOVED("THING_REMOVED"); + + private final String stringValue; + + MessageTypeOutbound(String stringValue) { + this.stringValue = stringValue; + } + + @Override + public String toString() { + return this.stringValue; + } + +} diff --git a/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/hawkbit/model/outbound/ThingCreatedOutboundMessage.java b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/hawkbit/model/outbound/ThingCreatedOutboundMessage.java new file mode 100644 index 000000000..02249b9e1 --- /dev/null +++ b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/hawkbit/model/outbound/ThingCreatedOutboundMessage.java @@ -0,0 +1,76 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.hawkbit.model.outbound; + +import java.util.HashMap; +import java.util.Map; + +import javax.validation.Valid; +import javax.validation.constraints.NotNull; + +import eu.arrowhead.core.confmgr.hawkbit.model.MessageHeader; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + + +/** + * Transfer object, encapsulating THING_CREATED requests, sent by the HawkBit client. + */ +@AllArgsConstructor +@NoArgsConstructor +@Data +@Builder +public class ThingCreatedOutboundMessage { + + @Valid + @NotNull + private Body body; + + @Valid + @NotNull + private Headers headers; + + @AllArgsConstructor + @NoArgsConstructor + @Data + @Builder + public static class Body { + private String name; + } + + @AllArgsConstructor + @NoArgsConstructor + @Data + @Builder + public static class Headers { + @NotNull + private final String type = MessageTypeOutbound.THING_CREATED.toString(); + + @NotNull + private String thingId; + + private String sender; + + private String tenant; + + public Map asMap() { + HashMap headers = new HashMap<>(); + if (this.type != null) headers.put(MessageHeader.TYPE.toString(), this.type); + if (this.thingId != null) headers.put(MessageHeader.THING_ID.toString(), this.thingId); + if (this.sender != null) headers.put(MessageHeader.SENDER.toString(), this.sender); + if (this.tenant != null) headers.put(MessageHeader.TENANT.toString(), this.tenant); + return headers; + } + } + +} diff --git a/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/hawkbit/model/outbound/ThingRemovedOutboundMessage.java b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/hawkbit/model/outbound/ThingRemovedOutboundMessage.java new file mode 100644 index 000000000..85f3d7b8d --- /dev/null +++ b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/hawkbit/model/outbound/ThingRemovedOutboundMessage.java @@ -0,0 +1,48 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.hawkbit.model.outbound; + +import java.util.HashMap; +import java.util.Map; + +import javax.validation.constraints.NotNull; + +import eu.arrowhead.core.confmgr.hawkbit.model.MessageHeader; +import lombok.Builder; +import lombok.Data; + + +/** + * Transfer object, encapsulating THING_REMOVED requests, sent by the HawkBit client. + */ +@Builder +@Data +public class ThingRemovedOutboundMessage { + + @NotNull + private Headers headers; + + @Builder + @Data + public static class Headers { + private final String type = MessageTypeOutbound.THING_REMOVED.toString(); + private String thingId; + private String tenant; + + public Map asMap() { + HashMap headers = new HashMap<>(); + if (this.type != null) headers.put(MessageHeader.TYPE.toString(), this.type); + if (this.thingId != null) headers.put(MessageHeader.THING_ID.toString(), this.thingId); + if (this.tenant != null) headers.put(MessageHeader.TENANT.toString(), this.tenant); + return headers; + } + } +} diff --git a/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/hawkbit/model/outbound/UpdateActionStatusOutboundMessage.java b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/hawkbit/model/outbound/UpdateActionStatusOutboundMessage.java new file mode 100644 index 000000000..81b27d495 --- /dev/null +++ b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/hawkbit/model/outbound/UpdateActionStatusOutboundMessage.java @@ -0,0 +1,85 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.hawkbit.model.outbound; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.validation.ConstraintViolationException; +import javax.validation.Valid; +import javax.validation.constraints.NotNull; + +import eu.arrowhead.core.confmgr.hawkbit.model.ActionStatus; +import eu.arrowhead.core.confmgr.hawkbit.model.EventType; +import eu.arrowhead.core.confmgr.hawkbit.model.MessageHeader; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + + +/** + * Transfer object, encapsulating UPDATE_ACTION_STATUS requests, sent by the HawkBit client. + */ +@AllArgsConstructor +@NoArgsConstructor +@Data +@Builder +public class UpdateActionStatusOutboundMessage { + + @Valid + @NotNull + private UpdateActionStatusOutboundMessageBody body; + + @Valid + @NotNull + private UpdateActionStatusOutboundMessageHeaders headers; + + @AllArgsConstructor + @NoArgsConstructor + @Data + @Builder + public static class UpdateActionStatusOutboundMessageBody { + @NotNull + private Long actionId; + + @NotNull + private ActionStatus actionStatus; + + private Long softwareModuleId; + + private List message; + } + + @AllArgsConstructor + @NoArgsConstructor + @Data + @Builder + public static class UpdateActionStatusOutboundMessageHeaders { + @NotNull + private final String type = MessageTypeOutbound.EVENT.toString(); + + @NotNull + private final String topic = EventType.UPDATE_ACTION_STATUS.toString(); + + private String tenant; + + public Map asMap() throws ConstraintViolationException { + HashMap headers = new HashMap<>(); + if (this.type != null) headers.put(MessageHeader.TYPE.toString(), this.type); + if (this.topic != null) headers.put(MessageHeader.TOPIC.toString(), this.topic); + if (this.tenant != null) headers.put(MessageHeader.TENANT.toString(), this.tenant); + return headers; + } + } + +} diff --git a/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/model/HawkbitActionUpdateStatus.java b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/model/HawkbitActionUpdateStatus.java new file mode 100644 index 000000000..63ada5140 --- /dev/null +++ b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/model/HawkbitActionUpdateStatus.java @@ -0,0 +1,35 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.model; + +import java.util.List; + +import eu.arrowhead.core.confmgr.hawkbit.model.ActionStatus; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Internal model for representation of the update of a status of an action in hawkBit. + */ +@AllArgsConstructor +@NoArgsConstructor +@Data +@Builder +public class HawkbitActionUpdateStatus { + + private Long actionId; + private Long softwareModuleId; + private ActionStatus actionStatus; + private List message; + +} \ No newline at end of file diff --git a/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/properties/SystemProperties.java b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/properties/SystemProperties.java new file mode 100644 index 000000000..878862bfe --- /dev/null +++ b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/properties/SystemProperties.java @@ -0,0 +1,75 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.properties; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +import lombok.Data; + +/** + * This class provides all properties of the configuration system itself. + */ +@Data +public class SystemProperties { + + /** + * This is the address of the configuration system itself, e.g. "https://192.168.1.1" or + * "https://subdomain.domain.tld". + *

It must be not blank (validated with {@link NotBlank @NotBlank}). + */ + @NotBlank + private String address; + + /** + * This is the port of the configuration system itself, e.g. 8445. + *

It must be not null (validated with {@link NotNull @NotNull}). + */ + @NotNull + private Integer port; + + /** + * This is the name of the configuration system itself, e.g. "conf-system". + *

In the context of Arrowhead the system (provider) name and certificate common name must match. + *

It must be not blank (validated with {@link NotBlank @NotBlank}). + */ + @NotBlank + private String name; + + /** + * This is the protocol of the provided service from the configuration system itself, e.g. "HTTPS-SECURE-JSON". + *

It must be not blank (validated with {@link NotBlank @NotBlank}). + */ + @NotBlank + private String providedServiceInterface; + + /** + * This is the definition of the provided service from the configuration system itself, e.g. "definition5". + *

It must be not blank (validated with {@link NotBlank @NotBlank}). + */ + @NotBlank + private String providedServiceDefinition; + + /** + * This is the uri of the provided service from the configuration system itself, e.g "/". + *

It must be not blank (validated with {@link NotBlank @NotBlank}). + */ + @NotBlank + private String providedServiceUri; + + /** + * This is the version of the provided service from the configuration system itself, e.g. 2. + *

It must be not null (validated with {@link NotNull @NotNull}). + */ + @NotNull + private Integer providedServiceVersion; + +} diff --git a/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/security/CidValidator.java b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/security/CidValidator.java new file mode 100644 index 000000000..184238c67 --- /dev/null +++ b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/security/CidValidator.java @@ -0,0 +1,34 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.security; + +import org.jose4j.jwt.JwtClaims; +import org.jose4j.jwt.consumer.ErrorCodeValidator; +import org.jose4j.jwt.consumer.ErrorCodes; +import org.jose4j.jwt.consumer.JwtContext; + +/** + * The cid validator checks that the claim "cid" is available in a JWT. + */ +public class CidValidator implements ErrorCodeValidator { + + @Override + public Error validate(JwtContext jwtContext) { + JwtClaims jwtClaims = jwtContext.getJwtClaims(); + Object clientIdentifier = jwtClaims.getClaimValue(JwtAuthenticationToken.CLIENT_IDENTIFIER_CLAIM); + if (clientIdentifier == null ) { + return new Error(ErrorCodes.MISCELLANEOUS, "No client identifier (cid) claim is present."); + } + + return null; + } + +} diff --git a/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/security/JwtAuthenticationFilter.java b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/security/JwtAuthenticationFilter.java new file mode 100644 index 000000000..905265dbe --- /dev/null +++ b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/security/JwtAuthenticationFilter.java @@ -0,0 +1,145 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.security; + +import java.io.IOException; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.jose4j.jwa.AlgorithmConstraints; +import org.jose4j.jwe.ContentEncryptionAlgorithmIdentifiers; +import org.jose4j.jwe.KeyManagementAlgorithmIdentifiers; +import org.jose4j.jws.AlgorithmIdentifiers; +import org.jose4j.jwt.JwtClaims; +import org.jose4j.jwt.consumer.InvalidJwtException; +import org.jose4j.jwt.consumer.JwtConsumer; +import org.jose4j.jwt.consumer.JwtConsumerBuilder; +import org.springframework.http.HttpHeaders; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.filter.OncePerRequestFilter; + +import eu.arrowhead.core.confmgr.service.ArrowheadService; +import lombok.extern.log4j.Log4j2; + +/** + * Implementation of JWT authentication filter for Spring Security. It supports bearer tokens (JWT) in the standard + * HTTP authentication header "Authorization". A subject is authenticated if it provides a valid JWT with all required + * claims according to {@link #validateJwtAndRetrieveClaims(String, PublicKey, PrivateKey)}. + */ +@Log4j2 +public class JwtAuthenticationFilter extends OncePerRequestFilter { + + /** + * The value of the header "Authorization" must start with "Bearer " according to + * RFC6750. + */ + private static final String AUTHENTICATION_SCHEME_BEARER = "Bearer "; + + private final ArrowheadService arrowheadService; + private final PrivateKey configurationSystemPrivateKey; + + /** + * A filter for validation JWT. + * + * @param arrowheadService required to receive the public key for JWT validation + * @param configurationSystemPrivateKey the private key for JWT encryption + */ + public JwtAuthenticationFilter(ArrowheadService arrowheadService, PrivateKey configurationSystemPrivateKey) { + this.arrowheadService = arrowheadService; + this.configurationSystemPrivateKey = configurationSystemPrivateKey; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) + throws IOException, ServletException { + String authorizationHeader = request.getHeader(HttpHeaders.AUTHORIZATION); + + if (authorizationHeader == null || !authorizationHeader.startsWith(AUTHENTICATION_SCHEME_BEARER)) { + log.debug("No bearer token found"); + chain.doFilter(request, response); + return; + } + String clientJwtString = authorizationHeader.replaceFirst(AUTHENTICATION_SCHEME_BEARER, ""); + + try { + + SecurityContext context = SecurityContextHolder.createEmptyContext(); + String authorizationSystemPublicKeyString = arrowheadService.receiveAuthorizationSystemPublicKey(); + PublicKey authorizationSystemPublicKey = loadPublicKeyFromString(authorizationSystemPublicKeyString); + + JwtClaims clientJwtClaims = validateJwtAndRetrieveClaims(clientJwtString, authorizationSystemPublicKey, + this.configurationSystemPrivateKey); + JwtAuthenticationToken authentication = new JwtAuthenticationToken(clientJwtClaims, true); + context.setAuthentication(authentication); + + SecurityContextHolder.setContext(context); + + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + log.error("Public key from JWT provider couldn't be loaded", e); + } catch (InvalidJwtException e) { + log.info("The provided JWT is not valid", e); + } finally { + chain.doFilter(request, response); + } + + } + + /** + * Validates a JWT and retrieves it claims. + * + * @param clientJwt the JWT that should be validated + * @param publicKey the public key for verification of the JWT + * @param privateKey the private key for encryption of the JWT + * @return on success: all claims of the JWT + * @throws InvalidJwtException if the JWT is not valid + */ + private JwtClaims validateJwtAndRetrieveClaims(String clientJwt, PublicKey publicKey, PrivateKey privateKey) + throws InvalidJwtException { + JwtConsumer jwtConsumer = new JwtConsumerBuilder() + .registerValidator(new CidValidator()) + .setRequireJwtId() + .setRequireNotBefore() + .setEnableRequireEncryption() + .setEnableRequireIntegrity() + .setDecryptionKey(privateKey) + .setVerificationKey(publicKey) + .setJwsAlgorithmConstraints(new AlgorithmConstraints( + AlgorithmConstraints.ConstraintType.WHITELIST, + AlgorithmIdentifiers.RSA_USING_SHA512)) + .setJweAlgorithmConstraints(new AlgorithmConstraints( + AlgorithmConstraints.ConstraintType.WHITELIST, + KeyManagementAlgorithmIdentifiers.RSA_OAEP_256)) + .setJweContentEncryptionAlgorithmConstraints(new AlgorithmConstraints( + AlgorithmConstraints.ConstraintType.WHITELIST, + ContentEncryptionAlgorithmIdentifiers.AES_256_CBC_HMAC_SHA_512)) + .build(); + return jwtConsumer.processToClaims(clientJwt); + } + + private PublicKey loadPublicKeyFromString(String publicKeyString) throws NoSuchAlgorithmException, + InvalidKeySpecException { + KeyFactory kf = KeyFactory.getInstance("RSA"); + byte[] keyBytes = Base64.getDecoder().decode(publicKeyString); + return kf.generatePublic(new X509EncodedKeySpec(keyBytes)); + } + +} \ No newline at end of file diff --git a/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/security/JwtAuthenticationToken.java b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/security/JwtAuthenticationToken.java new file mode 100644 index 000000000..919e4d3a6 --- /dev/null +++ b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/security/JwtAuthenticationToken.java @@ -0,0 +1,56 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.security; + +import org.jose4j.jwt.JwtClaims; +import org.springframework.security.authentication.AbstractAuthenticationToken; + +public class JwtAuthenticationToken extends AbstractAuthenticationToken { + private static final long serialVersionUID = 1L; + + public static final String CLIENT_IDENTIFIER_CLAIM = "cid"; + + private final String clientIdentifier; + private final JwtClaims jwtClaims; + + /** + * Creates a JWT (JSON Web Token) authentication token that can be used in the context of Spring Security. + * + * @param jwtClaims the claims of the JWT itself + * @param authenticated if the JWT is already validated and therefore authenticated + */ + public JwtAuthenticationToken(JwtClaims jwtClaims, boolean authenticated) { + super(null); + this.jwtClaims = jwtClaims; + this.clientIdentifier = String.valueOf(jwtClaims.getClaimValue(CLIENT_IDENTIFIER_CLAIM)); + super.setAuthenticated(authenticated); + } + + /** + * + * @return the client identifier (cid claim) of the JWT + */ + @Override + public String getName() { + return this.clientIdentifier; + } + + @Override + public Object getPrincipal() { + return this.jwtClaims; + } + + @Override + public Object getCredentials() { + return this.jwtClaims.toString(); + } + +} diff --git a/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/service/ArrowheadService.java b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/service/ArrowheadService.java new file mode 100644 index 000000000..cf6e2ee44 --- /dev/null +++ b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/service/ArrowheadService.java @@ -0,0 +1,45 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import eu.arrowhead.core.confmgr.arrowhead.ArrowheadAuthorizationSystemClient; + +/** + * This is the Arrowhead service that implements business logic specifically for the configuration system. + */ +@Service +public class ArrowheadService { + + private final ArrowheadAuthorizationSystemClient authorizationSystemClient; + + @Autowired + public ArrowheadService( + ArrowheadAuthorizationSystemClient arrowheadAuthorizationSystemClient + ) { + this.authorizationSystemClient = arrowheadAuthorizationSystemClient; + } + + /** + * Returns the public key of the Arrowhead authorization system. The public key is used for validating JWT issued + * by the authorization system itself. + *

+ * The result of this method is cached, the configuration for caching can be found in resources/ehcache.xml. + * + * @return public key of Arrowhead authorization system + */ + public String receiveAuthorizationSystemPublicKey() { + return this.authorizationSystemClient.getPublicKey(); + } + +} diff --git a/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/service/HawkbitService.java b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/service/HawkbitService.java new file mode 100644 index 000000000..42f1a1fdb --- /dev/null +++ b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/service/HawkbitService.java @@ -0,0 +1,111 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.service; + +import java.io.IOException; + +import javax.validation.ConstraintViolationException; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import eu.arrowhead.core.confmgr.hawkbit.HawkbitDmfOutboundClient; +import eu.arrowhead.core.confmgr.hawkbit.model.outbound.ThingCreatedOutboundMessage; +import eu.arrowhead.core.confmgr.hawkbit.model.outbound.ThingRemovedOutboundMessage; +import eu.arrowhead.core.confmgr.hawkbit.model.outbound.UpdateActionStatusOutboundMessage; +import eu.arrowhead.core.confmgr.model.HawkbitActionUpdateStatus; +import lombok.extern.log4j.Log4j2; + +/** + * This is the hawkBit service that implements business logic specifically for + * the configuration system. + */ +@Log4j2 +@Service +public class HawkbitService { + + private final HawkbitDmfOutboundClient hawkbitDmfClient; + private final String hawkbitTenant; + + @Autowired + public HawkbitService(@Value("${hawkbit.tenant}") String hawkbitTenant, HawkbitDmfOutboundClient hawkbitDmfClient) { + this.hawkbitDmfClient = hawkbitDmfClient; + this.hawkbitTenant = hawkbitTenant; + } + + /** + * Create a new device in HawkBit. + * + * @param deviceId the id of the device that should be create + */ + public void createDevice(String deviceId) { + try { + ThingCreatedOutboundMessage message = ThingCreatedOutboundMessage.builder() + .body( + ThingCreatedOutboundMessage.Body.builder() + .name(deviceId) + .build() + ) + .headers( + ThingCreatedOutboundMessage.Headers.builder() + .thingId(deviceId) + .tenant(hawkbitTenant) + .build() + ) + .build(); + this.hawkbitDmfClient.createThing(message); + } catch (IOException e) { + log.error("Creating new device was not possible", e); + } + } + + public void removeDevice(String deviceId) { + ThingRemovedOutboundMessage message = ThingRemovedOutboundMessage.builder() + .headers( + ThingRemovedOutboundMessage.Headers.builder() + .thingId(deviceId) + .tenant(hawkbitTenant) + .build() + ) + .build(); + + try { + this.hawkbitDmfClient.removeThing(message); + } catch (IOException e) { + log.error("Deleting the device was not possible", e); + } + } + + /** + * Update the status of an action in hawkBit. + * + * @param actionUpdateStatus contains the information for the new status of an + * action + * @throws ConstraintViolationException if a specification for sending a message + * to hawkBit is not adhered + * @throws IOException if there is a connection problem with + * hawkBit + */ + public void updateActionStatus(HawkbitActionUpdateStatus actionUpdateStatus) + throws ConstraintViolationException, IOException { + UpdateActionStatusOutboundMessage message = UpdateActionStatusOutboundMessage.builder() + .body(UpdateActionStatusOutboundMessage.UpdateActionStatusOutboundMessageBody.builder() + .actionId(actionUpdateStatus.getActionId()) + .actionStatus(actionUpdateStatus.getActionStatus()) + .softwareModuleId(actionUpdateStatus.getSoftwareModuleId()) + .message(actionUpdateStatus.getMessage()).build()) + .headers(UpdateActionStatusOutboundMessage.UpdateActionStatusOutboundMessageHeaders.builder() + .tenant("DEFAULT").build()) + .build(); + this.hawkbitDmfClient.updateActionStatus(message); + } +} diff --git a/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/service/WebSocketService.java b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/service/WebSocketService.java new file mode 100644 index 000000000..ebfb90929 --- /dev/null +++ b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/service/WebSocketService.java @@ -0,0 +1,80 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.service; + +import java.io.IOException; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import eu.arrowhead.core.confmgr.hawkbit.model.inbound.CancelDownloadInboundMessage; +import eu.arrowhead.core.confmgr.hawkbit.model.inbound.DownloadRequestInboundMessage; +import eu.arrowhead.core.confmgr.hawkbit.model.inbound.MessageTypeInbound; +import eu.arrowhead.core.confmgr.hawkbit.model.inbound.ThingDeletedInboundMessage; +import eu.arrowhead.core.confmgr.websocket.DeviceNotConnectedException; +import eu.arrowhead.core.confmgr.websocket.WebsocketSender; +import eu.arrowhead.core.confmgr.websocket.model.DeviceMessage; +import lombok.extern.log4j.Log4j2; + +/** + * This is the web socket service that implements business logic specifically + * for the configuration system. + */ +@Log4j2 +@Service +public class WebSocketService { + private final WebsocketSender wsSender; + + @Autowired + public WebSocketService(WebsocketSender wsSender) throws IOException { + this.wsSender = wsSender; + } + + public void sendDownloadEventMessage(DownloadRequestInboundMessage message) + throws IOException, DeviceNotConnectedException { + log.info("Message {}", message); + String clientId = message.getHeaders().getThingId(); + + DeviceMessage deviceMessage = DeviceMessage.builder() + .type(MessageTypeInbound.EVENT.toString()) + .message(message) + .build(); + + this.wsSender.sendMessage(clientId, deviceMessage); + } + + public void sendThingDeletedMessage(ThingDeletedInboundMessage message) throws IOException, + DeviceNotConnectedException { + log.info("Message {}", message); + String clientId = message.getHeaders().getThingId(); + + DeviceMessage deviceMessage = DeviceMessage.builder() + .type(MessageTypeInbound.THING_DELETED.toString()) + .message(message) + .build(); + + this.wsSender.sendMessage(clientId, deviceMessage); + } + + public void sendCancelDownloadMessage(CancelDownloadInboundMessage message) throws IOException, + DeviceNotConnectedException { + log.info("Message {}", message); + + String clientId = message.getHeaders().getThingId(); + + DeviceMessage deviceMessage = DeviceMessage.builder() + .type(MessageTypeInbound.EVENT.toString()) + .message(message) + .build(); + + this.wsSender.sendMessage(clientId, deviceMessage); + } +} diff --git a/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/websocket/DeviceNotConnectedException.java b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/websocket/DeviceNotConnectedException.java new file mode 100644 index 000000000..146ac130e --- /dev/null +++ b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/websocket/DeviceNotConnectedException.java @@ -0,0 +1,30 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.websocket; + +public class DeviceNotConnectedException extends Exception { + private static final long serialVersionUID = 7329483234L; + + public DeviceNotConnectedException() { super(); } + public DeviceNotConnectedException(String message) { super(message); } + public DeviceNotConnectedException(String message, Throwable cause) { super(message, cause); } + public DeviceNotConnectedException(Throwable cause) { super(cause); } +} diff --git a/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/websocket/WebSocketController.java b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/websocket/WebSocketController.java new file mode 100644 index 000000000..4c6ed2b13 --- /dev/null +++ b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/websocket/WebSocketController.java @@ -0,0 +1,72 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.websocket; + +import java.io.IOException; +import java.util.Map; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.handler.AbstractWebSocketHandler; + +import eu.arrowhead.core.confmgr.service.HawkbitService; +import eu.arrowhead.core.confmgr.websocket.model.ActionUpdateStatusMapper; +import eu.arrowhead.core.confmgr.websocket.model.UpdateActionRequestDTO; +import lombok.extern.log4j.Log4j2; + +@Log4j2 +@Controller +public class WebSocketController extends AbstractWebSocketHandler { + private final HawkbitService hawkbitService; + private final ObjectMapper objectMapper; + private final Map webSocketSessionMap; + + @Autowired + public WebSocketController(HawkbitService hawkbitService, Map webSocketSessionMap) { + this.objectMapper = new ObjectMapper(); + this.hawkbitService = hawkbitService; + this.webSocketSessionMap = webSocketSessionMap; + } + + @Override + public void afterConnectionEstablished(WebSocketSession session) throws IOException { + if (session.getPrincipal() != null) { + String clientId = session.getPrincipal().getName(); + this.hawkbitService.createDevice(clientId); + this.webSocketSessionMap.put(clientId, session); + } else { + session.close(); + } + } + + @Override + public void afterConnectionClosed(WebSocketSession session, CloseStatus status) { + if (session.getPrincipal() != null) { + this.webSocketSessionMap.remove(session.getPrincipal().getName()); + } + } + + @Override + protected void handleTextMessage(WebSocketSession session, TextMessage message) { + try { + UpdateActionRequestDTO request = objectMapper.readValue(message.getPayload(), UpdateActionRequestDTO.class); + this.hawkbitService.updateActionStatus(ActionUpdateStatusMapper.mapToActionUpdateStatus(request)); + } catch (Exception e) { + log.error("Web socket text message could not be handled", e); + } + } + +} diff --git a/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/websocket/WebsocketSender.java b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/websocket/WebsocketSender.java new file mode 100644 index 000000000..8fd880093 --- /dev/null +++ b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/websocket/WebsocketSender.java @@ -0,0 +1,48 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.websocket; + +import java.io.IOException; +import java.util.Map; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; + +import eu.arrowhead.core.confmgr.websocket.model.DeviceMessage; + +@Component +public class WebsocketSender { + private final Map webSocketSessionMap; + private final ObjectMapper objectMapper; + + @Autowired + public WebsocketSender(Map webSocketSessionMap) { + this.webSocketSessionMap = webSocketSessionMap; + this.objectMapper = new ObjectMapper(); + } + + public void sendMessage(String thingId, DeviceMessage message) throws IOException, DeviceNotConnectedException { + String body = this.objectMapper.writeValueAsString(message); + TextMessage wsMessage = new TextMessage(body); + if(this.webSocketSessionMap.containsKey(thingId)){ + WebSocketSession session = this.webSocketSessionMap.get(thingId); + + session.sendMessage(wsMessage); + } else { + throw new DeviceNotConnectedException("The device" + thingId + "is currently not connected."); + } + + } +} diff --git a/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/websocket/model/ActionUpdateStatusMapper.java b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/websocket/model/ActionUpdateStatusMapper.java new file mode 100644 index 000000000..2a01c1d2f --- /dev/null +++ b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/websocket/model/ActionUpdateStatusMapper.java @@ -0,0 +1,30 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.websocket.model; + +import eu.arrowhead.core.confmgr.hawkbit.model.ActionStatus; +import eu.arrowhead.core.confmgr.model.HawkbitActionUpdateStatus; + +public class ActionUpdateStatusMapper { + + private ActionUpdateStatusMapper() { + } + + public static HawkbitActionUpdateStatus mapToActionUpdateStatus(UpdateActionRequestDTO input) throws IllegalArgumentException { + return HawkbitActionUpdateStatus.builder() + .actionId(input.getActionId()) + .actionStatus(ActionStatus.valueOf(input.getActionStatus())) + .message(input.getMessage()) + .softwareModuleId(input.getSoftwareModuleId()) + .build(); + } + +} diff --git a/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/websocket/model/DeviceMessage.java b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/websocket/model/DeviceMessage.java new file mode 100644 index 000000000..c7a3ed569 --- /dev/null +++ b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/websocket/model/DeviceMessage.java @@ -0,0 +1,22 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.websocket.model; + +import eu.arrowhead.core.confmgr.hawkbit.model.inbound.InboundMessage; +import lombok.Builder; +import lombok.Data; + +@Builder +@Data +public class DeviceMessage { + private final String type; + private InboundMessage message; +} diff --git a/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/websocket/model/UpdateActionRequestDTO.java b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/websocket/model/UpdateActionRequestDTO.java new file mode 100644 index 000000000..92cd85cc7 --- /dev/null +++ b/configuration-manager/src/main/java/eu/arrowhead/core/confmgr/websocket/model/UpdateActionRequestDTO.java @@ -0,0 +1,31 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.websocket.model; + +import java.util.List; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +@Data +@Builder +public class UpdateActionRequestDTO { + + private Long actionId; + private Long softwareModuleId; + private String actionStatus; + private List message; + +} \ No newline at end of file diff --git a/configuration-manager/src/main/resources/application.yml b/configuration-manager/src/main/resources/application.yml new file mode 100644 index 000000000..bb22b019f --- /dev/null +++ b/configuration-manager/src/main/resources/application.yml @@ -0,0 +1,65 @@ +--- +spring: + profiles: + active: develop + +# Configuration of the WebSocket endpoint +# Listens on port 8443 and loads its certificate from the truststore +server: + port: 8447 + ssl: + key-store: path/to/key-store.p12 + key-store-password: password + keyStoreType: PKCS12 + + +# These are the properties of the configuration system itself +# The properties are loaded into the class eu.arrowhead.core.confsys.properties.SystemProperties +system: + # This is the address of the configuration system itself, e.g. "https://192.168.1.1" or "https://subdomain.domain.tld" + address: + # This is the port of the configuration system itself, e.g. 8445 + port: + # This is the name of the configuration system itself, e.g. "conf-system". In the context of Arrowhead the system + # (provider) name and certificate common name must match + name: configuration + # This is the protocol of the provided service from the configuration system itself, e.g. "HTTPS-SECURE-JSON" + providedServiceInterface: HTTPS-SECURE-JSON + # This is the definition of the provided service from the configuration system itself, e.g. "definition5" + providedServiceDefinition: + # This is the uri of the provided service from the configuration system itself, e.g "/" + providedServiceUri: / + # This is the version of the provided service from the configuration system itself, e.g. 2 + providedServiceVersion: 1 + +# This is the truststore the configuration system uses for verifying the identity of clients in the Arrowhead system +trustStore: + path: path/to/trust-store.p12 + password: password + +# This is the keystore the configuration system uses for mutual authentication against the Arrowhead system +keyStore: + path: path/to/key-store.p12 + password: password + alias: "configuration.example.corp.arrowhead.eu" + +# These are the connection parameters for the Arrowhead authorization system +arrowheadAuthorizationSystem: + protocol: https:// + address: + port: + +# These are the connection parameters for the Arrowhead service registry +arrowheadServiceRegistry: + protocol: https:// + address: + port: + +# These are the connection parameters for the hawkBit DMF API +hawkbit: + host: + port: + username: + password: + # This is the tenant in hawkBit itself + tenant: DEFAULT \ No newline at end of file diff --git a/configuration-manager/src/main/resources/log4j2.xml b/configuration-manager/src/main/resources/log4j2.xml new file mode 100644 index 000000000..a032f1984 --- /dev/null +++ b/configuration-manager/src/main/resources/log4j2.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/arrowhead/ArrowheadAuthorizationSystemClientTest.java b/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/arrowhead/ArrowheadAuthorizationSystemClientTest.java new file mode 100644 index 000000000..0abe973a1 --- /dev/null +++ b/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/arrowhead/ArrowheadAuthorizationSystemClientTest.java @@ -0,0 +1,50 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.arrowhead; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertThat; + +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.Test; + +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; + +public class ArrowheadAuthorizationSystemClientTest { + + @Test + public void givenPublicKeyIsAvailable_whenGetPublicKey_thenPublicKeyReceived() throws Exception { + MockWebServer mockWebServer = new MockWebServer(); + mockWebServer.enqueue(new MockResponse() + .setResponseCode(200) + .addHeader("Content-Type", "text/plain") + .setBody("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvmaFymz4cSB+LP3pixhhgrczq8G2rWTMyyajMQNSXOuvGDBhB7S32rl/+B8VUvTrYBx7ab8AM/TxrDwWAzsQ3p3oo0+5NNibHpZ8QucWIXqbpQqFobiNWx+ogoBLV4UfJekxXYH54QrTdwkvlOFXxjkL11uwBKEsZ0TzKgGaRs+iR80sjDeicnpPlMm0RbpXjx10OzmjqfC1Wrusyl2gjsW9ySfwJ1461n6cPndn8csvC37IHdHpkKTRlcDDPG6/GN3+vfUeznA7zGFWv2IOTzsuruehXaOuDNgekvREK3Xh4BpvGTZ6oEFVmr5853U3PS6ExKlvwR8NwFiPXI9LAQIDAQAB")); + mockWebServer.start(); + + String baseUrl = mockWebServer.url("").toString(); + ArrowheadAuthorizationSystemClient client = new ArrowheadAuthorizationSystemClient(baseUrl); + + String publicKey = client.getPublicKey(); + + RecordedRequest recordedRequest = mockWebServer.takeRequest(10, TimeUnit.SECONDS); + assertThat(recordedRequest).isNotNull(); + assertThat(recordedRequest.getMethod()).isEqualTo("GET"); + assertThat(recordedRequest.getPath()).isEqualTo("/authorization/publickey"); + + assertThat(publicKey).isEqualTo("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvmaFymz4cSB+LP3pixhhgrczq8G2rWTMyyajMQNSXOuvGDBhB7S32rl/+B8VUvTrYBx7ab8AM/TxrDwWAzsQ3p3oo0+5NNibHpZ8QucWIXqbpQqFobiNWx+ogoBLV4UfJekxXYH54QrTdwkvlOFXxjkL11uwBKEsZ0TzKgGaRs+iR80sjDeicnpPlMm0RbpXjx10OzmjqfC1Wrusyl2gjsW9ySfwJ1461n6cPndn8csvC37IHdHpkKTRlcDDPG6/GN3+vfUeznA7zGFWv2IOTzsuruehXaOuDNgekvREK3Xh4BpvGTZ6oEFVmr5853U3PS6ExKlvwR8NwFiPXI9LAQIDAQAB"); + + mockWebServer.close(); + } + +} diff --git a/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/arrowhead/ArrowheadServiceRegistryClientTest.java b/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/arrowhead/ArrowheadServiceRegistryClientTest.java new file mode 100644 index 000000000..db603ed24 --- /dev/null +++ b/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/arrowhead/ArrowheadServiceRegistryClientTest.java @@ -0,0 +1,232 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.arrowhead; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.catchThrowableOfType; + +import java.util.Collections; +import java.util.concurrent.TimeUnit; + +import javax.validation.ConstraintViolationException; + +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; +import org.skyscreamer.jsonassert.JSONCompareMode; +import org.springframework.http.HttpStatus; +import org.springframework.web.reactive.function.client.WebClientResponseException; + +import eu.arrowhead.core.confmgr.arrowhead.model.request.ServiceRegistryRequestDTO; +import eu.arrowhead.core.confmgr.arrowhead.model.request.SystemRequestDTO; +import eu.arrowhead.core.confmgr.arrowhead.model.response.ServiceDefinitionResponseDTO; +import eu.arrowhead.core.confmgr.arrowhead.model.response.ServiceInterfaceResponseDTO; +import eu.arrowhead.core.confmgr.arrowhead.model.response.ServiceRegistryResponseDTO; +import eu.arrowhead.core.confmgr.arrowhead.model.response.SystemResponseDTO; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; + +public class ArrowheadServiceRegistryClientTest { + + @Test + public void givenServiceIsNotRegistered_whenRegisterService_thenServiceIsRegistered() throws Exception { + MockWebServer mockWebServer = new MockWebServer(); + mockWebServer.enqueue(new MockResponse() + .setResponseCode(201) + .addHeader("Content-Type", "application/json") + .setBody("{\n" + + " \"id\": 22,\n" + + " \"serviceDefinition\": {\n" + + " \"id\": 7,\n" + + " \"serviceDefinition\": \"definition3\",\n" + + " \"createdAt\": \"2020-04-21 11:27:39\",\n" + + " \"updatedAt\": \"2020-04-21 11:27:39\"\n" + + " },\n" + + " \"provider\": {\n" + + " \"id\": 9,\n" + + " \"systemName\": \"conf-system\",\n" + + " \"address\": \"192.168.1.1\",\n" + + " \"port\": 1234,\n" + + " \"authenticationInfo\": \"test\",\n" + + " \"createdAt\": \"2020-05-05 06:37:19\",\n" + + " \"updatedAt\": \"2020-05-05 06:37:19\"\n" + + " },\n" + + " \"serviceUri\": \"/\",\n" + + " \"secure\": \"TOKEN\",\n" + + " \"version\": 1,\n" + + " \"interfaces\": [{\n" + + " \"id\": 3,\n" + + " \"interfaceName\": \"HTTPS-SECURE-JSON\",\n" + + " \"createdAt\": \"2020-04-21 11:27:39\",\n" + + " \"updatedAt\": \"2020-04-21 11:27:39\"\n" + + " }\n" + + " ],\n" + + " \"createdAt\": \"2020-05-05 06:37:19\",\n" + + " \"updatedAt\": \"2020-05-05 06:37:19\"\n" + + "}")); + mockWebServer.start(); + + String baseUrl = mockWebServer.url("").toString(); + ArrowheadServiceRegistryClient client = new ArrowheadServiceRegistryClient(baseUrl); + ServiceRegistryRequestDTO requestDTO = ServiceRegistryRequestDTO.builder() + .serviceDefinition("definition3") + .providerSystem(SystemRequestDTO.builder() + .systemName("conf-system") + .address("192.168.1.1") + .port(1234) + .authenticationInfo("test") + .build()) + .serviceUri("/") + .secure(ServiceRegistryRequestDTO.SecurityLevel.TOKEN) + .version(1) + .interfaces(Collections.singletonList("HTTPS-SECURE-JSON")) + .build(); + ServiceRegistryResponseDTO responseDTO = client.registerService(requestDTO); + + RecordedRequest recordedRequest = mockWebServer.takeRequest(10, TimeUnit.SECONDS); + assertThat(recordedRequest).isNotNull(); + assertThat(recordedRequest.getMethod()).isEqualTo("POST"); + assertThat(recordedRequest.getPath()).isEqualTo("/serviceregistry/register"); + JSONAssert.assertEquals("{\n" + + " \"serviceDefinition\": \"definition3\",\n" + + " \"providerSystem\": {\n" + + " \"systemName\": \"conf-system\",\n" + + " \"address\": \"192.168.1.1\",\n" + + " \"port\": 1234,\n" + + " \"authenticationInfo\": \"test\"\n" + + " },\n" + + " \"serviceUri\": \"/\",\n" + + " \"endOfValidity\": null,\n" + + " \"secure\": \"TOKEN\",\n" + + " \"metadata\": null,\n" + + " \"version\": 1,\n" + + " \"interfaces\": [\"HTTPS-SECURE-JSON\"]\n" + + "}", recordedRequest.getBody().readUtf8(), JSONCompareMode.NON_EXTENSIBLE); + + assertThat(responseDTO).isEqualTo(ServiceRegistryResponseDTO.builder() + .id(22L) + .serviceDefinition(ServiceDefinitionResponseDTO.builder() + .id(7L) + .serviceDefinition("definition3") + .createdAt("2020-04-21 11:27:39") + .updatedAt("2020-04-21 11:27:39") + .build()) + .provider(SystemResponseDTO.builder() + .id(9L) + .systemName("conf-system") + .address("192.168.1.1") + .port(1234) + .authenticationInfo("test") + .createdAt("2020-05-05 06:37:19") + .updatedAt("2020-05-05 06:37:19") + .build()) + .serviceUri("/") + .secure("TOKEN") + .version(1) + .interfaces(Collections.singletonList(ServiceInterfaceResponseDTO.builder() + .id(3L) + .interfaceName("HTTPS-SECURE-JSON") + .createdAt("2020-04-21 11:27:39") + .updatedAt("2020-04-21 11:27:39") + .build())) + .createdAt("2020-05-05 06:37:19") + .updatedAt("2020-05-05 06:37:19") + .build()); + mockWebServer.close(); + } + + @Test + public void givenServiceIsAlreadyRegistered_whenRegisterService_thenWebClientResponseExceptionIsThrown() throws Exception { + MockWebServer mockWebServer = new MockWebServer(); + mockWebServer.enqueue(new MockResponse() + .setResponseCode(400) + .addHeader("Content-Type", "application/json") + .setBody("{\n" + + " \"errorMessage\": \"Service Registry entry with provider: (conf-system, 192.168.1.1:1234) and service definition: definition3 already exists.\",\n" + + " \"errorCode\": 400,\n" + + " \"exceptionType\": \"INVALID_PARAMETER\"\n" + + "}")); + mockWebServer.start(); + + String baseUrl = mockWebServer.url("").toString(); + ArrowheadServiceRegistryClient client = new ArrowheadServiceRegistryClient(baseUrl); + ServiceRegistryRequestDTO requestDTO = ServiceRegistryRequestDTO.builder() + .serviceDefinition("definition3") + .providerSystem(SystemRequestDTO.builder() + .systemName("conf-system") + .address("192.168.1.1") + .port(1234) + .authenticationInfo("test") + .build()) + .serviceUri("/") + .secure(ServiceRegistryRequestDTO.SecurityLevel.TOKEN) + .version(1) + .interfaces(Collections.singletonList("HTTPS-SECURE-JSON")) + .build(); + + WebClientResponseException exception = catchThrowableOfType(() -> + client.registerService(requestDTO), WebClientResponseException.class); + + assertThat(exception).hasMessageContaining("400 Bad Request from POST"); + assertThat(exception.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); + + mockWebServer.close(); + } + + @Test + public void givenServiceRegistryRequestDTOIsInvalid_whenRegisterService_thenConstraintViolationExceptionIsThrown() throws Exception { + MockWebServer mockWebServer = new MockWebServer(); + mockWebServer.start(); + + String baseUrl = mockWebServer.url("").toString(); + ArrowheadServiceRegistryClient client = new ArrowheadServiceRegistryClient(baseUrl); + ServiceRegistryRequestDTO requestDTO = ServiceRegistryRequestDTO.builder().build(); + + assertThatThrownBy(() -> client.registerService(requestDTO)) + .isInstanceOf(ConstraintViolationException.class) + .hasMessageContaining("providerSystem: must not be null") + .hasMessageContaining("serviceDefinition: must not be null") + .hasMessageContaining("interfaces: must not be empty") + .hasMessageContaining("serviceUri: must not be null"); + assertThat(mockWebServer.getRequestCount()).isZero(); + + mockWebServer.close(); + } + + @Test + public void givenSystemRequestDTOIsInvalid_whenRegisterService_thenConstraintViolationExceptionIsThrown() throws Exception { + MockWebServer mockWebServer = new MockWebServer(); + mockWebServer.start(); + + String baseUrl = mockWebServer.url("").toString(); + ArrowheadServiceRegistryClient client = new ArrowheadServiceRegistryClient(baseUrl); + ServiceRegistryRequestDTO requestDTO = ServiceRegistryRequestDTO.builder() + .serviceDefinition("definition3") + .providerSystem(SystemRequestDTO.builder().build()) + .serviceUri("/") + .secure(ServiceRegistryRequestDTO.SecurityLevel.TOKEN) + .version(1) + .interfaces(Collections.singletonList("HTTPS-SECURE-JSON")) + .build(); + + assertThatThrownBy(() -> client.registerService(requestDTO)) + .isInstanceOf(ConstraintViolationException.class) + .hasMessageContaining("providerSystem.authenticationInfo: must not be null") + .hasMessageContaining("providerSystem.address: must not be null") + .hasMessageContaining("providerSystem.port: must not be null") + .hasMessageContaining("providerSystem.systemName: must not be null"); + assertThat(mockWebServer.getRequestCount()).isZero(); + + mockWebServer.close(); + } + +} diff --git a/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/arrowhead/CertificateCheckFailureIT.java b/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/arrowhead/CertificateCheckFailureIT.java new file mode 100644 index 000000000..954369d46 --- /dev/null +++ b/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/arrowhead/CertificateCheckFailureIT.java @@ -0,0 +1,84 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.arrowhead; + +import static org.junit.Assert.assertThrows; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.nio.channels.ClosedChannelException; +import java.util.Collections; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import eu.arrowhead.core.confmgr.arrowhead.model.request.ServiceRegistryRequestDTO; +import eu.arrowhead.core.confmgr.arrowhead.model.request.SystemRequestDTO; +import eu.arrowhead.core.confmgr.config.InitArrowheadMockServers; + + +@SpringBootTest +@ActiveProfiles("test") +public class CertificateCheckFailureIT { + private static InitArrowheadMockServers initMockServer; + + @Autowired + ArrowheadAuthorizationSystemClient authClient; + + @Autowired + ArrowheadServiceRegistryClient srClient; + + @BeforeAll + public static void beforeAll() { + CertificateCheckFailureIT.initMockServer = new InitArrowheadMockServers(); + CertificateCheckFailureIT.initMockServer.setUp(false); + } + + @Test + public void testMissingServerCertificatesAuthorizationSystem() { + Exception exception = assertThrows(Exception.class, () -> { + authClient.getPublicKey(); + }); + + assertEquals(ClosedChannelException.class, exception.getCause().getClass()); + } + + @Test + public void testMissingServerCertificatesServiceRegistry() { + ServiceRegistryRequestDTO requestDTO = ServiceRegistryRequestDTO.builder() + .serviceDefinition("definition3") + .providerSystem(SystemRequestDTO.builder() + .systemName("conf-system") + .address("192.168.1.1") + .port(1234) + .authenticationInfo("test") + .build()) + .serviceUri("/") + .secure(ServiceRegistryRequestDTO.SecurityLevel.TOKEN) + .version(1) + .interfaces(Collections.singletonList("HTTPS-SECURE-JSON")) + .build(); + + Exception exception = assertThrows(Exception.class, () -> { + srClient.registerService(requestDTO); + }); + + assertEquals(ClosedChannelException.class, exception.getCause().getClass()); + } + + @AfterAll + public static void afterAll() { + CertificateCheckFailureIT.initMockServer.shutDown(); + } +} diff --git a/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/arrowhead/CoreSystemsFailure.java b/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/arrowhead/CoreSystemsFailure.java new file mode 100644 index 000000000..b1913d5d8 --- /dev/null +++ b/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/arrowhead/CoreSystemsFailure.java @@ -0,0 +1,61 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.arrowhead; + +import static org.junit.Assert.assertThrows; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.net.ConnectException; +import java.util.Collections; + +import org.junit.jupiter.api.Test; + +import eu.arrowhead.core.confmgr.arrowhead.model.request.ServiceRegistryRequestDTO; +import eu.arrowhead.core.confmgr.arrowhead.model.request.SystemRequestDTO; + + +public class CoreSystemsFailure { + @Test + public void testServiceRegistryFailure() { + ArrowheadServiceRegistryClient srClient = new ArrowheadServiceRegistryClient("https://localhost:8443"); + + ServiceRegistryRequestDTO requestDTO = ServiceRegistryRequestDTO.builder() + .serviceDefinition("definition3") + .providerSystem(SystemRequestDTO.builder() + .systemName("conf-system") + .address("192.168.1.1") + .port(1234) + .authenticationInfo("test") + .build()) + .serviceUri("/") + .secure(ServiceRegistryRequestDTO.SecurityLevel.TOKEN) + .version(1) + .interfaces(Collections.singletonList("HTTPS-SECURE-JSON")) + .build(); + + Exception exception = assertThrows(Exception.class, () -> { + srClient.registerService(requestDTO); + }); + + assertEquals(ConnectException.class, exception.getCause().getCause().getClass()); + } + + @Test + public void testAuthorizationSystemFailure() { + ArrowheadAuthorizationSystemClient authClient = new ArrowheadAuthorizationSystemClient("https://localhost:8443"); + + Exception exception = assertThrows(Exception.class, () -> { + authClient.getPublicKey(); + }); + + assertEquals(ConnectException.class, exception.getCause().getCause().getClass()); + } +} diff --git a/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/config/InitArrowheadMockServers.java b/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/config/InitArrowheadMockServers.java new file mode 100644 index 000000000..17ce5e037 --- /dev/null +++ b/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/config/InitArrowheadMockServers.java @@ -0,0 +1,91 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.config; + +import static org.mockserver.model.HttpRequest.request; +import static org.mockserver.model.HttpResponse.response; + +import org.mockserver.configuration.ConfigurationProperties; +import org.mockserver.integration.ClientAndServer; + + +public class InitArrowheadMockServers { + + ClientAndServer serviceRegistryMockServer; + ClientAndServer authorizationSystemMockServer; + + public void setUp() { + startServiceRegistryMockServer(true); + startAuthorizationSystemMockServer(true); + } + + public void setUp(Boolean useCertificates) { + startServiceRegistryMockServer(useCertificates); + startAuthorizationSystemMockServer(useCertificates); + } + + private void startServiceRegistryMockServer(Boolean useCertificates) { + if(useCertificates) { + String certificateAuthorityKeyPath = PropertiesExtractor.getProperty("mockserver.certificateAuthorityPrivateKey"); + String certificateAuthorityCertificatePath = PropertiesExtractor.getProperty("mockserver.certificateAuthorityCertificate"); + String serviceRegistryKeyPath = PropertiesExtractor.getProperty("mockserver.sreg.privateKeyPath"); + String serviceRegistryCertPath = PropertiesExtractor.getProperty("mockserver.sreg.x509CertificatePath"); + + ConfigurationProperties.certificateAuthorityPrivateKey(certificateAuthorityKeyPath); + ConfigurationProperties.certificateAuthorityCertificate(certificateAuthorityCertificatePath); + ConfigurationProperties.privateKeyPath(serviceRegistryKeyPath); + ConfigurationProperties.x509CertificatePath(serviceRegistryCertPath); + } else { + ConfigurationProperties.certificateAuthorityPrivateKey(""); + ConfigurationProperties.certificateAuthorityCertificate(""); + ConfigurationProperties.privateKeyPath(""); + ConfigurationProperties.x509CertificatePath(""); + } + + serviceRegistryMockServer = ClientAndServer.startClientAndServer(8443); + + serviceRegistryMockServer.when(request().withPath("/serviceregistry/register")) + .respond(response().withStatusCode(400)); + } + + private void startAuthorizationSystemMockServer(Boolean useCertificates) { + if(useCertificates) { + String certificateAuthorityKeyPath = PropertiesExtractor.getProperty("mockserver.certificateAuthorityPrivateKey"); + String certificateAuthorityCertificatePath = PropertiesExtractor.getProperty("mockserver.certificateAuthorityCertificate"); + String authorizationSystemKeyPath = PropertiesExtractor.getProperty("mockserver.auth.privateKeyPath"); + String authorizationSystemCertPath = PropertiesExtractor.getProperty("mockserver.auth.x509CertificatePath"); + + ConfigurationProperties.certificateAuthorityPrivateKey(certificateAuthorityKeyPath); + ConfigurationProperties.certificateAuthorityCertificate(certificateAuthorityCertificatePath); + ConfigurationProperties.privateKeyPath(authorizationSystemKeyPath); + ConfigurationProperties.x509CertificatePath(authorizationSystemCertPath); + } + + String authorizationSystemPubKey = PropertiesExtractor.getProperty("mockserver.auth.pubKeyResponse"); + + authorizationSystemMockServer = ClientAndServer.startClientAndServer(8445); + + authorizationSystemMockServer.when( + request() + .withPath("/authorization/publickey") + ) + .respond( + response() + .withBody(authorizationSystemPubKey) + ); + } + + public void shutDown() { + serviceRegistryMockServer.stop(); + authorizationSystemMockServer.stop(); + } + +} diff --git a/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/config/PropertiesExtractor.java b/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/config/PropertiesExtractor.java new file mode 100644 index 000000000..d3b405e2b --- /dev/null +++ b/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/config/PropertiesExtractor.java @@ -0,0 +1,33 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.config; + +import java.io.FileInputStream; +import java.io.IOException; +import java.net.URL; +import java.util.Properties; + +public class PropertiesExtractor { + private static Properties properties; + static { + properties = new Properties(); + URL url = PropertiesExtractor.class.getClassLoader().getResource("mockserver.properties"); + try{ + properties.load(new FileInputStream(url.getPath())); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public static String getProperty(String key){ + return properties.getProperty(key); + } +} \ No newline at end of file diff --git a/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/config/TestConfig.java b/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/config/TestConfig.java new file mode 100644 index 000000000..5bbaaaf17 --- /dev/null +++ b/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/config/TestConfig.java @@ -0,0 +1,80 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.config; + +import java.net.Socket; +import java.security.cert.X509Certificate; + +import javax.net.ssl.SSLEngine; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509ExtendedTrustManager; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; + +import lombok.extern.log4j.Log4j2; + + +@Log4j2 +@Configuration +@Profile("test") +public class TestConfig { + /** + * This Bean returns a mocked TrustManager implementation to test + * whether the correct certificate is used during TLS handshake + * @return mocked TrustManager object + */ + @Bean + public TrustManager[] getTestTrustManager() { + TrustManager[] tm = { + new X509ExtendedTrustManager(){ + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket) { + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket) { + log.info("Server authentication with the following certificate:"); + log.error(chain[0]); + } + + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine) { + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine) { + log.info("Server authentication with the following certificate:"); + log.error(chain[0]); + } + + @Override + public java.security.cert.X509Certificate[] getAcceptedIssuers() { + return null; + } + + @Override + public void checkClientTrusted(X509Certificate[] certs, String authType) { + } + + @Override + public void checkServerTrusted(X509Certificate[] certs, String authType) { + log.info("Server authentication with the following certificate:"); + log.error(certs[0]); + } + } + }; + + return tm; + } + +} diff --git a/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/config/TestHawkbitConfig.java b/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/config/TestHawkbitConfig.java new file mode 100644 index 000000000..011872d67 --- /dev/null +++ b/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/config/TestHawkbitConfig.java @@ -0,0 +1,35 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.config; + +import com.github.fridujo.rabbitmq.mock.MockConnectionFactory; +import com.rabbitmq.client.ConnectionFactory; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.context.annotation.Profile; + +/** + * This class provides the clients for interaction with hawkBit via beans managed by the Spring container. + */ +@Configuration +@Profile("test") +public class TestHawkbitConfig { + @Primary + @Bean + public ConnectionFactory hawkbitConnectionFactory() { + MockConnectionFactory mockConnectionFactory = new MockConnectionFactory(); + mockConnectionFactory.setAutomaticRecoveryEnabled(true); + + return mockConnectionFactory; + } +} diff --git a/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/hawkbit/HawkbitDmfConsumerTest.java b/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/hawkbit/HawkbitDmfConsumerTest.java new file mode 100644 index 000000000..989a57d01 --- /dev/null +++ b/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/hawkbit/HawkbitDmfConsumerTest.java @@ -0,0 +1,149 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.hawkbit; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import java.io.IOException; +import java.util.Collections; +import java.util.Map; + +import com.github.fridujo.rabbitmq.mock.MockConnection; +import com.github.fridujo.rabbitmq.mock.MockConnectionFactory; +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.Envelope; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import eu.arrowhead.core.confmgr.hawkbit.model.inbound.CancelDownloadInboundMessage; +import eu.arrowhead.core.confmgr.hawkbit.model.inbound.DownloadRequestInboundMessage; +import eu.arrowhead.core.confmgr.hawkbit.model.inbound.ThingDeletedInboundMessage; +import eu.arrowhead.core.confmgr.service.WebSocketService; +import eu.arrowhead.core.confmgr.websocket.DeviceNotConnectedException; + +public class HawkbitDmfConsumerTest { + HawkbitDmfConsumer consumer; + MockConnection mockConnection; + WebSocketService mock_wsService; + + @BeforeEach + public void init() { + mockConnection = new MockConnectionFactory().newConnection(); + + mock_wsService = mock(WebSocketService.class); + consumer = new HawkbitDmfConsumer(mockConnection.createChannel(), mock_wsService); + } + + @Test + public void testDownloadRequestInboundGeneration() throws IOException, DeviceNotConnectedException { + AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder().contentType("application/json") + .headers(Map.ofEntries(Map.entry("type", "EVENT"), Map.entry("thingId", "device7"), + Map.entry("topic", "DOWNLOAD_AND_INSTALL"), Map.entry("tenant", "tenant1"))) + .build(); + + String body = "{\n" + " \"actionId\": 8,\n" + " \"targetSecurityToken\": \"aslkhju4kjasdz9uas\",\n" + + " \"softwareModules\": [{\n" + " \"moduleId\": 1,\n" + + " \"moduleType\": \"OS\",\n" + " \"moduleVersion\": \"1.0\",\n" + + " \"artifacts\": [{\n" + " \"filename\": \"os.zip\",\n" + + " \"urls\": {\n" + + " \"COAP\": \"coap://coap.local/os.zip\",\n" + + " \"HTTP\": \"http://localhost/os.zip\",\n" + + " \"HTTPS\": \"https://localhost/os.zip\"\n" + " },\n" + + " \"hashes\": {\n" + + " \"md5\": \"8c9693121361daf0d992df4eeb714ebb\",\n" + + " \"sha1\": \"e5ee90a0f8d0570b8bbdcaa23aa9a6b737c0c826\"\n" + + " },\n" + " \"size\": 539475\n" + " }\n" + + " ],\n" + " \"metadata\": [{\n" + + " \"key\": \"Description\",\n" + + " \"value\": \"This is the new operating system\"\n" + " }\n" + + " ]\n" + " }\n" + " ]\n" + "}"; + + DownloadRequestInboundMessage.Body assertBody = DownloadRequestInboundMessage.Body.builder().actionId(8L) + .targetSecurityToken("aslkhju4kjasdz9uas") + .softwareModules(Collections.singletonList(DownloadRequestInboundMessage.Body.SoftwareModule.builder() + .moduleId(1L).moduleType("OS").moduleVersion("1.0") + .artifacts(Collections.singletonList(DownloadRequestInboundMessage.Body.Artifact.builder() + .filename("os.zip") + .urls(DownloadRequestInboundMessage.Body.Urls.builder().COAP("coap://coap.local/os.zip") + .HTTP("http://localhost/os.zip").HTTPS("https://localhost/os.zip").build()) + .hashes(DownloadRequestInboundMessage.Body.Hashes.builder() + .md5("8c9693121361daf0d992df4eeb714ebb") + .sha1("e5ee90a0f8d0570b8bbdcaa23aa9a6b737c0c826").build()) + .size(539475L).build())) + .metadata(Collections.singletonList(DownloadRequestInboundMessage.Body.Metadata.builder() + .key("Description").value("This is the new operating system").build())) + .build())) + .build(); + + DownloadRequestInboundMessage.Headers assertHeaders = DownloadRequestInboundMessage.Headers.builder().type("EVENT") + .thingId("device7").topic("DOWNLOAD_AND_INSTALL").tenant("tenant1").build(); + + DownloadRequestInboundMessage assertMessage = DownloadRequestInboundMessage.builder().body(assertBody).deliveryTag(1l).headers(assertHeaders).build(); + + consumer.handleDelivery("tag", new Envelope(1L, false, "exchange", "routingKey"), properties, body.getBytes()); + + verify(mock_wsService).sendDownloadEventMessage(assertMessage); + } + + @Test + public void testCancelDownload() throws IOException, DeviceNotConnectedException { + AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder().contentType("application/json") + .headers(Map.ofEntries(Map.entry("type", "EVENT"), Map.entry("thingId", "device7"), + Map.entry("topic", "CANCEL_DOWNLOAD"), Map.entry("tenant", "tenant1"))) + .build(); + + String rawMessage = "{" + + "\"actionId\": 16" + + "}"; + + CancelDownloadInboundMessage cancelDownloadInboundMessage = CancelDownloadInboundMessage.builder() + .body( + CancelDownloadInboundMessage.Body.builder() + .actionId(16L) + .build() + ) + .headers( + CancelDownloadInboundMessage.Headers.builder() + .type("EVENT") + .tenant("tenant1") + .thingId("device7") + .topic("CANCEL_DOWNLOAD") + .build() + ) + .build(); + + consumer.handleDelivery("tag", new Envelope(1L, false, "exchange", "routingKey"), properties, rawMessage.getBytes()); + + verify(mock_wsService).sendCancelDownloadMessage(cancelDownloadInboundMessage); + } + + @Test + public void testThingDeleted() throws IOException, DeviceNotConnectedException { + AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder().contentType("application/json") + .headers(Map.ofEntries(Map.entry("type", "THING_DELETED"), Map.entry("thingId", "device7"), + Map.entry("topic", "THING_DELETED"), Map.entry("tenant", "tenant1"))) + .build(); + + ThingDeletedInboundMessage thingDeletedInboundMessage = ThingDeletedInboundMessage.builder() + .headers( + ThingDeletedInboundMessage.Headers.builder() + .thingId("device7") + .build() + ) + .build(); + + consumer.handleDelivery("tag", new Envelope(1L, false, "exchange", "routingKey"), properties, null); + + verify(mock_wsService).sendThingDeletedMessage(thingDeletedInboundMessage); + } +} diff --git a/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/hawkbit/HawkbitDmfOutboundClientTest.java b/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/hawkbit/HawkbitDmfOutboundClientTest.java new file mode 100644 index 000000000..b05e61174 --- /dev/null +++ b/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/hawkbit/HawkbitDmfOutboundClientTest.java @@ -0,0 +1,112 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.hawkbit; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; +import static org.junit.Assert.assertThat; + +import java.io.IOException; +import java.util.Collections; +import java.util.Map; + +import com.github.fridujo.rabbitmq.mock.MockConnection; +import com.github.fridujo.rabbitmq.mock.MockConnectionFactory; +import com.rabbitmq.client.AMQP; + +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; +import org.skyscreamer.jsonassert.JSONCompareMode; + +import eu.arrowhead.core.confmgr.hawkbit.model.ActionStatus; +import eu.arrowhead.core.confmgr.hawkbit.model.outbound.ThingCreatedOutboundMessage; +import eu.arrowhead.core.confmgr.hawkbit.model.outbound.UpdateActionStatusOutboundMessage; +import eu.arrowhead.core.confmgr.hawkbit.util.HawkbitDmfMockServer; +import eu.arrowhead.core.confmgr.hawkbit.util.Message; + +public class HawkbitDmfOutboundClientTest { + + @Test + public void givenCorrectMessage_whenCreateThing_thenThingIsCreated() throws IOException, JSONException { + MockConnection mockConnection = new MockConnectionFactory().newConnection(); + HawkbitDmfMockServer mockServer = new HawkbitDmfMockServer(mockConnection.createChannel()); + HawkbitDmfOutboundClient client = new HawkbitDmfOutboundClient(mockConnection.createChannel()); + + ThingCreatedOutboundMessage message = ThingCreatedOutboundMessage.builder() + .body(ThingCreatedOutboundMessage.Body.builder() + .name("device3") + .build()) + .headers(ThingCreatedOutboundMessage.Headers.builder() + .tenant("tenant1") + .thingId("device3") + .sender("testSender") + .build()) + .build(); + + client.createThing(message); + + await().until(() -> mockServer.getMessages().size() == 1); + Message receivedMessage = mockServer.getMessages().get(0); + assertThat(receivedMessage).isNotNull(); + assertThat(receivedMessage.getProperties()).isEqualTo(new AMQP.BasicProperties.Builder() + .headers(Map.ofEntries( + Map.entry("type", "THING_CREATED"), + Map.entry("thingId", "device3"), + Map.entry("sender", "testSender"), + Map.entry("tenant", "tenant1"))) + .contentType("application/json") + .replyTo("configuration_system.direct.exchange") + .build()); + JSONAssert.assertEquals("{\n" + + " \"name\": \"device3\"\n" + + "}", receivedMessage.getBody(), JSONCompareMode.NON_EXTENSIBLE); + } + + @Test + public void givenCorrectMessage_whenUpdateActionStatus_thenActionIsUpdated() throws IOException, JSONException { + MockConnection mockConnection = new MockConnectionFactory().newConnection(); + HawkbitDmfMockServer mockServer = new HawkbitDmfMockServer(mockConnection.createChannel()); + HawkbitDmfOutboundClient client = new HawkbitDmfOutboundClient(mockConnection.createChannel()); + + UpdateActionStatusOutboundMessage message = UpdateActionStatusOutboundMessage.builder() + .body(UpdateActionStatusOutboundMessage.UpdateActionStatusOutboundMessageBody.builder() + .actionId(7L) + .actionStatus(ActionStatus.FINISHED) + .message(Collections.singletonList("Successfully applied update on device")) + .softwareModuleId(3L) + .build()) + .headers(UpdateActionStatusOutboundMessage.UpdateActionStatusOutboundMessageHeaders.builder() + .tenant("tenant1") + .build()) + .build(); + + client.updateActionStatus(message); + + await().until(() -> mockServer.getMessages().size() == 1); + Message receivedMessage = mockServer.getMessages().get(0); + assertThat(receivedMessage).isNotNull(); + assertThat(receivedMessage.getProperties()).isEqualTo(new AMQP.BasicProperties.Builder() + .headers(Map.ofEntries( + Map.entry("type", "EVENT"), + Map.entry("topic", "UPDATE_ACTION_STATUS"), + Map.entry("tenant", "tenant1"))) + .contentType("application/json") + .build()); + JSONAssert.assertEquals("{\n" + + " \"actionId\": 7,\n" + + " \"softwareModuleId\": 3,\n" + + " \"actionStatus\": \"FINISHED\",\n" + + " \"message\": [\"Successfully applied update on device\"]\n" + + "}", receivedMessage.getBody(), JSONCompareMode.NON_EXTENSIBLE); + } + +} diff --git a/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/hawkbit/HawkbitFailureTest.java b/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/hawkbit/HawkbitFailureTest.java new file mode 100644 index 000000000..e1905a6fb --- /dev/null +++ b/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/hawkbit/HawkbitFailureTest.java @@ -0,0 +1,62 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.hawkbit; + +import static org.junit.Assert.assertThrows; + +import java.io.IOException; + +import com.github.fridujo.rabbitmq.mock.MockConnection; +import com.github.fridujo.rabbitmq.mock.MockConnectionFactory; + +import org.junit.jupiter.api.Test; + +import eu.arrowhead.core.confmgr.hawkbit.model.outbound.ThingCreatedOutboundMessage; +import eu.arrowhead.core.confmgr.hawkbit.util.HawkbitDmfMockServer; + + +public class HawkbitFailureTest { + + HawkbitDmfMockServer dmfMockServer; + + @Test + public void testMissingExchange() throws IOException { + MockConnection mockConnection = new MockConnectionFactory().newConnection(); + + HawkbitDmfOutboundClient hawkbitClient = new HawkbitDmfOutboundClient(mockConnection.createChannel()); + + dmfMockServer = new HawkbitDmfMockServer(mockConnection.createChannel()); + + ThingCreatedOutboundMessage message = ThingCreatedOutboundMessage.builder() + .body( + ThingCreatedOutboundMessage.Body.builder() + .name("some.device") + .build() + ) + .headers( + ThingCreatedOutboundMessage.Headers.builder() + .sender("sender") + .tenant("tenant") + .thingId("thingId") + .build() + ) + .build(); + + hawkbitClient.createThing(message); + + dmfMockServer.stageFailure(); + + assertThrows(IllegalArgumentException.class, () -> { + hawkbitClient.createThing(message); + }); + + } +} diff --git a/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/hawkbit/RabbitMQAPITest.java b/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/hawkbit/RabbitMQAPITest.java new file mode 100644 index 000000000..6242e6a30 --- /dev/null +++ b/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/hawkbit/RabbitMQAPITest.java @@ -0,0 +1,122 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.hawkbit; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; + +import java.io.IOException; +import java.util.Collections; +import java.util.Map; + +import com.github.fridujo.rabbitmq.mock.MockConnectionFactory; +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.Connection; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import eu.arrowhead.core.confmgr.hawkbit.model.inbound.DownloadRequestInboundMessage; +import eu.arrowhead.core.confmgr.hawkbit.util.HawkbitDmfMockServer; +import eu.arrowhead.core.confmgr.service.WebSocketService; +import eu.arrowhead.core.confmgr.websocket.DeviceNotConnectedException; + +public class RabbitMQAPITest { + HawkbitDmfConsumer consumer; + + WebSocketService mock_wsService; + + HawkbitDmfMockServer mockServer; + + Connection mockConnection; + + @BeforeEach + public void init() throws IOException { + mock_wsService = mock(WebSocketService.class); + + mockConnection = new MockConnectionFactory().newConnection(); + + mockServer = new HawkbitDmfMockServer(mockConnection.createChannel()); + consumer = new HawkbitDmfConsumer(mockConnection.createChannel(), mock_wsService); + } + + @Test + public void testAMQPInterface() throws IOException, DeviceNotConnectedException { + + consumer.subscribeToDownloadEvents(); + + AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder() + .contentType("application/json") + .headers(Map.ofEntries( + Map.entry("type", "EVENT"), + Map.entry("thingId", "device7"), + Map.entry("topic", "DOWNLOAD_AND_INSTALL"), + Map.entry("tenant", "tenant1") + )) + .build(); + String body = "{\n" + + " \"actionId\": 8,\n" + + " \"targetSecurityToken\": \"aslkhju4kjasdz9uas\",\n" + + " \"softwareModules\": [{\n" + + " \"moduleId\": 1,\n" + + " \"moduleType\": \"OS\",\n" + + " \"moduleVersion\": \"1.0\",\n" + + " \"artifacts\": [{\n" + + " \"filename\": \"os.zip\",\n" + + " \"urls\": {\n" + + " \"COAP\": \"coap://coap.local/os.zip\",\n" + + " \"HTTP\": \"http://localhost/os.zip\",\n" + + " \"HTTPS\": \"https://localhost/os.zip\"\n" + + " },\n" + + " \"hashes\": {\n" + + " \"md5\": \"8c9693121361daf0d992df4eeb714ebb\",\n" + + " \"sha1\": \"e5ee90a0f8d0570b8bbdcaa23aa9a6b737c0c826\"\n" + + " },\n" + + " \"size\": 539475\n" + + " }\n" + + " ],\n" + + " \"metadata\": [{\n" + + " \"key\": \"Description\",\n" + + " \"value\": \"This is the new operating system\"\n" + + " }\n" + + " ]\n" + + " }\n" + + " ]\n" + + "}"; + mockServer.publish(properties, body); + + DownloadRequestInboundMessage.Body assertBody = DownloadRequestInboundMessage.Body.builder().actionId(8L) + .targetSecurityToken("aslkhju4kjasdz9uas") + .softwareModules(Collections.singletonList(DownloadRequestInboundMessage.Body.SoftwareModule.builder() + .moduleId(1L).moduleType("OS").moduleVersion("1.0") + .artifacts(Collections.singletonList(DownloadRequestInboundMessage.Body.Artifact.builder() + .filename("os.zip") + .urls(DownloadRequestInboundMessage.Body.Urls.builder().COAP("coap://coap.local/os.zip") + .HTTP("http://localhost/os.zip").HTTPS("https://localhost/os.zip").build()) + .hashes(DownloadRequestInboundMessage.Body.Hashes.builder() + .md5("8c9693121361daf0d992df4eeb714ebb") + .sha1("e5ee90a0f8d0570b8bbdcaa23aa9a6b737c0c826") + .build()) + .size(539475L).build())) + .metadata(Collections.singletonList(DownloadRequestInboundMessage.Body.Metadata.builder() + .key("Description").value("This is the new operating system").build())) + .build())) + .build(); + + DownloadRequestInboundMessage.Headers assertHeaders = DownloadRequestInboundMessage.Headers.builder().type("EVENT") + .thingId("device7").topic("DOWNLOAD_AND_INSTALL").tenant("tenant1").build(); + + DownloadRequestInboundMessage assertMessage = DownloadRequestInboundMessage.builder().body(assertBody).deliveryTag(1l).headers(assertHeaders).build(); + + verify(mock_wsService, timeout(1000)).sendDownloadEventMessage(assertMessage); + } +} diff --git a/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/hawkbit/util/HawkbitDmfMockServer.java b/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/hawkbit/util/HawkbitDmfMockServer.java new file mode 100644 index 000000000..07b016fce --- /dev/null +++ b/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/hawkbit/util/HawkbitDmfMockServer.java @@ -0,0 +1,70 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.hawkbit.util; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.BuiltinExchangeType; +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.DefaultConsumer; +import com.rabbitmq.client.Envelope; + +import lombok.Getter; +import lombok.extern.log4j.Log4j2; + +@Log4j2 +public class HawkbitDmfMockServer { + + @Getter + private final Channel channel; + + @Getter + private final List messages; + + public HawkbitDmfMockServer(Channel channel) throws IOException { + this.channel = channel; + this.messages = new ArrayList<>(); + this.initializeMockServer(); + } + + public void clearMessages() { + messages.clear(); + } + + public void publish(AMQP.BasicProperties properties, String body) throws IOException { + byte[] byteBody = body.getBytes(StandardCharsets.UTF_8); + this.channel.basicPublish("configuration_system.direct.exchange", "", properties, byteBody); + } + + private void initializeMockServer() throws IOException { + String queueName = this.channel.queueDeclare().getQueue(); + this.channel.exchangeDeclare("dmf.exchange", BuiltinExchangeType.DIRECT); + this.channel.queueBind(queueName, "dmf.exchange", ""); + this.channel.basicConsume(queueName, new DefaultConsumer(this.channel) { + @Override + public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) { + String bodyString = new String(body, StandardCharsets.UTF_8); + log.debug("Received amqp message: consumerTag {}, envelope {}, properties {}, body {}", + consumerTag, envelope, properties, bodyString); + messages.add(new Message(envelope, properties, bodyString)); + } + }); + } + + public void stageFailure() throws IOException { + this.channel.exchangeDelete("dmf.exchange", false); + } + +} diff --git a/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/hawkbit/util/Message.java b/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/hawkbit/util/Message.java new file mode 100644 index 000000000..f5afde998 --- /dev/null +++ b/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/hawkbit/util/Message.java @@ -0,0 +1,31 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.hawkbit.util; + +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.Envelope; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +@Data +@Builder +public class Message { + + private Envelope envelope; + private AMQP.BasicProperties properties; + private String body; + +} diff --git a/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/service/HawbitServiceTest.java b/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/service/HawbitServiceTest.java new file mode 100644 index 000000000..feda5ea62 --- /dev/null +++ b/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/service/HawbitServiceTest.java @@ -0,0 +1,73 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.service; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import java.io.IOException; +import java.util.ArrayList; + +import javax.validation.ConstraintViolationException; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import eu.arrowhead.core.confmgr.hawkbit.HawkbitDmfOutboundClient; +import eu.arrowhead.core.confmgr.hawkbit.model.ActionStatus; +import eu.arrowhead.core.confmgr.hawkbit.model.outbound.UpdateActionStatusOutboundMessage; +import eu.arrowhead.core.confmgr.model.HawkbitActionUpdateStatus; + +public class HawbitServiceTest { + HawkbitService hawkbitService; + HawkbitDmfOutboundClient mock_hawkbitDmfClient; + + @BeforeEach + public void init() { + mock_hawkbitDmfClient = mock(HawkbitDmfOutboundClient.class); + + hawkbitService = new HawkbitService("testTenant", mock_hawkbitDmfClient); + } + + @Test + public void testUpdateActionStatus() throws ConstraintViolationException, IOException { + ArrayList messageList = new ArrayList(); + messageList.add("test"); + + HawkbitActionUpdateStatus hActionUpdateStatus = HawkbitActionUpdateStatus + .builder() + .actionId(123456789L) + .actionStatus(ActionStatus.CANCELED) + .softwareModuleId(987654321L) + .message(messageList) + .build(); + + UpdateActionStatusOutboundMessage message = UpdateActionStatusOutboundMessage.builder() + .body( + UpdateActionStatusOutboundMessage.UpdateActionStatusOutboundMessageBody.builder() + .actionId(123456789L) + .actionStatus(ActionStatus.CANCELED) + .softwareModuleId(987654321L) + .message(messageList).build() + ) + .headers( + UpdateActionStatusOutboundMessage.UpdateActionStatusOutboundMessageHeaders.builder() + .tenant("DEFAULT") + .build() + ) + .build(); + + + hawkbitService.updateActionStatus(hActionUpdateStatus); + + verify(mock_hawkbitDmfClient).updateActionStatus(message); + } +} diff --git a/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/websocket/WebSocketControllerIT.java b/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/websocket/WebSocketControllerIT.java new file mode 100644 index 000000000..7e124286a --- /dev/null +++ b/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/websocket/WebSocketControllerIT.java @@ -0,0 +1,175 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.websocket; + +import static org.junit.Assert.assertThrows; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.URI; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.time.Duration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.websocket.DeploymentException; + +import com.google.common.base.Throwables; +import com.rabbitmq.client.Connection; + +import org.apache.http.ssl.SSLContexts; +import org.apache.http.ssl.TrustStrategy; +import org.apache.tomcat.websocket.Constants; +import org.awaitility.Awaitility; +import org.json.JSONException; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketHttpHeaders; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.client.standard.StandardWebSocketClient; +import org.springframework.web.socket.handler.TextWebSocketHandler; + +import eu.arrowhead.core.confmgr.config.InitArrowheadMockServers; +import eu.arrowhead.core.confmgr.hawkbit.util.HawkbitDmfMockServer; +import eu.arrowhead.core.confmgr.hawkbit.util.Message; +import lombok.extern.log4j.Log4j2; + +@Log4j2 +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@ActiveProfiles("test") +public class WebSocketControllerIT { + @Value("${testParameters.token}") + String testToken; + + @Value("${testParameters.incorrectToken}") + String incorrectTestToken; + + @Autowired + Connection mockConnection; + + @Autowired + TrustManager[] tm; + + @LocalServerPort + private Integer port; + + HawkbitDmfMockServer hawkbitMockServer; + + StandardWebSocketClient wsClient; + + private static InitArrowheadMockServers initMockServer; + + @BeforeAll + public static void beforeAll() { + WebSocketControllerIT.initMockServer = new InitArrowheadMockServers(); + WebSocketControllerIT.initMockServer.setUp(); + } + + @BeforeEach + public void init() throws NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException, + KeyStoreException, UnrecoverableKeyException, KeyManagementException { + wsClient = new StandardWebSocketClient(); + Map userProperties = new HashMap<>(); + SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(new TrustStrategy() { + @Override + public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException { + log.error(chain); + return true; + } + }).build(); + + + sslContext.init(null, tm, new java.security.SecureRandom()); + userProperties.put(Constants.SSL_CONTEXT_PROPERTY, sslContext); + + wsClient.setUserProperties(userProperties); + + // Mock the HawkBit DMF API (RabbitMQ) + hawkbitMockServer = new HawkbitDmfMockServer(mockConnection.createChannel()); + } + + @Test + public void testWebsocketWithCorrectTokenAndCorrectPayload() + throws InterruptedException, ExecutionException, IOException, JSONException { + WebSocketHttpHeaders headers = new WebSocketHttpHeaders(); + headers.setBearerAuth(testToken); + + WebSocketSession session = wsClient.doHandshake(new TextWebSocketHandler() { + + }, headers, URI.create("wss://localhost:" + port)).get(); + + String message = "{" + + "\"actionId\": 11," + + "\"softwareModuleId\": 12," + + "\"actionStatus\": \"DOWNLOAD\"," + + "\"message\": [" + + "\"value\"" + + "]" + + "}"; + + session.sendMessage(new TextMessage(message)); + + // Mock server should contain two messages up to this point: + // 1. the device creation message resulting from the call of doHandshake() + // 2. the action update status message + + Awaitility.await().atMost(Duration.ofMillis(500)).until(() -> { + if (hawkbitMockServer.getMessages().size() == 2) { + return true; + } else { + return false; + } + }); + + List receivedMessages = hawkbitMockServer.getMessages(); + Message receivedUpdateActionStatus = receivedMessages.get(1); + String receivedUpdateActionStatusBody = receivedUpdateActionStatus.getBody(); + + JSONAssert.assertEquals(message, receivedUpdateActionStatusBody, true); + } + + @Test + public void testWebsocketWithIncorrectToken() throws IOException, InterruptedException, ExecutionException { + WebSocketHttpHeaders headers = new WebSocketHttpHeaders(); + headers.setBearerAuth(incorrectTestToken); + + + Exception e = assertThrows(ExecutionException.class, () -> { + wsClient.doHandshake(new TextWebSocketHandler(), headers, URI.create("wss://localhost:" + port)).get(); + }); + + assertEquals(DeploymentException.class, Throwables.getRootCause(e).getClass()); + } + + @AfterAll + public static void afterAll() { + WebSocketControllerIT.initMockServer.shutDown(); + } +} \ No newline at end of file diff --git a/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/websocket/WebSocketSenderTest.java b/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/websocket/WebSocketSenderTest.java new file mode 100644 index 000000000..7adf7695b --- /dev/null +++ b/configuration-manager/src/test/java/eu/arrowhead/core/confmgr/websocket/WebSocketSenderTest.java @@ -0,0 +1,95 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.confmgr.websocket; + +import static org.junit.Assert.assertThrows; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import java.io.IOException; +import java.util.concurrent.ConcurrentHashMap; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; + +import eu.arrowhead.core.confmgr.hawkbit.model.inbound.MessageTypeInbound; +import eu.arrowhead.core.confmgr.hawkbit.model.inbound.ThingDeletedInboundMessage; +import eu.arrowhead.core.confmgr.websocket.model.DeviceMessage; + +public class WebSocketSenderTest { + WebsocketSender wsSender; + + WebSocketSession wsSession; + + @BeforeEach + public void init() { + ConcurrentHashMap webSocketSessionMap = new ConcurrentHashMap<>(); + + wsSession = mock(WebSocketSession.class); + webSocketSessionMap.put("testIdX", wsSession); + + wsSender = new WebsocketSender(webSocketSessionMap); + } + + @Test + public void testSendMessageWithMissingSessionWebSocketSessionMap() throws IOException { + DeviceMessage deviceMessage = DeviceMessage.builder() + .type(MessageTypeInbound.EVENT.toString()) + .message( + ThingDeletedInboundMessage.builder() + .headers( + ThingDeletedInboundMessage.Headers.builder() + .thingId("testIdY") + .build() + ) + .build() + ) + .build(); + + assertThrows(DeviceNotConnectedException.class, () -> { + wsSender.sendMessage("testIdY", deviceMessage); + }); + } + + @Test + public void testSendMessageWithExistingSession() throws IOException, + DeviceNotConnectedException { + DeviceMessage deviceMessage = DeviceMessage.builder() + .type(MessageTypeInbound.EVENT.toString()) + .message( + ThingDeletedInboundMessage.builder() + .headers( + ThingDeletedInboundMessage.Headers.builder() + .thingId("testIdX") + .build() + ) + .build() + ) + .build(); + + String expectedBody = "{" + + "\"type\":\"EVENT\"," + + "\"message\":{" + + "\"headers\":{" + + "\"thingId\":\"testIdX\"" + + "}" + + "}" + + "}"; + + TextMessage expectedWSMessage = new TextMessage(expectedBody); + + wsSender.sendMessage("testIdX", deviceMessage); + + verify(wsSession).sendMessage(expectedWSMessage); + } +} diff --git a/configuration-manager/src/test/resources/application-test.yml b/configuration-manager/src/test/resources/application-test.yml new file mode 100644 index 000000000..b259b0c55 --- /dev/null +++ b/configuration-manager/src/test/resources/application-test.yml @@ -0,0 +1,80 @@ +--- +# Configuration of the WebSocket endpoint +# Listens on port 8443 and loads its certificate from the truststore +server: + port: 8443 + ssl: + key-store: ./src/test/resources/test-certificates/test-conf-system.p12 + key-store-password: confsys-test-pw + keyStoreType: PKCS12 +# These are the properties of the configuration system itself +# The properties are loaded into the class eu.arrowhead.core.confsys.properties.SystemProperties +system: + # This is the address of the configuration system itself, e.g. "https://192.168.1.1" or "https://subdomain.domain.tld" + address: 127.0.0.1 + # This is the port of the configuration system itself, e.g. 8445 + port: 9090 + # This is the name of the configuration system itself, e.g. "conf-system". In the context of Arrowhead the system + # (provider) name and certificate common name must match + name: conf-system + # This is the protocol of the provided service from the configuration system itself, e.g. "HTTPS-SECURE-JSON" + providedServiceInterface: HTTPS-SECURE-JSON + # This is the definition of the provided service from the configuration system itself, e.g. "definition5" + providedServiceDefinition: definition1 + # This is the uri of the provided service from the configuration system itself, e.g "/" + providedServiceUri: / + # This is the version of the provided service from the configuration system itself, e.g. 2 + providedServiceVersion: 2 + +# This is the truststore the configuration system uses for verifying the identity of clients in the Arrowhead system +trustStore: + path: ./src/test/resources/test-certificates/test-cloud.truststore.p12 + password: test-cloud-trust-store-pw + +# This is the keystore the configuration system uses for mutual authentication against the Arrowhead system +keyStore: + path: ./src/test/resources/test-certificates/test-conf-system.p12 + password: confsys-test-pw + # As set in the generate-certificates.sh, equals the CN of the certificate + alias: confsys.example.corp.arrowhead.eu + +# These are the connection parameters for the Arrowhead authorization system +arrowheadAuthorizationSystem: + protocol: https:// + address: 127.0.0.1 + port: 8445 + +# These are the connection parameters for the Arrowhead service registry +arrowheadServiceRegistry: + protocol: https:// + address: 127.0.0.1 + port: 8443 + +# These are the connection parameters for the hawkBit DMF API +hawkbit: + host: 127.0.0.1 + port: 5672 + username: hawkbit + password: hawkbit + # This is the tenant in hawkBit itself + tenant: DEFAULT + +testParameters: + authorization: + cert: + path: ./src/test/resources/test-certificates/test-auth-system.cert.pem + key: + path: ./src/test/resources/test-certificates/test-auth-system.key.pem + pubKey: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvmaFymz4cSB+LP3pixhhgrczq8G2rWTMyyajMQNSXOuvGDBhB7S32rl/+B8VUvTrYBx7ab8AM/TxrDwWAzsQ3p3oo0+5NNibHpZ8QucWIXqbpQqFobiNWx+ogoBLV4UfJekxXYH54QrTdwkvlOFXxjkL11uwBKEsZ0TzKgGaRs+iR80sjDeicnpPlMm0RbpXjx10OzmjqfC1Wrusyl2gjsW9ySfwJ1461n6cPndn8csvC37IHdHpkKTRlcDDPG6/GN3+vfUeznA7zGFWv2IOTzsuruehXaOuDNgekvREK3Xh4BpvGTZ6oEFVmr5853U3PS6ExKlvwR8NwFiPXI9LAQIDAQAB + serviceRegistry: + cert: + path: ./src/test/resources/test-certificates/test-sreg.cert.pem + key: + path: ./src/test/resources/test-certificates/test-sreg.key.pem + cloud: + cert: + path: ./src/test/resources/test-certificates/test-cloud.cert.pem + key: + path: ./src/test/resources/test-certificates/test-cloud.key.pem + token: eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIiwiY3R5IjoiSldUIn0.LgLfAMnAvIKBhSThP6hhY9q5sVH9krMhXlb6UvVC5Bmvm-ph_ThOKlqd4iIoXws6kDaKJx--sWKKZhjbnRabLAZUHMwkmQHTbHl9FfUinCXBf95rwVxlL6hUS7tPvzp4awh5QqVMi_Focb6FboXx0ScXNUlaqsP3AreSFPiS_w2wPyBvsfKHF2xFkCmsSRb2Cxoh2P7_MFxtXK-eY4IiDlCOaBv9VijeDVV2hJ251R10k0w2yjG-b8--cdgDLsjQ3vjpDoIDqRHR8_7VzfnQZKVsU_4YNsAJMPx0OdwGBXKsUhx5yEqHtaglq3ym4P7qm3zXcYWgG9u8wD3KHvnPiw.4d8ObjIomnHzKdilPBGOQg.yhdv2DSpw4N_sQtalwVBnHhIXPRkm8G82KF11tHAJZ-iw3NKGjMI57_xOGPkL04yF3-HPsr52lxNLn883nynIJRcj2g8rri1D08mQ6hil4B2HpL3O4OvyM-CLgLNQ8ZODFMbTQ8rZgw4B1-gT61H0RYq4XMKyubf3_QrLKDkz4CLzuZR_F4EsXYZZ682zX1_Ym3sM6rDA2YI7wVZRSIAf0FOnV2IOu5WfABNcRYJtuSE60Tx20rz86nLdvC7LVo6xzFRAzsW3YQLSCQrZeanxzVU0lSN3p6AxifuD38CZzNMDqFIEuNJA3PpEU6-hwrKVYksrngbUSdJ1cjaSrGuXR5zI4LwsJOXMoCUDvGhO1Wz4qzWAdLJNZ2qOz_FC_6rN2hrU7YgWJ4-vDNLTJVyqCYURUiv1b3UDjEBUgSBPBp0gpX7-xBwv3EW0DB8c-r5_93lS8c0DIJ_bwr5p2q9o2nFSVbjWOPBzz0i8heGq_ZgkzE-x2672NhxoCQRGEfC9EhZH5pUtjhqIKedZn90XtbeXgytDtpRJ8sgCdMejjnzZpFIdyQru549e14t9bjoyE7mNs4INd6gNB8qQyFaTV07pLDsTdWrfvVRcp6BZx_0wWkXQOpUF5jmrSIGdpHOhFmNlrKJCapivyTwVs_IPmqzFrIdexTKA3Gx7w3fT1-4ckrTtsVR115xPbh_b0ZPPRH9mI9OLlf_TR1r9ZAI3KOAA4yCwDYZWVi0x87BPmnt0O1RWtQRfmDrXapLCHxdEz1KuCv8_KPWjKSm1lnyzg._lX83lJYfL2lf8Mq5t8N3r9-Qa9045Odx3bg9yL6deY + incorrectToken: eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIiwiY3R5IjoiSldUIn0.U5PblBD4SRwVIYjZbG-OwA83pZeU63mj9VlOWxSgcRdskU24KDRA1utE3Ubl6zLtSgUuTtVtGgWNJWi-AHHQBRY1XLSfr4f7-5fUp3bUes1QtF3Sj8cUknA04_H4nyIzoHwxb_b80upS5wpps6gcASmBuWtVlwyCxiv-mR5mNvHro3ueZo1Lrxk0y4MSIXQA6FQlnTkamSdGGA9WDI1Q6pm4YVkPF4Z7OOobvwhqQJR3r4HUQyglf8jhdIBfxxzD6RRGqXTk7PVQgq2D018UAAgJfNmpD37_A9EPFsWDFNMj_7Prf_to48QnwVX5qHrVcHTclHPjgZb4TD8sEzKjTg.qxBxIfX41UfQbSOhLhRWoQ.bFqcOHyl8A6ujQR2oXToQIfbxvpKLchZwuz7KJ-y5jvOqEaOo0hzLX0G-ueeLZVbQFsuLFJSaJKPmEe1odPaRCjH4H1Seo2eUWMDIMGj7ub6v5dvn2Y7p5jpYKG2mHsFC0bA7nvMx6RyT4wMwPsX8GTDHDPwFFMRVoWzT0ENRuV358IjNp5wEWuKEHTB2_VKuUYKFGmP7rSsXlgSWYz6anJC41bVkzfv4OnJyYTV7DJ5yXtESkE9b1UZNDXpBezbeCAshpFgjKrACi_m46zRJR5kJP9Fkn_lJPPNvKd11Q3ij6OLPjLnoHm1vENnsP7BhWvLJhoTiGjMROe5hLL1AzajNYegSYHwKONPk2w_Zf9UZP8A2hq_s5olkDmopKPEiWJ_Ml8PN61Q9dfMtio7UvLIu9MP-12pU0hvCK1BMs_F8jqqm-SpxqSDa0sM-DJaeYjbxrCVzCiiJg04bq1y_2BrGP1sGi3pl6W-ezpmksyn0m_UjwCFl9nYi8caYHr4fNvBt4mRRY1ZoH-ihGWM0befFPyG-QjHqMTfdcwU17NyBHEqoq5Yxq4maOeULg7MM0MPP0I5g_s9P8ByrwJ0arTkmZIh4crjhhjXycsOMFNKJRd8CogmHHQPJ3VrT78bmLjVQ-2EyK7X3R-qs-JiUwY6HnQR9EpNcw_LeFRSPuqCWZBeyEDIS8T_Fc8fcHUj4nz0pdSN1GS5uSbdy8OwO87zF8y6g1pEo__yL_D1GwrLr1r6tfJbAAhIJC6vu4MwoJ1lhdGN3HODA2Kt87-1ww.onky0el9l6HXWQOIs2ZrWDHNl1jI06dxmQeJUJ_UwcA \ No newline at end of file diff --git a/configuration-manager/src/test/resources/log4j2.xml b/configuration-manager/src/test/resources/log4j2.xml new file mode 100644 index 000000000..c521b8136 --- /dev/null +++ b/configuration-manager/src/test/resources/log4j2.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/configuration-manager/src/test/resources/mockserver.properties b/configuration-manager/src/test/resources/mockserver.properties new file mode 100644 index 000000000..2249ee4e4 --- /dev/null +++ b/configuration-manager/src/test/resources/mockserver.properties @@ -0,0 +1,7 @@ +mockserver.certificateAuthorityPrivateKey=./src/test/resources/test-certificates/test-cloud.key.pem +mockserver.certificateAuthorityCertificate=./src/test/resources/test-certificates/test-cloud.cert.pem +mockserver.sreg.privateKeyPath=./src/test/resources/test-certificates/test-sreg.key.pem +mockserver.sreg.x509CertificatePath=./src/test/resources/test-certificates/test-sreg.cert.pem +mockserver.auth.privateKeyPath=./src/test/resources/test-certificates/test-auth-system.key.pem +mockserver.auth.x509CertificatePath=./src/test/resources/test-certificates/test-auth-system.cert.pem +mockserver.auth.pubKeyResponse=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqIZpQa/yCE8JBYtLz+XkoXpHYUsTFHuntQIPV6pPgcX07OagpFWW+LIRWD4jXPc+ndXOVD7XmimnkAOXGYD+1GF+glBSCKVcjOM55S5LLoJbV9J77H1B/NhKQX6kZDxP94jRPql7YmMlo8ge3NHjc/isNh2mX8yS4LnjjXXdhv2Ggn0fcItgAAYd2CLpds/obrsue6hWD4G1T/TUTjgoYR3JiPh+XTOOQJXWTDMA5ay81/TGTLJ+PAc8Fp2GP+AED2QVmPO+PPuk4RjTCxS7L63aH0HW2o1ibf4kndKbsg5XwOEP56+IwEutGqctzQw4y3luLHXOBSxsfWcnyoMeDwIDAQAB \ No newline at end of file diff --git a/configuration-manager/src/test/resources/test-certificates/master.p12 b/configuration-manager/src/test/resources/test-certificates/master.p12 new file mode 100755 index 000000000..c6bc2c6ea Binary files /dev/null and b/configuration-manager/src/test/resources/test-certificates/master.p12 differ diff --git a/configuration-manager/src/test/resources/test-certificates/test-auth-system.cert.pem b/configuration-manager/src/test/resources/test-certificates/test-auth-system.cert.pem new file mode 100644 index 000000000..446256a20 --- /dev/null +++ b/configuration-manager/src/test/resources/test-certificates/test-auth-system.cert.pem @@ -0,0 +1,80 @@ +Bag Attributes + friendlyName: authorization.example.corp.arrowhead.eu + localKeyID: 54 69 6D 65 20 31 36 30 34 36 36 32 30 32 37 30 39 34 +subject=CN = authorization.example.corp.arrowhead.eu + +issuer=C = DE, ST = Berlin, L = Berlin, O = AHT, OU = arrowhead.eu, CN = example.corp.arrowhead.eu + +-----BEGIN CERTIFICATE----- +MIIDqjCCApKgAwIBAgIEX6UyfTANBgkqhkiG9w0BAQsFADB4MQswCQYDVQQGEwJE +RTEPMA0GA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4xDDAKBgNVBAoMA0FI +VDEVMBMGA1UECwwMYXJyb3doZWFkLmV1MSIwIAYDVQQDDBlleGFtcGxlLmNvcnAu +YXJyb3doZWFkLmV1MB4XDTIwMTEwNjExMjQ0NVoXDTMwMTEwNjExMjQ0NVowMjEw +MC4GA1UEAwwnYXV0aG9yaXphdGlvbi5leGFtcGxlLmNvcnAuYXJyb3doZWFkLmV1 +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqIZpQa/yCE8JBYtLz+Xk +oXpHYUsTFHuntQIPV6pPgcX07OagpFWW+LIRWD4jXPc+ndXOVD7XmimnkAOXGYD+ +1GF+glBSCKVcjOM55S5LLoJbV9J77H1B/NhKQX6kZDxP94jRPql7YmMlo8ge3NHj +c/isNh2mX8yS4LnjjXXdhv2Ggn0fcItgAAYd2CLpds/obrsue6hWD4G1T/TUTjgo +YR3JiPh+XTOOQJXWTDMA5ay81/TGTLJ+PAc8Fp2GP+AED2QVmPO+PPuk4RjTCxS7 +L63aH0HW2o1ibf4kndKbsg5XwOEP56+IwEutGqctzQw4y3luLHXOBSxsfWcnyoMe +DwIDAQABo4GBMH8wGgYDVR0RBBMwEYIJbG9jYWxob3N0hwR/AAABMEIGA1UdIwQ7 +MDmAFBOMBYikgCvo8FXUYauQwlW8VZvqoRukGTAXMRUwEwYDVQQDDAxhcnJvd2hl +YWQuZXWCBF+kKMowHQYDVR0OBBYEFKAIzWSlATK8//WtDrPAzVbefzZUMA0GCSqG +SIb3DQEBCwUAA4IBAQCKlv6loenAlJPgAzNkm9HVKU72Qk+Atn7J/lM3ub8SxTO2 +0tD8B9Jm+0kpn5Tl14KOPtCnjSu2hFRzp+q6x4N2P63SXezaBCuxIOfS3CSdEWPA +sF9oTbhBaaz528lx2xjkI4yagDRdmNcnSvmoOJd4AfJpD8C1aWOm2EgDsC/MgB3w +rMEBs67hFpzS0rxxNM1J6myc2Bhq2lzgwKE5tQxquSUBSjJtRyh0p3lZWLWp72jN +YfwHg8T5l7Xvf/4l2kDiAOLZRRo/88SQpxxgIb3v54JfvRJmazapikgISMyv1U9s +ukfQtyNSPeCX4+//LYda1D7EvuRIq5ljK0UdzEUn +-----END CERTIFICATE----- +Bag Attributes + friendlyName: CN=example.corp.arrowhead.eu,OU=arrowhead.eu,O=AHT,L=Berlin,ST=Berlin,C=DE +subject=C = DE, ST = Berlin, L = Berlin, O = AHT, OU = arrowhead.eu, CN = example.corp.arrowhead.eu + +issuer=CN = arrowhead.eu + +-----BEGIN CERTIFICATE----- +MIIDgzCCAmugAwIBAgIEX6QoyjANBgkqhkiG9w0BAQsFADAXMRUwEwYDVQQDDAxh +cnJvd2hlYWQuZXUwHhcNMjAxMTA1MTYzMTA2WhcNMzAxMTA1MTYzMTA2WjB4MQsw +CQYDVQQGEwJERTEPMA0GA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4xDDAK +BgNVBAoMA0FIVDEVMBMGA1UECwwMYXJyb3doZWFkLmV1MSIwIAYDVQQDDBlleGFt +cGxlLmNvcnAuYXJyb3doZWFkLmV1MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAmrcvpwqTKaGgpQLuqXMi6TrH5KxkpFH8lH5vq2aozu7QnZexM0Qyf1TY +kvpiy3sDTu6mzGysT96Qv8K8CI8g24oIVRKnJUEs23NgS+KUK6Nz2eIjRaaMVRh4 +cFBcRkr3oFbk+ujGySn+BR8v4eRjq5+qzFX8fswcoeBmlkU+M92kg/Bcy65Pg2v5 +1ORuGhbnmqsEuoyOsSjucL43zTOQtwAva5rvdERVPh7978I+UGFUXaSQnYpP8oyw +CRRIYrPTBHHBA0Q/7sDb2lCA1yLPSwD/H5QvFOxmN3cYAnzvgQZO8+spCiy1T7Kx +FBAnutVn8Sj6BeFNGZvszmlDCs54AwIDAQABo3YwdDAPBgNVHRMECDAGAQH/AgEC +MEIGA1UdIwQ7MDmAFJqKmR4xTB6y5i22mu3HHjHK3Tv+oRukGTAXMRUwEwYDVQQD +DAxhcnJvd2hlYWQuZXWCBFzVPq0wHQYDVR0OBBYEFBOMBYikgCvo8FXUYauQwlW8 +VZvqMA0GCSqGSIb3DQEBCwUAA4IBAQAEhHykqvmRnl3S0aVv4tSoA+tGxwBMaRoY +OvxEDC321CFY/QkWaQfUpVg/eGgsLLFweUp9OFXxkgK4H/rUvQ/twt7oUIDUdJd+ +sGn04mIaym3M9IWhCTxqYf+X3/SHWmI5GnT3dQu9ayJKitD28/6FMLMqEdPiON/9 +VMzkoD2ndN6VSJWOWSg3xmOJ93u+xGbTkweIFildnhf79QwgColJGBytIWBhggvV +DVccNfLqmq9NNaxvPx/y2Oe1ngNhLcqqTUSiwKH42pdupKWZD/EcNlMXttRrkiOt +S3sXv18pDV/094abPB38ulgeeY7Kt2lz12qK7irxjZsVPahyW1Z0 +-----END CERTIFICATE----- +Bag Attributes + friendlyName: CN=arrowhead.eu +subject=CN = arrowhead.eu + +issuer=CN = arrowhead.eu + +-----BEGIN CERTIFICATE----- +MIIC3jCCAcagAwIBAgIEXNU+rTANBgkqhkiG9w0BAQsFADAXMRUwEwYDVQQDDAxh +cnJvd2hlYWQuZXUwHhcNMTkwNTEwMDkwNDQ1WhcNMjkwNTEwMDkwNDQ1WjAXMRUw +EwYDVQQDDAxhcnJvd2hlYWQuZXUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQCuB4z+wchXDKdfy9YFZha2U0khBAWuHYerBlBLM0Oqvr4c/YYcZpNwTWY2 +tk6UXPUTQ8gI9V6Ob7DRXoAfLDhCRGKySN0BlGnjUJkItP25Sj0RfiTL3b8fFEIT +Z8pg+6pAfeFQgV0yz+ziyL+0uu69VZPv+RAEf1GKgshGGLJw3sOlcIdKuZaEAaA2 +b0nDUn229VpKXb9cg47Ae1Yb0sJcTkyIBuhkQKln3uhG2xct9nDfVal05+229AJQ +Ly1f0UfEofvD/OLjFG3umF857T1Vr5azj8zFOvNi503gV458lH3wKC+9UHUf46sg +Hc8Tyrz1q8VAIamJe7BLUeRFwGvBAgMBAAGjMjAwMA8GA1UdEwQIMAYBAf8CAQMw +HQYDVR0OBBYEFJqKmR4xTB6y5i22mu3HHjHK3Tv+MA0GCSqGSIb3DQEBCwUAA4IB +AQCkFsqyeAjztDBkTQrPxAB0Vvx6KPINApHGIHkJj/9crKXZEQcNJcJr35hfLcgv +hSsmLMdeRFCeaG5QLmUKI6GFYIbX+6nawMLGzIPUTOGetNeuMauDXkq09Hu/UmjN +AOgoD5vWdtyTbItv21enJnUelClAJ7VXti2QpyRM2puPHpZMNi4FWgLGPo6hq5ka +d7KomzW8JLh2Vd67v/6mXGpST4EzyRe+Yb2FJZUmhxVWt68/MFaflPQ2toPIsIpW +5m4OS+rT7t+uxPKWU/ogCK7BOUfE/qf3Al/osWkNKnFQDtO/7x7InEmRoP9EUv07 +JY4vK/+k5fJUZHpJzpuKxbBo +-----END CERTIFICATE----- diff --git a/configuration-manager/src/test/resources/test-certificates/test-auth-system.key.pem b/configuration-manager/src/test/resources/test-certificates/test-auth-system.key.pem new file mode 100644 index 000000000..cde3c39ae --- /dev/null +++ b/configuration-manager/src/test/resources/test-certificates/test-auth-system.key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCohmlBr/IITwkF +i0vP5eShekdhSxMUe6e1Ag9Xqk+BxfTs5qCkVZb4shFYPiNc9z6d1c5UPteaKaeQ +A5cZgP7UYX6CUFIIpVyM4znlLksugltX0nvsfUH82EpBfqRkPE/3iNE+qXtiYyWj +yB7c0eNz+Kw2HaZfzJLgueONdd2G/YaCfR9wi2AABh3YIul2z+huuy57qFYPgbVP +9NROOChhHcmI+H5dM45AldZMMwDlrLzX9MZMsn48BzwWnYY/4AQPZBWY8748+6Th +GNMLFLsvrdofQdbajWJt/iSd0puyDlfA4Q/nr4jAS60apy3NDDjLeW4sdc4FLGx9 +ZyfKgx4PAgMBAAECggEAB5o8sFpsHETYftgIXQIdfM1j3/c/jpp/d9J2d7l2qb+Q +64kB8TftPh3mW2k+NijvObaV/sXw+wLrDLukuuFPXaxiRSWzdbFkCfUMPRMIEDqQ +PVeBAKur6Oim/OpmJyFzaBLTgbSqnMclQQy7m7GaRbaPqkdPJJ45eArawZxyGG9P +nB1Iwnv1J7KilklJuWkzFSrlk2VE+UUzAiUiRN2TPtGQ0HOnTz+piuovSEIDco3v +EUCpr6sppUTHKRCyN6h6UZlMeFAfyjiTDWXhx8N5pN00m8QXdL7g2EILBnB8/m1r +sUUiVlAznSQ+C5oVEuc17NpuABA94yfCSRA8XUvVXQKBgQDqVjjfZnXMJsi7N4gO +DTuuWrpPBV03RVOBHtcrW0zGcyxGkz4B/E9pm0A7q2PSfVR0YaunetLFjMpm/GOC +AD7Bys19NtejKnrgOFwcDxYnj5spvu5huMpkIdmr0sSMaQ8CoaCm3R16GOUEUax+ +6s7HfBoJedWSmrOT/V9n+DyfjQKBgQC4GrK/mteRXgq/2NrukxrfhIcFcwQ3Hige +V3ZgsMdwROUeKz5AYX/vQdL3t1cQkmTfBiIS+MPxRrtCIaR0AQ10IvA1ch7eIIrJ +8YVTZQEurTRJrzDuwpJSlHQyGEqBKCTx6wKesAnb+D6CWmTpl5569Yya/VTGLvkL +sISo9YsPCwKBgQCUaRsH0XpVW1LRzy+pxpO/rtJD7Z/Fu3BRXFjlqqMLJDIsWGBz +HXql5Z9eqwNDcgCk3J68KTuHxc0CQGI9GS6lyW0vGiajvemoS8l6M8vSLk8Ut0l+ +Nmxn0lBrU8cLLhZFkluOkodypoWUoK1gjBF9oYlT6wdqG/QTi+p6jGrImQKBgQCq +7HaZtzt+Bc57EO0p5D9l11e38nGubnwWt2L7IAT5saz6FnHEOgEZ84XXYEsf4ppu +CPanoHfiUKXYHv3ciYGhGjLjm+EWnyXbmTUMGNFBMaNC6bjHYrOQoa1DORQq10d9 +po+XHp74r3/xa9UWniKPy9tPpfUHzmJ/vNFdQCJrwwKBgAyVj1mgcmvT4ISfgwtm +nBLJ7a0fDvRRphCC6LUyYMntAHRzAJspYMmgdBb32PClF4WmJ01APtToYFWroUYL +JG6lQAZE92qyhxaR/Juth30zsTVVczsL0zQ16cAEMneNf9LCjZG2f78driNMsX6h +ystjmZOkSuYTKgNc7nfDnNZE +-----END PRIVATE KEY----- diff --git a/configuration-manager/src/test/resources/test-certificates/test-auth-system.p12 b/configuration-manager/src/test/resources/test-certificates/test-auth-system.p12 new file mode 100644 index 000000000..5f54d7f3e Binary files /dev/null and b/configuration-manager/src/test/resources/test-certificates/test-auth-system.p12 differ diff --git a/configuration-manager/src/test/resources/test-certificates/test-cloud.cert.pem b/configuration-manager/src/test/resources/test-certificates/test-cloud.cert.pem new file mode 100644 index 000000000..128567567 --- /dev/null +++ b/configuration-manager/src/test/resources/test-certificates/test-cloud.cert.pem @@ -0,0 +1,52 @@ +Bag Attributes + friendlyName: example.corp.arrowhead.eu + localKeyID: 54 69 6D 65 20 31 36 30 34 35 39 34 31 32 36 38 31 36 +subject=C = DE, ST = Berlin, L = Berlin, O = AHT, OU = arrowhead.eu, CN = example.corp.arrowhead.eu + +issuer=CN = arrowhead.eu + +-----BEGIN CERTIFICATE----- +MIIDgzCCAmugAwIBAgIEX6QoyjANBgkqhkiG9w0BAQsFADAXMRUwEwYDVQQDDAxh +cnJvd2hlYWQuZXUwHhcNMjAxMTA1MTYzMTA2WhcNMzAxMTA1MTYzMTA2WjB4MQsw +CQYDVQQGEwJERTEPMA0GA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4xDDAK +BgNVBAoMA0FIVDEVMBMGA1UECwwMYXJyb3doZWFkLmV1MSIwIAYDVQQDDBlleGFt +cGxlLmNvcnAuYXJyb3doZWFkLmV1MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAmrcvpwqTKaGgpQLuqXMi6TrH5KxkpFH8lH5vq2aozu7QnZexM0Qyf1TY +kvpiy3sDTu6mzGysT96Qv8K8CI8g24oIVRKnJUEs23NgS+KUK6Nz2eIjRaaMVRh4 +cFBcRkr3oFbk+ujGySn+BR8v4eRjq5+qzFX8fswcoeBmlkU+M92kg/Bcy65Pg2v5 +1ORuGhbnmqsEuoyOsSjucL43zTOQtwAva5rvdERVPh7978I+UGFUXaSQnYpP8oyw +CRRIYrPTBHHBA0Q/7sDb2lCA1yLPSwD/H5QvFOxmN3cYAnzvgQZO8+spCiy1T7Kx +FBAnutVn8Sj6BeFNGZvszmlDCs54AwIDAQABo3YwdDAPBgNVHRMECDAGAQH/AgEC +MEIGA1UdIwQ7MDmAFJqKmR4xTB6y5i22mu3HHjHK3Tv+oRukGTAXMRUwEwYDVQQD +DAxhcnJvd2hlYWQuZXWCBFzVPq0wHQYDVR0OBBYEFBOMBYikgCvo8FXUYauQwlW8 +VZvqMA0GCSqGSIb3DQEBCwUAA4IBAQAEhHykqvmRnl3S0aVv4tSoA+tGxwBMaRoY +OvxEDC321CFY/QkWaQfUpVg/eGgsLLFweUp9OFXxkgK4H/rUvQ/twt7oUIDUdJd+ +sGn04mIaym3M9IWhCTxqYf+X3/SHWmI5GnT3dQu9ayJKitD28/6FMLMqEdPiON/9 +VMzkoD2ndN6VSJWOWSg3xmOJ93u+xGbTkweIFildnhf79QwgColJGBytIWBhggvV +DVccNfLqmq9NNaxvPx/y2Oe1ngNhLcqqTUSiwKH42pdupKWZD/EcNlMXttRrkiOt +S3sXv18pDV/094abPB38ulgeeY7Kt2lz12qK7irxjZsVPahyW1Z0 +-----END CERTIFICATE----- +Bag Attributes + friendlyName: CN=arrowhead.eu +subject=CN = arrowhead.eu + +issuer=CN = arrowhead.eu + +-----BEGIN CERTIFICATE----- +MIIC3jCCAcagAwIBAgIEXNU+rTANBgkqhkiG9w0BAQsFADAXMRUwEwYDVQQDDAxh +cnJvd2hlYWQuZXUwHhcNMTkwNTEwMDkwNDQ1WhcNMjkwNTEwMDkwNDQ1WjAXMRUw +EwYDVQQDDAxhcnJvd2hlYWQuZXUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQCuB4z+wchXDKdfy9YFZha2U0khBAWuHYerBlBLM0Oqvr4c/YYcZpNwTWY2 +tk6UXPUTQ8gI9V6Ob7DRXoAfLDhCRGKySN0BlGnjUJkItP25Sj0RfiTL3b8fFEIT +Z8pg+6pAfeFQgV0yz+ziyL+0uu69VZPv+RAEf1GKgshGGLJw3sOlcIdKuZaEAaA2 +b0nDUn229VpKXb9cg47Ae1Yb0sJcTkyIBuhkQKln3uhG2xct9nDfVal05+229AJQ +Ly1f0UfEofvD/OLjFG3umF857T1Vr5azj8zFOvNi503gV458lH3wKC+9UHUf46sg +Hc8Tyrz1q8VAIamJe7BLUeRFwGvBAgMBAAGjMjAwMA8GA1UdEwQIMAYBAf8CAQMw +HQYDVR0OBBYEFJqKmR4xTB6y5i22mu3HHjHK3Tv+MA0GCSqGSIb3DQEBCwUAA4IB +AQCkFsqyeAjztDBkTQrPxAB0Vvx6KPINApHGIHkJj/9crKXZEQcNJcJr35hfLcgv +hSsmLMdeRFCeaG5QLmUKI6GFYIbX+6nawMLGzIPUTOGetNeuMauDXkq09Hu/UmjN +AOgoD5vWdtyTbItv21enJnUelClAJ7VXti2QpyRM2puPHpZMNi4FWgLGPo6hq5ka +d7KomzW8JLh2Vd67v/6mXGpST4EzyRe+Yb2FJZUmhxVWt68/MFaflPQ2toPIsIpW +5m4OS+rT7t+uxPKWU/ogCK7BOUfE/qf3Al/osWkNKnFQDtO/7x7InEmRoP9EUv07 +JY4vK/+k5fJUZHpJzpuKxbBo +-----END CERTIFICATE----- diff --git a/configuration-manager/src/test/resources/test-certificates/test-cloud.key.pem b/configuration-manager/src/test/resources/test-certificates/test-cloud.key.pem new file mode 100644 index 000000000..5c201123b --- /dev/null +++ b/configuration-manager/src/test/resources/test-certificates/test-cloud.key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCaty+nCpMpoaCl +Au6pcyLpOsfkrGSkUfyUfm+rZqjO7tCdl7EzRDJ/VNiS+mLLewNO7qbMbKxP3pC/ +wrwIjyDbighVEqclQSzbc2BL4pQro3PZ4iNFpoxVGHhwUFxGSvegVuT66MbJKf4F +Hy/h5GOrn6rMVfx+zByh4GaWRT4z3aSD8FzLrk+Da/nU5G4aFueaqwS6jI6xKO5w +vjfNM5C3AC9rmu90RFU+Hv3vwj5QYVRdpJCdik/yjLAJFEhis9MEccEDRD/uwNva +UIDXIs9LAP8flC8U7GY3dxgCfO+BBk7z6ykKLLVPsrEUECe61WfxKPoF4U0Zm+zO +aUMKzngDAgMBAAECggEADZysXp/5jqIfxiTZ5qKwoaCAUcgxGpv0AwSUwpnHpF0y +kYr1FjOb1cQbQeMxqgFqqxkoJ7gv+N72EUkKzh35oBcm4IVBm29+iNg/0q1ep/A7 +kyHoRIqAskPy5L7NP4n8+2hpwQGV68D8DmtOBSvY8RKdLlkqW+WNZ1ic7f+tl1Z3 +vi3o0SfID6FvVTUXhWMopR2eLDOn5oX9MQE1uXqZyBDk8/6DxcMqjleDnVMmJWMz +HPlhPK2TFcW8UPxYuCe2z1s1OMR+sdOKZ0g87wD/GEm95SR8gTzbN6OJkvWVxYIl +RPTk4ukK5gyhJDKAh+/jQXEkzdmwAySBR/x6to2dwQKBgQDSW9XloxjpcvkpkaPe +rXsR+FY/TCBLSfFrtXnKxSmqLxSZBBWHNlyhMO7zdJRa6m9EnPZizIv0Kl/RkHzH +GMc4GYiJEJ9p0kiyB2iDXuxurw2BrCyowNWJOvQ1i8EMULv39DIYGHtTNxHXRPqx +mkPtAed2mi+BWieCtoRJto2JUQKBgQC8SLUEB/f/Wll2ujhyi01WSbNe9+MoOTmn +ibN6Uf3bZtul0MS6uKs4Bljjo9BovyBNXqCVcG+VngyhyoV+Ed/dSjBRnG9nol0o +uMWHDIFn7BjdfT5tekzUy40K2BeU6RRRLZ+Wrh87mVC0H18hTqKQoXPZmQwyXBGQ +sq1bOccXEwKBgQCvRCz3Y+jBuTW4WMw8IDbGRi82FetiT32CzHVpaNTKIuf6hdia +C8Up2Gd/GMby6RlEBbOTpfGFwjiLluMfz5lNOJj1+o+Xz8kZ6+o8ar57igaq4BVl +lSVVbXVDl+mEpU+3zBJg2SUHtH586dAmYe3ubwO3Ycfq8n6w/flCoYNTwQKBgQCb +9eXrVuZ4IFDm5c4II5eGGDp6Of9xvCUjwA5pDi2nZYYfzdSHjpxVJfzPY0wo19hb +/jwqTR5A4tA33FfDW+8BkIiBsYEeaQGdz5/fA97VRF48aZgieyHVSl6kUucFtCPe +Mlp1J/o7Ff4hlbLpFgfWocYiwoG13Um3gCnU3QEq/QKBgQCpXFRfz3CKM2Qs1ThV +Rs7qxcmpReQTRy0uMQ5C/VTMSI+S9LAreVn/juZV4DY+ukV6gXFENpXRzU+K71cN +nducNnGJ0LoiNdtiPuCiCVLNkuYU87HlIPIc07g3MSUJ6ZVs38mI8yFnGhXsMpfB +9WU9YlS49UkHvw9ma7U8+MldoQ== +-----END PRIVATE KEY----- diff --git a/configuration-manager/src/test/resources/test-certificates/test-cloud.p12 b/configuration-manager/src/test/resources/test-certificates/test-cloud.p12 new file mode 100644 index 000000000..ac088c1cb Binary files /dev/null and b/configuration-manager/src/test/resources/test-certificates/test-cloud.p12 differ diff --git a/configuration-manager/src/test/resources/test-certificates/test-cloud.truststore.p12 b/configuration-manager/src/test/resources/test-certificates/test-cloud.truststore.p12 new file mode 100644 index 000000000..434905c9a Binary files /dev/null and b/configuration-manager/src/test/resources/test-certificates/test-cloud.truststore.p12 differ diff --git a/configuration-manager/src/test/resources/test-certificates/test-conf-system.p12 b/configuration-manager/src/test/resources/test-certificates/test-conf-system.p12 new file mode 100644 index 000000000..0c7e3633e Binary files /dev/null and b/configuration-manager/src/test/resources/test-certificates/test-conf-system.p12 differ diff --git a/configuration-manager/src/test/resources/test-certificates/test-hdevice.p12 b/configuration-manager/src/test/resources/test-certificates/test-hdevice.p12 new file mode 100644 index 000000000..c7eb364e2 Binary files /dev/null and b/configuration-manager/src/test/resources/test-certificates/test-hdevice.p12 differ diff --git a/configuration-manager/src/test/resources/test-certificates/test-orch.p12 b/configuration-manager/src/test/resources/test-certificates/test-orch.p12 new file mode 100644 index 000000000..119baf27a Binary files /dev/null and b/configuration-manager/src/test/resources/test-certificates/test-orch.p12 differ diff --git a/configuration-manager/src/test/resources/test-certificates/test-sreg.cert.pem b/configuration-manager/src/test/resources/test-certificates/test-sreg.cert.pem new file mode 100644 index 000000000..72e8d632a --- /dev/null +++ b/configuration-manager/src/test/resources/test-certificates/test-sreg.cert.pem @@ -0,0 +1,80 @@ +Bag Attributes + friendlyName: service_registry.example.corp.arrowhead.eu + localKeyID: 54 69 6D 65 20 31 36 30 34 36 36 32 37 30 37 31 37 34 +subject=CN = service_registry.example.corp.arrowhead.eu + +issuer=C = DE, ST = Berlin, L = Berlin, O = AHT, OU = arrowhead.eu, CN = example.corp.arrowhead.eu + +-----BEGIN CERTIFICATE----- +MIIDrTCCApWgAwIBAgIEX6UzaTANBgkqhkiG9w0BAQsFADB4MQswCQYDVQQGEwJE +RTEPMA0GA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4xDDAKBgNVBAoMA0FI +VDEVMBMGA1UECwwMYXJyb3doZWFkLmV1MSIwIAYDVQQDDBlleGFtcGxlLmNvcnAu +YXJyb3doZWFkLmV1MB4XDTIwMTEwNjExMjg0MVoXDTMwMTEwNjExMjg0MVowNTEz +MDEGA1UEAwwqc2VydmljZV9yZWdpc3RyeS5leGFtcGxlLmNvcnAuYXJyb3doZWFk +LmV1MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxRAbGzhXa/9qmsVs +tavu72oQaPxaqkinE4NOxlqv309nKrtV24ETW0xilD9L6UUkdHOMXDuCi9vSivHD +9LT+lSIgy+O1+7mpH4ykN9Fv6KSXoDCt37+nCV8ysp7syy1cVh9sMGbGZcbqenOn +S7aNZ0YlLMsvgbB4rG2CdRRFtOUaVWre3lKd3U6DW69jTk+ObtbLymfrlDqTugmj +FWbBLbEq5MCtnDR1sZ5aupHW8KTxnqMOz4Q0Xg7NYUAad1hGkzkM304Y8BO99RNp +Of96B54R8zB30nvsCBR7vp4/pzgqtf4b2qY25eU1Sn530LHpp2TrEumUpyyTbh8P +xGssiwIDAQABo4GBMH8wGgYDVR0RBBMwEYIJbG9jYWxob3N0hwR/AAABMEIGA1Ud +IwQ7MDmAFBOMBYikgCvo8FXUYauQwlW8VZvqoRukGTAXMRUwEwYDVQQDDAxhcnJv +d2hlYWQuZXWCBF+kKMowHQYDVR0OBBYEFMRvboBK5sZoOK6kt5PUmkNnWrTtMA0G +CSqGSIb3DQEBCwUAA4IBAQA2yW1neYa+FCfiBQApV17RJDA8CbdU0jgcJNwwg1OG +AHhtDZ5zy/v/mfI1fYF3aiB/UgIfaNzqRtTLNLdpSgV3TNCsP5O7EYgyElQkNcIH +LJAFbqUnObFgrhuU6Sdv7qtHy3s0sKePDvpxa4G6jD6RdJqxCd5ziIjh57tFE3bt +0KMFdMKL9YahBElPrwqqA6wEKWHQBJSg841Up4noYg1HI32hJQP2pzeXl5zU0grR +Cp5y37gEa0KPosQp7u9e6D1O1/WyYuEZ5p81fCgdzNsJc77Pi147AT5UQLxgv3FZ +mchn0yB3mDBE/qmPSeoq5L8lwkpjHuDHbTZA8LSg8CbO +-----END CERTIFICATE----- +Bag Attributes + friendlyName: CN=example.corp.arrowhead.eu,OU=arrowhead.eu,O=AHT,L=Berlin,ST=Berlin,C=DE +subject=C = DE, ST = Berlin, L = Berlin, O = AHT, OU = arrowhead.eu, CN = example.corp.arrowhead.eu + +issuer=CN = arrowhead.eu + +-----BEGIN CERTIFICATE----- +MIIDgzCCAmugAwIBAgIEX6QoyjANBgkqhkiG9w0BAQsFADAXMRUwEwYDVQQDDAxh +cnJvd2hlYWQuZXUwHhcNMjAxMTA1MTYzMTA2WhcNMzAxMTA1MTYzMTA2WjB4MQsw +CQYDVQQGEwJERTEPMA0GA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4xDDAK +BgNVBAoMA0FIVDEVMBMGA1UECwwMYXJyb3doZWFkLmV1MSIwIAYDVQQDDBlleGFt +cGxlLmNvcnAuYXJyb3doZWFkLmV1MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAmrcvpwqTKaGgpQLuqXMi6TrH5KxkpFH8lH5vq2aozu7QnZexM0Qyf1TY +kvpiy3sDTu6mzGysT96Qv8K8CI8g24oIVRKnJUEs23NgS+KUK6Nz2eIjRaaMVRh4 +cFBcRkr3oFbk+ujGySn+BR8v4eRjq5+qzFX8fswcoeBmlkU+M92kg/Bcy65Pg2v5 +1ORuGhbnmqsEuoyOsSjucL43zTOQtwAva5rvdERVPh7978I+UGFUXaSQnYpP8oyw +CRRIYrPTBHHBA0Q/7sDb2lCA1yLPSwD/H5QvFOxmN3cYAnzvgQZO8+spCiy1T7Kx +FBAnutVn8Sj6BeFNGZvszmlDCs54AwIDAQABo3YwdDAPBgNVHRMECDAGAQH/AgEC +MEIGA1UdIwQ7MDmAFJqKmR4xTB6y5i22mu3HHjHK3Tv+oRukGTAXMRUwEwYDVQQD +DAxhcnJvd2hlYWQuZXWCBFzVPq0wHQYDVR0OBBYEFBOMBYikgCvo8FXUYauQwlW8 +VZvqMA0GCSqGSIb3DQEBCwUAA4IBAQAEhHykqvmRnl3S0aVv4tSoA+tGxwBMaRoY +OvxEDC321CFY/QkWaQfUpVg/eGgsLLFweUp9OFXxkgK4H/rUvQ/twt7oUIDUdJd+ +sGn04mIaym3M9IWhCTxqYf+X3/SHWmI5GnT3dQu9ayJKitD28/6FMLMqEdPiON/9 +VMzkoD2ndN6VSJWOWSg3xmOJ93u+xGbTkweIFildnhf79QwgColJGBytIWBhggvV +DVccNfLqmq9NNaxvPx/y2Oe1ngNhLcqqTUSiwKH42pdupKWZD/EcNlMXttRrkiOt +S3sXv18pDV/094abPB38ulgeeY7Kt2lz12qK7irxjZsVPahyW1Z0 +-----END CERTIFICATE----- +Bag Attributes + friendlyName: CN=arrowhead.eu +subject=CN = arrowhead.eu + +issuer=CN = arrowhead.eu + +-----BEGIN CERTIFICATE----- +MIIC3jCCAcagAwIBAgIEXNU+rTANBgkqhkiG9w0BAQsFADAXMRUwEwYDVQQDDAxh +cnJvd2hlYWQuZXUwHhcNMTkwNTEwMDkwNDQ1WhcNMjkwNTEwMDkwNDQ1WjAXMRUw +EwYDVQQDDAxhcnJvd2hlYWQuZXUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQCuB4z+wchXDKdfy9YFZha2U0khBAWuHYerBlBLM0Oqvr4c/YYcZpNwTWY2 +tk6UXPUTQ8gI9V6Ob7DRXoAfLDhCRGKySN0BlGnjUJkItP25Sj0RfiTL3b8fFEIT +Z8pg+6pAfeFQgV0yz+ziyL+0uu69VZPv+RAEf1GKgshGGLJw3sOlcIdKuZaEAaA2 +b0nDUn229VpKXb9cg47Ae1Yb0sJcTkyIBuhkQKln3uhG2xct9nDfVal05+229AJQ +Ly1f0UfEofvD/OLjFG3umF857T1Vr5azj8zFOvNi503gV458lH3wKC+9UHUf46sg +Hc8Tyrz1q8VAIamJe7BLUeRFwGvBAgMBAAGjMjAwMA8GA1UdEwQIMAYBAf8CAQMw +HQYDVR0OBBYEFJqKmR4xTB6y5i22mu3HHjHK3Tv+MA0GCSqGSIb3DQEBCwUAA4IB +AQCkFsqyeAjztDBkTQrPxAB0Vvx6KPINApHGIHkJj/9crKXZEQcNJcJr35hfLcgv +hSsmLMdeRFCeaG5QLmUKI6GFYIbX+6nawMLGzIPUTOGetNeuMauDXkq09Hu/UmjN +AOgoD5vWdtyTbItv21enJnUelClAJ7VXti2QpyRM2puPHpZMNi4FWgLGPo6hq5ka +d7KomzW8JLh2Vd67v/6mXGpST4EzyRe+Yb2FJZUmhxVWt68/MFaflPQ2toPIsIpW +5m4OS+rT7t+uxPKWU/ogCK7BOUfE/qf3Al/osWkNKnFQDtO/7x7InEmRoP9EUv07 +JY4vK/+k5fJUZHpJzpuKxbBo +-----END CERTIFICATE----- diff --git a/configuration-manager/src/test/resources/test-certificates/test-sreg.key.pem b/configuration-manager/src/test/resources/test-certificates/test-sreg.key.pem new file mode 100644 index 000000000..0d24c2281 --- /dev/null +++ b/configuration-manager/src/test/resources/test-certificates/test-sreg.key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDFEBsbOFdr/2qa +xWy1q+7vahBo/FqqSKcTg07GWq/fT2cqu1XbgRNbTGKUP0vpRSR0c4xcO4KL29KK +8cP0tP6VIiDL47X7uakfjKQ30W/opJegMK3fv6cJXzKynuzLLVxWH2wwZsZlxup6 +c6dLto1nRiUsyy+BsHisbYJ1FEW05RpVat7eUp3dToNbr2NOT45u1svKZ+uUOpO6 +CaMVZsEtsSrkwK2cNHWxnlq6kdbwpPGeow7PhDReDs1hQBp3WEaTOQzfThjwE731 +E2k5/3oHnhHzMHfSe+wIFHu+nj+nOCq1/hvapjbl5TVKfnfQsemnZOsS6ZSnLJNu +Hw/EayyLAgMBAAECggEAEK4k/TrZpi8/1iCb67dv9B1QN51qDJE1wzW8Iey53Pca ++Rz+Ts7a1UGJSUR0/SZ4ctBNCImTqpmiyqwINYW/KYnH/jokxKKZ1C0oFqlzBYgu +ZXAkEj+7YymxtY405kXNU7o5LXz653vc4RyPrp2h089g2i0gtPjmXgIEUZCzuHSo +privjmUQtX/8MSj68gDQ5555/EUtWhMjEBrl0nOBWcsg3vNhPtHTWJCnKMMlA/hr +hqItoygjjYMMXujxX/laPke1yjPiXA7NP/YM1gAZUicB7TPaf9ltJ3bMyhnuUWab +k5iprSGMlxYaJIjoHDYtzRYe6DAZ998YMfjJD3MosQKBgQDpXp8vBi819Ucw4viQ +N5Fh1GOgq7QaUlMdobZDe6AUKC2ngdlvt3linmbGsLjivxwcgmBBqT+5vdAN4ND4 +JURvobAiLNE+1pUEdNBUJ62RrorIEqHH/Egrgb9azW5GT+WRk3TPWL78WIC/PBfI +laJetx32KlVc+vVYcJH9yk8IzQKBgQDYLCx2Ysz3H+XFa5Wvar5owrhBXBZoWclR +8yHIYyrjaqYQIo+cy8Lhneh+KQ97DITTTuJHAv1gvMxtwnU2ZiJCNYdgEl4+vH+t +9aJTjdTI5NvYb1mxDOA9JUT3DzZK1s2VnkOmA43eIlPCog7ZkvHKt/rX0iKX2NaX +wsY/GWNqtwKBgQCaBNYAXg+IiBHtJM8xt0rfCyKZptjdylmKo/C7xvqWcxH2jI9p +2Ohm+u4P0hCjrceq1S0cCMzDFJAcqLSiIU5ycn7hfzy2QT7mSwY3lFxMWqrDcvCN +IWasOByHnC9cflyf4HbmZcbemraV/94ehws7gZVnovblv8dvEBR6MCxLPQKBgBH1 +0Zfga5EYqFl3r88MOlev3ekoQoBW/V/+qE7i1lxgrv1mMbJgR4fBO+DHfo7Fon9/ +7VLjD7Qq230/C8gCQlRn6CB2RjhGJwGIB/2TKhuq0A0yayxHmpXZ89nm/KbJI/mL +VamEYBQVnAnutvciu04RbRjjT5Z4IJQpTZlOfbEPAoGAO6RBn7kHsSh4F6QEVS4U +nL5X8uG0I6xSdLDT9xNbyjwHs5r0EBkjo+QXG5vC/ofgVp52DEPoicSCue5l/C3e +h2kq/H55iHkcJC/EVEwOAiGZPOznFMzwfWCT8sW9uIloe+vLAJiqezltKK+oDA2P +/rpiixiWCPNlHvre5ugcPw0= +-----END PRIVATE KEY----- diff --git a/configuration-manager/src/test/resources/test-certificates/test-sreg.p12 b/configuration-manager/src/test/resources/test-certificates/test-sreg.p12 new file mode 100644 index 000000000..caf350bff Binary files /dev/null and b/configuration-manager/src/test/resources/test-certificates/test-sreg.p12 differ diff --git a/configuration-manager/src/test/resources/test-certificates/test-sysop.p12 b/configuration-manager/src/test/resources/test-certificates/test-sysop.p12 new file mode 100644 index 000000000..7668c04a2 Binary files /dev/null and b/configuration-manager/src/test/resources/test-certificates/test-sysop.p12 differ diff --git a/configuration/.project b/configuration/.project index d99f4690a..bcce7699e 100644 --- a/configuration/.project +++ b/configuration/.project @@ -10,6 +10,11 @@ + + org.springframework.ide.eclipse.boot.validation.springbootbuilder + + + org.eclipse.m2e.core.maven2Builder diff --git a/core-common/pom.xml b/core-common/pom.xml index f1da647ff..3cd9d10e7 100644 --- a/core-common/pom.xml +++ b/core-common/pom.xml @@ -23,6 +23,18 @@ ${bouncy.castle.version} compile + + org.eclipse.californium + californium-core + 2.2.3 + jar + + + org.eclipse.californium + scandium + 2.2.3 + jar + diff --git a/core-common/src/deb/control/templates b/core-common/src/deb/control/templates index 358f5613c..b39c1f6d2 100644 --- a/core-common/src/deb/control/templates +++ b/core-common/src/deb/control/templates @@ -187,3 +187,48 @@ Description: A space-separated list of additional ip addresses to be used as SAN Template: arrowhead-core-common/san_dns Type: string Description: A space-separated list of domain names to be used as SANs + +Template: arrowhead-core-common/provided_service_definition +Type: string +Default: definition1 +Description: Enter the definition of the service provided by the HawkBit Configuration Manager + +Template: arrowhead-core-common/provided_service_uri +Type: string +Default: / +Description: Enter the URI of the service provided by the HawkBit Configuration Manager + +Template: arrowhead-core-common/provided_service_version +Type: string +Default: 1 +Description: Enter the version of the service provided by the HawkBit Configuration Manager + +Template: arrowhead-core-common/provided_service_interface +Type: string +Default: HTTP-SECURE-JSON +Description: Enter the interface name of the service provided by the HawkBit Configuration Manager + +Template: arrowhead-core-common/hawkbit_host +Type: string +Default: 127.0.0.1 +Description: Enter the hostname or IP address of the HawkBit + +Template: arrowhead-core-common/hawkbit_port +Type: string +Default: 8080 +Description: Enter a username that can access HawkBit + +Template: arrowhead-core-common/hawkbit_username +Type: string +Default: hawkbit +Description: Enter a username that can access HawkBit + +Template: arrowhead-core-common/hawkbit_password +Type: string +Default: hawkbit +Description: Enter the password the previously specified HawkBit user + +Template: arrowhead-core-common/hawkbit_tenant +Type: string +Default: DEFAULT +Description: Enter the name of the HawkBit tenant diff --git a/core-common/src/main/java/eu/arrowhead/common/CommonConstants.java b/core-common/src/main/java/eu/arrowhead/common/CommonConstants.java index f6e243e3e..e1bdeda0d 100644 --- a/core-common/src/main/java/eu/arrowhead/common/CommonConstants.java +++ b/core-common/src/main/java/eu/arrowhead/common/CommonConstants.java @@ -126,9 +126,9 @@ public class CommonConstants { public static final String CORE_SERVICE_ONBOARDING_WITH_SHARED_SECRET_AND_NAME = "onboarding-with-shared-secret-and-name"; public static final String CORE_SERVICE_ONBOARDING_WITH_SHARED_SECRET_AND_CSR = "onboarding-with-shared-secret-and-csr"; - public static final String CORE_SERVICE_TRANSLATOR = "translation-service"; - public static final String CORE_SERVICE_TRANSLATOR_FIWARE = "translation-fiware-service"; - public static final String CORE_SERVICE_TRANSLATOR_PLUGIN = "translation-plugin-service"; + public static final String CORE_SERVICE_TRANSLATOR = "translation-service"; + public static final String CORE_SERVICE_TRANSLATOR_FIWARE = "translation-fiware-service"; + public static final String CORE_SERVICE_TRANSLATOR_PLUGIN = "translation-plugin-service"; public static final String COMMON_FIELD_NAME_ID = "id"; @@ -288,9 +288,9 @@ public class CommonConstants { public static final String TIMEMANAGER_URI = "/timemanager"; public static final String OP_TIMEMANAGER_TIME = "/time"; - public static final String TRANSLATOR_URI = "/translator"; - public static final String OP_TRANSLATOR_FIWARE_URI = "/v2"; - public static final String OP_TRANSLATOR_PLUGIN_URI = "/plugin"; + public static final String TRANSLATOR_URI = "/translator"; + public static final String OP_TRANSLATOR_FIWARE_URI = "/v2"; + public static final String OP_TRANSLATOR_PLUGIN_URI = "/plugin"; public static final String SWAGGER_COMMON_PACKAGE = "eu.arrowhead.common.swagger"; public static final String SWAGGER_UI_URI = "/swagger-ui.html"; diff --git a/core-common/src/main/java/eu/arrowhead/common/CoreCommonConstants.java b/core-common/src/main/java/eu/arrowhead/common/CoreCommonConstants.java index 88eead8ae..c1660e3f5 100644 --- a/core-common/src/main/java/eu/arrowhead/common/CoreCommonConstants.java +++ b/core-common/src/main/java/eu/arrowhead/common/CoreCommonConstants.java @@ -120,6 +120,13 @@ public class CoreCommonConstants { public static final String REQUEST_PARAM_DIRECTION = "direction"; public static final String REQUEST_PARAM_SORT_FIELD = "sort_field"; public static final String REQUEST_PARAM_SERVICE_DEFINITION = "service_definition"; + + public static final String COAP_SERVER_ADDRESS_ENABLED = "coap.server.enabled"; + public static final String $COAP_SERVER_ADDRESS_ENABLED = "${" + COAP_SERVER_ADDRESS_ENABLED + ":false}"; + public static final String COAP_SERVER_ADDRESS = "coap.server.address"; + public static final String $COAP_SERVER_ADDRESS = "${" + COAP_SERVER_ADDRESS + ":0.0.0.0}"; + public static final String COAP_SERVER_PORT = "coap.server.port"; + public static final String $COAP_SERVER_PORT = "${" + COAP_SERVER_PORT + ":0}"; public static final long CONVERSION_MILLISECOND_TO_SECOND = 1000; public static final long CONVERSION_MILLISECOND_TO_MINUTE = 60000; diff --git a/core-common/src/main/java/eu/arrowhead/common/Defaults.java b/core-common/src/main/java/eu/arrowhead/common/Defaults.java index e6e287b5e..a33d8195d 100644 --- a/core-common/src/main/java/eu/arrowhead/common/Defaults.java +++ b/core-common/src/main/java/eu/arrowhead/common/Defaults.java @@ -50,6 +50,7 @@ public class Defaults { public static final int DEFAULT_CONFIGURATION_PORT = 8459; public static final int DEFAULT_TRANSLATOR_PORT = 8462; public static final int DEFAULT_PLANT_DESCRIPTION_ENGINE_PORT = 8432; + public static final int DEFAULT_HAWKBIT_CONFIGURATION_MANAGER_PORT = 8447; // HTTP client defaults public static final int DEFAULT_CONNECTION_TIMEOUT = 30000; diff --git a/core-common/src/main/java/eu/arrowhead/common/SslUtil.java b/core-common/src/main/java/eu/arrowhead/common/SslUtil.java index 351d500f5..400a6d66f 100644 --- a/core-common/src/main/java/eu/arrowhead/common/SslUtil.java +++ b/core-common/src/main/java/eu/arrowhead/common/SslUtil.java @@ -16,10 +16,17 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; + +import eu.arrowhead.common.CommonConstants; +import eu.arrowhead.common.SSLProperties; +import eu.arrowhead.common.exception.ArrowheadException; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStreamReader; +import java.util.ServiceConfigurationError; import java.nio.file.Files; import java.nio.file.InvalidPathException; import java.nio.file.Paths; @@ -31,12 +38,16 @@ import java.security.Security; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; - +import java.security.cert.X509Certificate; + import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManagerFactory; - + +import org.apache.http.ssl.SSLContexts; +import org.apache.http.ssl.TrustStrategy; + import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; import org.bouncycastle.jce.provider.BouncyCastleProvider; @@ -51,7 +62,7 @@ public class SslUtil { //================================================================================================= - // members + // members private static final String BOUNCYCASTLE_IDENTIFIER = "BC"; private static final String CA_CERTIFICATE = "ca-certificate"; @@ -64,69 +75,115 @@ public class SslUtil //================================================================================================= // methods + //------------------------------------------------------------------------------------------------- - public static SSLSocketFactory getSslSocketFactory(final String caCrtFile, final String crtFile, final String keyFile, final String password) - throws - InvalidPathException, IOException, KeyStoreException, NoSuchAlgorithmException, CertificateException, - UnrecoverableKeyException, KeyManagementException, Exception { - - PEMParser parser = null; - try { - Security.addProvider(new BouncyCastleProvider()); - - // load CA certificate - parser = new PEMParser(new InputStreamReader(new ByteArrayInputStream(Files.readAllBytes(Paths.get(caCrtFile))))); - final X509CertificateHolder caCert = (X509CertificateHolder) parser.readObject(); - parser.close(); - - // load client certificate - parser = new PEMParser(new InputStreamReader(new ByteArrayInputStream(Files.readAllBytes(Paths.get(crtFile))))); - final X509CertificateHolder cert = (X509CertificateHolder) parser.readObject(); - parser.close(); - - // load client private key - parser = new PEMParser(new InputStreamReader(new ByteArrayInputStream(Files.readAllBytes(Paths.get(keyFile))))); - Object obj = parser.readObject(); - parser.close(); - KeyPair key = null; - JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(BOUNCYCASTLE_IDENTIFIER); - if (obj instanceof PEMEncryptedKeyPair) { - PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().build(password.toCharArray()); - converter = new JcaPEMKeyConverter().setProvider(BOUNCYCASTLE_IDENTIFIER); - key = converter.getKeyPair(((PEMEncryptedKeyPair) obj).decryptKeyPair(decProv)); - } else { - key = converter.getKeyPair((PEMKeyPair) obj); - } - - final JcaX509CertificateConverter certConverter = new JcaX509CertificateConverter(); - certConverter.setProvider(BOUNCYCASTLE_IDENTIFIER); - - // CA certificate is used to authenticate server - final KeyStore caKs = KeyStore.getInstance(KeyStore.getDefaultType()); - caKs.load(null, null); - caKs.setCertificateEntry(CA_CERTIFICATE, certConverter.getCertificate(caCert)); - final TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - tmf.init(caKs); - - // Client key and certificates are sent to server so it can authenticate us - final KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); - ks.load(null, null); - ks.setCertificateEntry(CERTIFICATE, certConverter.getCertificate(cert)); - ks.setKeyEntry(PRIVATE_KEY, key.getPrivate(), password.toCharArray(), new java.security.cert.Certificate[]{certConverter.getCertificate(cert)}); - final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - kmf.init(ks, password.toCharArray()); - - final SSLContext context = SSLContext.getInstance(TLS_VERSION); - context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); - - return context.getSocketFactory(); - } catch(Exception e) { - logger.debug("Unhandled exception: " + e.toString()); - throw e; + /** + * Returns an SSLfactory from the system's key and trust stores + * + * @return An SSLfactory + * @throws Exception + */ + public static SSLSocketFactory getSSLSocketFactory(final SSLProperties sslProperties) throws Exception { + try { + X509Certificate caCert = null; + final KeyStore trustStore = getTrustStore(sslProperties); + final KeyStore keyStore = getKeyStore(sslProperties); + final TrustStrategy acceptingTrustStrategy = (final X509Certificate[] chain, final String authType) -> true; + final SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(trustStore, acceptingTrustStrategy).loadKeyMaterial(keyStore, + sslProperties.getKeyStorePassword().toCharArray()).build(); + return sslContext.getSocketFactory(); + } catch (final Exception e) { + throw new ArrowheadException("Certificate failure"); + } + } + + //------------------------------------------------------------------------------------------------- + private static KeyStore getKeyStore(final SSLProperties sslProperties) { + try { + final KeyStore keystore = KeyStore.getInstance(sslProperties.getKeyStoreType()); + keystore.load(sslProperties.getKeyStore().getInputStream(), sslProperties.getKeyStorePassword().toCharArray()); + return keystore; + } catch (final KeyStoreException | IOException | CertificateException | NoSuchAlgorithmException e) { + throw new ServiceConfigurationError("Cannot open keystore: " + e.getMessage()); + } + } + + //------------------------------------------------------------------------------------------------- + private static KeyStore getTrustStore(final SSLProperties sslProperties) { + try { + final KeyStore truststore = KeyStore.getInstance(sslProperties.getKeyStoreType()); + truststore.load(sslProperties.getTrustStore().getInputStream(), sslProperties.getTrustStorePassword().toCharArray()); + return truststore; + } catch (final KeyStoreException | IOException | CertificateException | NoSuchAlgorithmException e) { + throw new ServiceConfigurationError("Cannot open truststore: " + e.getMessage()); + } + } + + //------------------------------------------------------------------------------------------------- + public static SSLSocketFactory getSSLSocketFactory(final String caCrtFile, final String crtFile, final String keyFile, final String password) + throws + InvalidPathException, IOException, KeyStoreException, NoSuchAlgorithmException, CertificateException, + UnrecoverableKeyException, KeyManagementException, Exception { + + PEMParser parser = null; + try { + Security.addProvider(new BouncyCastleProvider()); + + // load CA certificate + parser = new PEMParser(new InputStreamReader(new ByteArrayInputStream(Files.readAllBytes(Paths.get(caCrtFile))))); + final X509CertificateHolder caCert = (X509CertificateHolder) parser.readObject(); + parser.close(); + + // load client certificate + parser = new PEMParser(new InputStreamReader(new ByteArrayInputStream(Files.readAllBytes(Paths.get(crtFile))))); + final X509CertificateHolder cert = (X509CertificateHolder) parser.readObject(); + parser.close(); + + // load client private key + parser = new PEMParser(new InputStreamReader(new ByteArrayInputStream(Files.readAllBytes(Paths.get(keyFile))))); + Object obj = parser.readObject(); + parser.close(); + KeyPair key = null; + JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(BOUNCYCASTLE_IDENTIFIER); + if (obj instanceof PEMEncryptedKeyPair) { + PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().build(password.toCharArray()); + converter = new JcaPEMKeyConverter().setProvider(BOUNCYCASTLE_IDENTIFIER); + key = converter.getKeyPair(((PEMEncryptedKeyPair) obj).decryptKeyPair(decProv)); + } else { + key = converter.getKeyPair((PEMKeyPair) obj); + } + + final JcaX509CertificateConverter certConverter = new JcaX509CertificateConverter(); + certConverter.setProvider(BOUNCYCASTLE_IDENTIFIER); + + // CA certificate is used to authenticate server + final KeyStore caKs = KeyStore.getInstance(KeyStore.getDefaultType()); + caKs.load(null, null); + caKs.setCertificateEntry(CA_CERTIFICATE, certConverter.getCertificate(caCert)); + final TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(caKs); + + // Client key and certificates are sent to server so it can authenticate us + final KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); + ks.load(null, null); + ks.setCertificateEntry(CERTIFICATE, certConverter.getCertificate(cert)); + ks.setKeyEntry(PRIVATE_KEY, key.getPrivate(), password.toCharArray(), new java.security.cert.Certificate[]{certConverter.getCertificate(cert)}); + final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + kmf.init(ks, password.toCharArray()); + + final SSLContext context = SSLContext.getInstance(TLS_VERSION); + context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); + + return context.getSocketFactory(); + } catch(Exception e) { + logger.debug("Unhandled exception: " + e.toString()); + throw e; } finally { if (parser != null) { parser.close(); } } } -} \ No newline at end of file + + +} diff --git a/core-common/src/main/java/eu/arrowhead/common/coap/AhCoapServer.java b/core-common/src/main/java/eu/arrowhead/common/coap/AhCoapServer.java new file mode 100644 index 000000000..62b813868 --- /dev/null +++ b/core-common/src/main/java/eu/arrowhead/common/coap/AhCoapServer.java @@ -0,0 +1,96 @@ +package eu.arrowhead.common.coap; + +import eu.arrowhead.common.coap.configuration.CoapServerConfiguration; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.security.GeneralSecurityException; +import java.security.cert.Certificate; +import java.util.ArrayList; +import java.util.List; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.eclipse.californium.core.CoapServer; +import org.eclipse.californium.core.network.CoapEndpoint; +import org.eclipse.californium.core.network.Endpoint; +import org.eclipse.californium.elements.UDPConnector; +import org.eclipse.californium.elements.util.SslContextUtil; +import org.eclipse.californium.scandium.DTLSConnector; +import org.eclipse.californium.scandium.config.DtlsConnectorConfig; +import org.eclipse.californium.scandium.dtls.CertificateType; + +public class AhCoapServer extends CoapServer { + + // ================================================================================================= + // members + private final Logger logger = LogManager.getLogger(AhCoapServer.class); + + public AhCoapServer(CoapServerConfiguration configuration) { + logger.info(String.format("CoAP Server [address:%s] [port:%d] [secured:%s]", + configuration.getAddress(), + configuration.getPort(), + configuration.isSecured())); + + if (configuration.isSecured()) { + logger.info(String.format("Security [ServerName:%s] [keyStore:%s] [TrustAlias:%s] [TrustStore:%s]", + configuration.getCredentials().getServerName(), + configuration.getCredentials().getKeyStorePath(), + configuration.getCertificates().getTrustName(), + configuration.getCertificates().getTrustStorePath())); + } + + addEndpoint( + configuration.isSecured() + ? createSecuredEndPoint(configuration) + : createUnsecuredEndPoint(configuration)); + } + + // ================================================================================================= + // assistant methods + // ------------------------------------------------------------------------------------------------- + private Endpoint createUnsecuredEndPoint(CoapServerConfiguration configuration) { + CoapEndpoint.Builder builder = new CoapEndpoint.Builder(); + UDPConnector udpConnector = new UDPConnector( + new InetSocketAddress(configuration.getAddress(), configuration.getPort())); + builder.setConnector(udpConnector); + return builder.build(); + } + + // ------------------------------------------------------------------------------------------------- + private Endpoint createSecuredEndPoint(CoapServerConfiguration configuration) { + DtlsConnectorConfig.Builder builder = new DtlsConnectorConfig.Builder(); + builder.setAddress(new InetSocketAddress(configuration.getAddress(), configuration.getPort())); + builder.setRecommendedCipherSuitesOnly(false); + try { + SslContextUtil.Credentials serverCredentials = SslContextUtil.loadCredentials( + SslContextUtil.CLASSPATH_SCHEME + configuration.getCredentials().getKeyStorePath().substring(10), + configuration.getCredentials().getServerName(), + configuration.getCredentials().getKeyStorePassword().toCharArray(), + configuration.getCredentials().getKeyPassword().toCharArray()); + Certificate[] trustedCertificates = SslContextUtil.loadTrustedCertificates( + SslContextUtil.CLASSPATH_SCHEME + configuration.getCertificates().getTrustStorePath().substring(10), + "coap-root", + configuration.getCertificates().getTrustStorePassword().toCharArray()); + builder.setTrustStore(trustedCertificates); + builder.setRpkTrustAll(); + List types = new ArrayList<>(); + types.add(CertificateType.RAW_PUBLIC_KEY); + types.add(CertificateType.X_509); + builder.setIdentity(serverCredentials.getPrivateKey(), serverCredentials.getCertificateChain(), types); + + } catch (GeneralSecurityException e) { + e.printStackTrace(); + + } catch (IOException e) { + e.printStackTrace(); + + } + + DTLSConnector connector = new DTLSConnector(builder.build()); + CoapEndpoint.Builder coapBuilder = new CoapEndpoint.Builder(); + + coapBuilder.setConnector(connector); + + return coapBuilder.build(); + } +} diff --git a/core-common/src/main/java/eu/arrowhead/common/coap/configuration/CoapCertificates.java b/core-common/src/main/java/eu/arrowhead/common/coap/configuration/CoapCertificates.java new file mode 100644 index 000000000..8969ebd62 --- /dev/null +++ b/core-common/src/main/java/eu/arrowhead/common/coap/configuration/CoapCertificates.java @@ -0,0 +1,26 @@ +package eu.arrowhead.common.coap.configuration; + +public class CoapCertificates { + + private final String trustName; + private final String trustStorePassword; + private final String trustStorePath; + + public CoapCertificates(String trustName, String trustStorePassword, String trustStorePath) { + this.trustName = trustName; + this.trustStorePassword = trustStorePassword; + this.trustStorePath = trustStorePath; + } + + public String getTrustName() { + return trustName; + } + + public String getTrustStorePassword() { + return trustStorePassword; + } + + public String getTrustStorePath() { + return trustStorePath; + } +} diff --git a/core-common/src/main/java/eu/arrowhead/common/coap/configuration/CoapCredentials.java b/core-common/src/main/java/eu/arrowhead/common/coap/configuration/CoapCredentials.java new file mode 100644 index 000000000..e74629dcf --- /dev/null +++ b/core-common/src/main/java/eu/arrowhead/common/coap/configuration/CoapCredentials.java @@ -0,0 +1,32 @@ +package eu.arrowhead.common.coap.configuration; + +public class CoapCredentials { + + private final String keyStorePath; + private final String keyStorePassword; + private final String keyPassword; + private final String serverName; + + public CoapCredentials(String keyStorePath, String keyStorePassword, String keyPassword, String serverName) { + this.keyStorePath = keyStorePath; + this.keyStorePassword = keyStorePassword; + this.keyPassword = keyPassword; + this.serverName = serverName; + } + + public String getKeyStorePath() { + return keyStorePath; + } + + public String getKeyStorePassword() { + return keyStorePassword; + } + + public String getKeyPassword() { + return keyPassword; + } + + public String getServerName() { + return serverName; + } +} diff --git a/core-common/src/main/java/eu/arrowhead/common/coap/configuration/CoapServerConfiguration.java b/core-common/src/main/java/eu/arrowhead/common/coap/configuration/CoapServerConfiguration.java new file mode 100644 index 000000000..2101f354b --- /dev/null +++ b/core-common/src/main/java/eu/arrowhead/common/coap/configuration/CoapServerConfiguration.java @@ -0,0 +1,39 @@ +package eu.arrowhead.common.coap.configuration; + +public class CoapServerConfiguration { + + private final String address; + private final int port; + private final boolean secured; + private final CoapCredentials credentials; + private final CoapCertificates certificates; + + public CoapServerConfiguration(String address, int port, boolean secured, CoapCredentials credentials, + CoapCertificates certificates) { + this.address = address; + this.port = port; + this.secured = secured; + this.credentials = credentials; + this.certificates = certificates; + } + + public String getAddress() { + return address; + } + + public int getPort() { + return port; + } + + public boolean isSecured() { + return secured; + } + + public CoapCredentials getCredentials() { + return credentials; + } + + public CoapCertificates getCertificates() { + return certificates; + } +} diff --git a/core-common/src/main/java/eu/arrowhead/common/coap/tools/CoapTools.java b/core-common/src/main/java/eu/arrowhead/common/coap/tools/CoapTools.java new file mode 100644 index 000000000..8e4e7bb8f --- /dev/null +++ b/core-common/src/main/java/eu/arrowhead/common/coap/tools/CoapTools.java @@ -0,0 +1,73 @@ +package eu.arrowhead.common.coap.tools; + +import java.net.URLDecoder; +import java.security.Principal; +import java.util.LinkedHashMap; +import java.util.Map; +import org.eclipse.californium.core.Utils; +import org.eclipse.californium.core.server.resources.CoapExchange; +import org.eclipse.californium.elements.DtlsEndpointContext; +import org.eclipse.californium.elements.EndpointContext; + +public class CoapTools { + + public static String formatExchange(CoapExchange exchange) { + StringBuilder sb = new StringBuilder(); + sb.append("Request:\n\n"); + EndpointContext context = exchange.advanced().getRequest().getSourceContext(); + Principal identity = context.getPeerIdentity(); + if (identity != null) { + sb.append(context.getPeerIdentity()).append("\n"); + } else { + sb.append("anonymous\n"); + } + sb.append(context.get(DtlsEndpointContext.KEY_CIPHER)).append("\n"); + sb.append(Utils.prettyPrint(exchange.advanced().getRequest())).append("\n"); + return sb.toString(); + } + + public static Map getQueryParams(CoapExchange exchange) { + Map qp = new LinkedHashMap<>(); + try { + String query = exchange.advanced().getRequest().getOptions().getUriQueryString(); + String[] pairs = query.split("&"); + for (String pair : pairs) { + int idx = pair.indexOf("="); + qp.put(URLDecoder.decode(pair.substring(0, idx), "UTF-8"), URLDecoder.decode(pair.substring(idx + 1), "UTF-8")); + } + } catch (Exception ex) { + + } + return qp; + } + + public static String getParam(Map queryParams, String key, String defaultValue) { + if (!queryParams.containsKey(key)) { + return defaultValue; + } + return queryParams.get(key); + } + + public static int getParam(Map queryParams, String key, int defaultValue) { + if (!queryParams.containsKey(key)) { + return defaultValue; + } + return Integer.parseInt(queryParams.get(key)); + } + + public static long getParam(Map queryParams, String key, long defaultValue) { + if (!queryParams.containsKey(key)) { + return defaultValue; + } + return Long.parseLong(queryParams.get(key)); + } + + public static String getUrlPathValue(CoapExchange exchange, String resourcePath) { + String url = exchange.advanced().getRequest().getOptions().getUriPathString(); + int len = url.indexOf(resourcePath); + String tmp = url.substring(len + resourcePath.length()); + String[] paths = tmp.split("/"); + return (paths.length < 2) ? "" : paths[1]; + } + +} diff --git a/core-common/src/main/java/eu/arrowhead/common/core/CoreSystem.java b/core-common/src/main/java/eu/arrowhead/common/core/CoreSystem.java index 1f43c4fea..8ee7f6b4a 100644 --- a/core-common/src/main/java/eu/arrowhead/common/core/CoreSystem.java +++ b/core-common/src/main/java/eu/arrowhead/common/core/CoreSystem.java @@ -112,7 +112,9 @@ public enum CoreSystem { CoreSystemService.TRANSLATOR_FIWARE_SERVICE, CoreSystemService.TRANSLATOR_PLUGIN_SERVICE)), - PLANTDESCRIPTIONENGINE(Defaults.DEFAULT_PLANT_DESCRIPTION_ENGINE_PORT, List.of()); //TODO add PDE services + PLANTDESCRIPTIONENGINE(Defaults.DEFAULT_PLANT_DESCRIPTION_ENGINE_PORT, List.of()), //TODO add PDE services + + HAWKBITCONFIGURATIONMANAGER(Defaults.DEFAULT_HAWKBIT_CONFIGURATION_MANAGER_PORT, List.of()); //================================================================================================= // members diff --git a/core-common/src/main/java/eu/arrowhead/common/dto/shared/MqttRequestDTO.java b/core-common/src/main/java/eu/arrowhead/common/dto/shared/MqttRequestDTO.java new file mode 100644 index 000000000..f77d6e77c --- /dev/null +++ b/core-common/src/main/java/eu/arrowhead/common/dto/shared/MqttRequestDTO.java @@ -0,0 +1,45 @@ +package eu.arrowhead.common.dto.shared; + +import java.io.Serializable; +import java.util.Map; +import java.util.HashMap; + +public class MqttRequestDTO implements Serializable { + + //================================================================================================= + // members + + private static final long serialVersionUID = 2128696165217136429L; + + private String method; + private Map queryParameters = new HashMap<>(); + private String replyTo; + private Object payload; + + + //================================================================================================= + // methods + + //------------------------------------------------------------------------------------------------- + public MqttRequestDTO() {} + + //------------------------------------------------------------------------------------------------- + public MqttRequestDTO(final String method, Map queryParameters, final String replyTo, final Object payload) { + this.method = method; + this.queryParameters = queryParameters; + this.replyTo = replyTo; + this.payload = payload; + } + + //------------------------------------------------------------------------------------------------- + public String getMethod() { return method; } + public Map getQueryParameters() { return queryParameters; } + public String getReplyTo() { return replyTo; } + public Object getPayload() { return payload; } + + //------------------------------------------------------------------------------------------------- + public void setMethod(final String method) { this.method = method; } + public void setQueryParameters(final Map queryParameters) { this.queryParameters = queryParameters; } + public void setReplyTo(final String replyTo) { this.replyTo = replyTo; } + public void setPayload(final Object payload) { this.payload = payload; } +} diff --git a/core-common/src/main/java/eu/arrowhead/common/dto/shared/MqttResponseDTO.java b/core-common/src/main/java/eu/arrowhead/common/dto/shared/MqttResponseDTO.java new file mode 100644 index 000000000..7738dc570 --- /dev/null +++ b/core-common/src/main/java/eu/arrowhead/common/dto/shared/MqttResponseDTO.java @@ -0,0 +1,40 @@ +package eu.arrowhead.common.dto.shared; + +import java.io.Serializable; +import java.util.Map; + +public class MqttResponseDTO implements Serializable { + + //================================================================================================= + // members + + private static final long serialVersionUID = 7233496112738400439L; + + private String code; + private String contentType; + private Object payload; + + + //================================================================================================= + // methods + + //------------------------------------------------------------------------------------------------- + public MqttResponseDTO() {} + + //------------------------------------------------------------------------------------------------- + public MqttResponseDTO(final String code, final String contentType, final Object payload) { + this.code = code; + this.contentType = contentType; + this.payload = payload; + } + + //------------------------------------------------------------------------------------------------- + public String getCode() { return code; } + public String getContentType() { return contentType; } + public Object getPayload() { return payload; } + + //------------------------------------------------------------------------------------------------- + public void setCode(final String code) { this.code = code; } + public void setContentType(final String contentType) { this.contentType = contentType; } + public void setPayload(final Object payload) { this.payload = payload; } +} diff --git a/datamanager/src/main/java/eu/arrowhead/core/datamanager/DataManagerController.java b/datamanager/src/main/java/eu/arrowhead/core/datamanager/DataManagerController.java index 454c6991e..488a09d00 100644 --- a/datamanager/src/main/java/eu/arrowhead/core/datamanager/DataManagerController.java +++ b/datamanager/src/main/java/eu/arrowhead/core/datamanager/DataManagerController.java @@ -334,7 +334,7 @@ public class DataManagerController { @ApiResponse(code = HttpStatus.SC_INTERNAL_SERVER_ERROR, message = CoreCommonConstants.SWAGGER_HTTP_500_MESSAGE) }) @GetMapping(value= CommonConstants.OP_DATAMANAGER_PROXY + "/{systemName}/{serviceName}", produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody public Vector proxyServiceGet(@PathVariable(value="systemName", required=true) final String systemName, + @ResponseBody public List proxyServiceGet(@PathVariable(value="systemName", required=true) final String systemName, @PathVariable(value="serviceName", required=true) final String serviceName) { if (Utilities.isEmpty(systemName) || Utilities.isEmpty(serviceName)) { throw new InvalidParameterException(OP_NOT_VALID_ERROR_MESSAGE, HttpStatus.SC_BAD_REQUEST, CommonConstants.OP_DATAMANAGER_PROXY); diff --git a/datamanager/src/main/java/eu/arrowhead/core/datamanager/HistorianWSHandler.java b/datamanager/src/main/java/eu/arrowhead/core/datamanager/HistorianWSHandler.java index a55147747..156985518 100644 --- a/datamanager/src/main/java/eu/arrowhead/core/datamanager/HistorianWSHandler.java +++ b/datamanager/src/main/java/eu/arrowhead/core/datamanager/HistorianWSHandler.java @@ -54,7 +54,7 @@ public class HistorianWSHandler extends TextWebSocketHandler { @Autowired private DataManagerDriver dataManagerDriver; - @Autowired + @Autowired(required = false) private DatamanagerACLFilter dataManagerACLFilter; //================================================================================================= @@ -96,7 +96,9 @@ protected void handleTextMessage(final WebSocketSession session, final TextMessa payload = message.getPayload(); logger.debug("Got message for {}/{}", systemName, serviceName); - final boolean authorized = dataManagerACLFilter.checkRequest(CN, DatamanagerACLFilter.ACL_METHOD_PUT, CommonConstants.DATAMANAGER_URI + CommonConstants.OP_DATAMANAGER_HISTORIAN + "/ws/" + attributes.get(WSConstants.SYSTEM_ID) + "/" + + boolean authorized = true; + if (dataManagerACLFilter != null) + authorized = dataManagerACLFilter.checkRequest(CN, DatamanagerACLFilter.ACL_METHOD_PUT, CommonConstants.DATAMANAGER_URI + CommonConstants.OP_DATAMANAGER_HISTORIAN + "/ws/" + attributes.get(WSConstants.SYSTEM_ID) + "/" + attributes.get(WSConstants.SERVICE_ID)); if (authorized) { final Vector sml = gson.fromJson(payload, new TypeToken>(){}.getType()); diff --git a/datamanager/src/main/java/eu/arrowhead/core/datamanager/service/ProxyElement.java b/datamanager/src/main/java/eu/arrowhead/core/datamanager/service/ProxyElement.java index 07a0c7a6d..526303f74 100644 --- a/datamanager/src/main/java/eu/arrowhead/core/datamanager/service/ProxyElement.java +++ b/datamanager/src/main/java/eu/arrowhead/core/datamanager/service/ProxyElement.java @@ -13,14 +13,14 @@ ********************************************************************************/ package eu.arrowhead.core.datamanager.service; -import java.util.Vector; +import java.util.List; import eu.arrowhead.common.dto.shared.SenML; public class ProxyElement { private String systemName = null; private String serviceName = null; - private Vector message = null; + private List message = null; public ProxyElement(final String systemName, final String serviceName) { @@ -30,7 +30,7 @@ public ProxyElement(final String systemName, final String serviceName) { } - public ProxyElement(final String systemName, final String serviceName, final Vector message) { + public ProxyElement(final String systemName, final String serviceName, final List message) { this.systemName = systemName; this.serviceName = serviceName; this.message = message; @@ -42,8 +42,8 @@ public ProxyElement(final String systemName, final String serviceName, final Vec public String getServiceName() {return serviceName; }; public void setServiceName(final String serviceName) {this.serviceName = serviceName; }; - public Vector getMessage() {return message; }; - public void setMessage(final Vector message) {this.message = message; }; + public List getMessage() {return message; }; + public void setMessage(final List message) {this.message = message; }; } diff --git a/deviceregistry/.project b/deviceregistry/.project index 41202b7c9..050b930f4 100644 --- a/deviceregistry/.project +++ b/deviceregistry/.project @@ -10,6 +10,11 @@ + + org.springframework.ide.eclipse.boot.validation.springbootbuilder + + + org.eclipse.m2e.core.maven2Builder diff --git a/docker/initSQL.sh b/docker/initSQL.sh deleted file mode 120000 index 011a494ef..000000000 --- a/docker/initSQL.sh +++ /dev/null @@ -1 +0,0 @@ -../scripts/initSQL.sh \ No newline at end of file diff --git a/docker/initSQL.sh b/docker/initSQL.sh new file mode 100644 index 000000000..c45918594 --- /dev/null +++ b/docker/initSQL.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +rm -rf sql + +mkdir sql +cd sql +wget https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/create_empty_arrowhead_db.sql +sed -i 's/source /& docker-entrypoint-initdb.d\/privileges\//g' create_empty_arrowhead_db.sql + +mkdir privileges +cd privileges +wget https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/authorization_privileges.sql +wget https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/certificate_authority_privileges.sql +wget https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/choreographer_privileges.sql +wget https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/create_arrowhead_tables.sql +wget https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/device_registry_privileges.sql +wget https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/event_handler_privileges.sql +wget https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/gatekeeper_privileges.sql +wget https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/gateway_privileges.sql +wget https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/onboarding_controller_privileges.sql +wget https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/orchestrator_privileges.sql +wget https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/qos_monitor_privileges.sql +wget https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/service_registry_privileges.sql +wget https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/system_registry_privileges.sql +wget https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/translator_privileges.sql +wget https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/datamanager_privileges.sql +wget https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/configuration_privileges.sql +wget https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/plant_description_engine_privileges.sql diff --git a/docker/initSQL_macOS.sh b/docker/initSQL_macOS.sh new file mode 100755 index 000000000..cdb94ba9d --- /dev/null +++ b/docker/initSQL_macOS.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +rm -rf sql + +mkdir sql +cd sql +curl -O https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/create_empty_arrowhead_db.sql +sed -i'.backup' -e 's/source /& docker-entrypoint-initdb.d\/privileges\//g' create_empty_arrowhead_db.sql + +mkdir privileges +cd privileges +curl -O https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/authorization_privileges.sql +curl -O https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/certificate_authority_privileges.sql +curl -O https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/choreographer_privileges.sql +curl -O https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/create_arrowhead_tables.sql +curl -O https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/device_registry_privileges.sql +curl -O https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/event_handler_privileges.sql +curl -O https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/gatekeeper_privileges.sql +curl -O https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/gateway_privileges.sql +curl -O https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/onboarding_controller_privileges.sql +curl -O https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/orchestrator_privileges.sql +curl -O https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/qos_monitor_privileges.sql +curl -O https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/service_registry_privileges.sql +curl -O https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/system_registry_privileges.sql +curl -O https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/translator_privileges.sql +curl -O https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/datamanager_privileges.sql +curl -O https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/configuration_privileges.sql +curl -O https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/plant_description_engine_privileges.sql diff --git a/documentation/certificates/Certificate Management for MQTT with Eclipse Mosquitto.docx b/documentation/certificates/Certificate Management for MQTT with Eclipse Mosquitto.docx new file mode 100644 index 000000000..9d83fe4e6 Binary files /dev/null and b/documentation/certificates/Certificate Management for MQTT with Eclipse Mosquitto.docx differ diff --git a/documentation/certificates/Certificate Management for MQTT with Eclipse Mosquitto.pdf b/documentation/certificates/Certificate Management for MQTT with Eclipse Mosquitto.pdf new file mode 100644 index 000000000..30dd644ae Binary files /dev/null and b/documentation/certificates/Certificate Management for MQTT with Eclipse Mosquitto.pdf differ diff --git a/hawkbit-configuration-manager/.gitignore b/hawkbit-configuration-manager/.gitignore new file mode 100644 index 000000000..17be33ce5 --- /dev/null +++ b/hawkbit-configuration-manager/.gitignore @@ -0,0 +1,10 @@ +# Ignore Maven build output directory +target + +# Custom files +.settings +.classpath +bin +.project +lombok.config +.vscode \ No newline at end of file diff --git a/hawkbit-configuration-manager/Dockerfile b/hawkbit-configuration-manager/Dockerfile new file mode 100644 index 000000000..9cfcd3d09 --- /dev/null +++ b/hawkbit-configuration-manager/Dockerfile @@ -0,0 +1,21 @@ +#******************************************************************************* +# Copyright (c) 2020 Bosch.IO GmbH[ and others] + +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License 2.0 which is available at +# http://www.eclipse.org/legal/epl-2.0. + +# SPDX-License-Identifier: EPL-2.0 +#******************************************************************************* + +# Use jre 11 from openjdk +FROM openjdk:11-jre-slim + +# Execute application under a non root user for improved security +RUN adduser --system --group spring +USER spring:spring + +# Include the fat-jar and run it in the docker container +ARG JAR_FILE=target/arrowhead-hawkbit-configuration-manager-4.4.0.jar +COPY --chown=spring:spring ${JAR_FILE} /home/spring/app.jar +ENTRYPOINT ["java","-jar","/home/spring/app.jar"] \ No newline at end of file diff --git a/hawkbit-configuration-manager/README.md b/hawkbit-configuration-manager/README.md new file mode 100644 index 000000000..e67d13110 --- /dev/null +++ b/hawkbit-configuration-manager/README.md @@ -0,0 +1,69 @@ +# HawkBit Configuration Manager +## Introduction +We identified [Eclipse hawkBit](https://www.eclipse.org/hawkbit/) to be a very good fit for an integration with the Arrowhead framework. +The wrapper code in this directory enables to integrate Eclipse hawkBit with the Arrowhead mandatory core systems and function as a configuration manager. +Eclipse hawkBit allows administrators of distributed systems, consisting of constrained, but also more powerful devices, such as contollers or gateways, to manage rollouts of configuration changes and software updates. +The featureset comprises the creation of cascading rollouts of consecutive device groups and the definition of error thresholds triggering emergency shutdowns of a rollout. + +Eclipse hawkBit consists of the following APIs and interfaces: +- Direct Device Integration (DDI): The DDI is a REST-based API that allows devices to frequently request hawkBit for configuration/software updates (polling). +- Device Management Federation (DMF): As the DDI only supports HTTP, it is necessary to enable developers to integrate new protocol implementations, as not every device includes an HTTP stack. The DDI therefore enables proprietary protocols and most importantly allows the integration of publish/subscribe based patterns, which are necessary to guarantee a faster rollout of configuration files and are also more efficient. +- Management UI: The Management UI allows users to manage their devices, plan and create new software/configuration updates, and deal with potential problems during the rollout +- Management API: the Management API allows the integration of other business solutions to integrate with the management interface of hawkBit + +This is the first version of the Eclipse hawkBit wrapper, which does support the basic hawkBit functionality, but should not be considered stable. +‚‚‚ +## Architecture‚‚ +The system consists of a Spring Boot application, which is responsible for managing connections with +- the Arrowhead systems, +- the [hawkBit DMF API](https://www.eclipse.org/hawkbit/apis/dmf_api/), +- and the devices. + +Currently, the configuration manager wrapper only supports devices that are able to communicate via WebSocket connection. +However, future implementations might integrate other protocols by adding other implementations for the Sping Boot service which interfaces the devices. + +## Authentication +The configuration manager uses a client certificate to connect to the Arrowhead Authorization system in order to retrieve the public key that is used to sign JWTs. +Therefore the configuration manager needs its own client certificate, as desribed [here](https://github.com/eclipse-arrowhead/core-java-spring#certificates). +To generate new credentials you can use the `generate-certificates.sh` script in the `certificates` directory. +You will need to modify the following properties: +- `CA_KEYSTORE` - the path to the keystore containing the cloud certificate/keys +- `CA_KEYSTORE_PASS` - password for the keystore +- `CLIENT_ID` - this is the ID/name of the core system (should comply with the Arrowhead CN naming conventions, e.g.: `hawkbitconfigurationmanager`) +- `CLOUD_DOMAIN` - the CN of the cloud certificate (results in the CN of the client certificate, when combined with the `CLIENT_ID`, e.g.: `example.corp.arrowhead.eu`) +- `CLIENT_KEYSTORE_PASS` - the password of the keystore +Also make sure to modify the `openssl.cnf` file and enter the correct subject alternative names in the `[alt_names]` section (e.g.: `IP.1 = 127.0.0.1` or `DNS.1 = example.com`). + +In order to work properly, you need to acquire the cloud certificate. +Change the file name in the `generate-certificates.sh` script using the `CA_KEYSTORE` variable and the corresponding password using the `CA_KEYSTORE_PASS` variable. +The resulting PKCS12 keystore will be named `${CLIENT_ID}.full.p12`. +The CN of the generated certificates is the combined value of `CLIENT_ID.CLOUD_DOMAIN`, e.g: configuration.example.corp.arrowhead.eu. +Don't forget to save the corresponding password, which will be printed after successful generation. +**Do not push any certificates!** + +There also exists a truststore, which is used by the configuration manager wrapper to validate the certificates of the Arrowhead core systems. +This truststore (file name `truststore.p12`) must be generated by using the cloud certificate, and then be placed in the `certificates` directory. +However, you can also modify the path in the `application.yml` +Arrowhead uses PKCS12 files to store certificates and keys. +For more information on how to generate the trust store see the [certificate section](https://github.com/arrowhead-f/core-java-spring#certificates) of the Arrowhead Framework documentation. + +## Compatibility +Currently, the following functionality of the hawkBit DMF API (version 0.3.0M6) is implemented and available to the user via WebSockets: + +| DMF API message type | supported | current version | +| --------------------------------- | ------------- | --------------- | +| THING_CREATED | ☑ | ☑ +| THING_REMOVED | ☑ | ☑ +| UPDATE_ATTRIBUTES | | +| UPDATE_ACTION_STATUS | ☑ | ☑ +| PING | | +| CANCEL_DOWNLOAD | ☑ | ☑ +| DOWNLOAD_AND_INSTALL or DOWNLOAD | ☑ | ☑ +| MULTI_ACTION | | +| THING_DELETED | ☑ | ☑ +| PING_RESPONSE | | + +## Deployment +To use the configuration manager, you need a running instance of Eclipse hawkBit. +You can use the [hawkBit image from Dockerhub](https://hub.docker.com/r/hawkbit/hawkbit-update-server) to deploy an instance and then wire up the configuration manager wrapper with it. +As part of this repository, there exists a `Dockerfile` that can be used to either build and run a container with the wrapper locally, or build and push the image into a private container registry. diff --git a/hawkbit-configuration-manager/certificates/.gitignore b/hawkbit-configuration-manager/certificates/.gitignore new file mode 100644 index 000000000..003240794 --- /dev/null +++ b/hawkbit-configuration-manager/certificates/.gitignore @@ -0,0 +1,3 @@ +*.key +*.pem +*.csr \ No newline at end of file diff --git a/hawkbit-configuration-manager/certificates/example.corp.arrowhead.eu.p12 b/hawkbit-configuration-manager/certificates/example.corp.arrowhead.eu.p12 new file mode 100644 index 000000000..a3bf1ff6e Binary files /dev/null and b/hawkbit-configuration-manager/certificates/example.corp.arrowhead.eu.p12 differ diff --git a/hawkbit-configuration-manager/certificates/generate-certificates.sh b/hawkbit-configuration-manager/certificates/generate-certificates.sh new file mode 100644 index 000000000..24b608b58 --- /dev/null +++ b/hawkbit-configuration-manager/certificates/generate-certificates.sh @@ -0,0 +1,52 @@ +#!/bin/bash +#******************************************************************************* +# Copyright (c) 2020 Bosch.IO GmbH[ and others] + +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License 2.0 which is available at +# http://www.eclipse.org/legal/epl-2.0. + +# SPDX-License-Identifier: EPL-2.0 +#******************************************************************************* + +CLOUD_DOMAIN="example.corp.arrowhead.eu" +CLIENT_ID="configuration" +CLIENT_SERIAL=1 +CA_KEYSTORE="example.corp.arrowhead.eu.p12" +CA_KEYSTORE_PASS="aeloim9do8hoe7AaWo7johpe" +CA_CERT_NAME="ca-cert.pem" +CA_KEY_NAME="ca-key.key" +VALIDITY=3650 + +# Use the password from application.yaml +CLIENT_KEYSTORE_PASS="conf-test-pw" +KEY_PASS=${CLIENT_KEYSTORE_PASS} + + +# Generate rsa key +openssl genrsa -aes256 -passout pass:${KEY_PASS} -out ${CLIENT_ID}.pass.key 2048 +openssl rsa -passin pass:${KEY_PASS} -in ${CLIENT_ID}.pass.key -out ${CLIENT_ID}.key +rm ${CLIENT_ID}.pass.key + +# generate CSR +openssl req -new -key ${CLIENT_ID}.key -out ${CLIENT_ID}.csr -subj "/C=DE/ST=Berlin/L=Berlin/O=Arrowhead/OU=AHT/CN=${CLIENT_ID}.${CLOUD_DOMAIN}" -config openssl.cnf + +# Extract private key and certificate from CA +openssl pkcs12 -password pass:${CA_KEYSTORE_PASS} -in ${CA_KEYSTORE} -nodes -nocerts -out ${CA_KEY_NAME} +openssl pkcs12 -password pass:${CA_KEYSTORE_PASS} -in ${CA_KEYSTORE} -nodes -clcerts -out ${CA_CERT_NAME} + +# sign new certificate using CSR +openssl x509 -req -days ${VALIDITY} -in ${CLIENT_ID}.csr -CA ${CA_CERT_NAME} -CAkey ${CA_KEY_NAME} -set_serial ${CLIENT_SERIAL} -extfile openssl.cnf -extensions req_ext -out ${CLIENT_ID}.pem + +# combine to create PKCS +openssl pkcs12 -export -out ${CLIENT_ID}.full.p12 -name "${CLIENT_ID}.${CLOUD_DOMAIN}" -inkey ${CLIENT_ID}.key -in ${CLIENT_ID}.pem -certfile ${CA_CERT_NAME} -password pass:${CLIENT_KEYSTORE_PASS} + +echo "Successfully generated client keystore, use the following password to open:" +echo ${CLIENT_KEYSTORE_PASS} + +# delete byproducts +rm ${CA_KEY_NAME} +rm ${CA_CERT_NAME} +rm ${CLIENT_ID}.pem +rm ${CLIENT_ID}.csr +rm ${CLIENT_ID}.key \ No newline at end of file diff --git a/hawkbit-configuration-manager/certificates/openssl.cnf b/hawkbit-configuration-manager/certificates/openssl.cnf new file mode 100644 index 000000000..1c5865bd7 --- /dev/null +++ b/hawkbit-configuration-manager/certificates/openssl.cnf @@ -0,0 +1,13 @@ +[req] +req_extensions = req_ext +distinguished_name = req_distinguished_name + +[req_distinguished_name] + +[ req_ext ] +basicConstraints = CA:FALSE +keyUsage = digitalSignature, keyEncipherment +subjectAltName = @alt_names + +[alt_names] +IP.1 = \ No newline at end of file diff --git a/hawkbit-configuration-manager/pom.xml b/hawkbit-configuration-manager/pom.xml new file mode 100644 index 000000000..e0f9b8052 --- /dev/null +++ b/hawkbit-configuration-manager/pom.xml @@ -0,0 +1,330 @@ + + + + 4.0.0 + + eu.arrowhead + ${revision} + + arrowhead-hawkbit-configuration-manager + jar + Arrowhead HawkBit Configuration Manager + An Arrowhead configuration manager system based on Eclipse HawkBit + + + 4.4.0 + ${base.version} + 2.4.0 + 2.15.0 + 11 + 11 + 11 + 3.1.0 + + + + + org.projectlombok + lombok + 1.18.16 + provided + + + + org.springframework.boot + ${spring.boot.version} + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-logging + + + + + org.springframework.boot + ${spring.boot.version} + spring-boot-starter-webflux + + + org.springframework.boot + ${spring.boot.version} + spring-boot-starter-websocket + + + org.springframework.boot + ${spring.boot.version} + spring-boot-starter-security + + + org.springframework.boot + ${spring.boot.version} + spring-boot-starter-log4j2 + + + org.springframework.boot + ${spring.boot.version} + spring-boot-starter-cache + + + org.springframework.boot + ${spring.boot.version} + spring-boot-starter-validation + + + + javax.cache + cache-api + 1.1.1 + + + javax.annotation + javax.annotation-api + 1.3.2 + + + com.rabbitmq + amqp-client + 5.9.0 + + + org.bitbucket.b_c + jose4j + 0.6.5 + + + org.yaml + snakeyaml + 1.27 + + + + org.apache.logging.log4j + log4j-slf4j-impl + ${log4j2.version} + compile + + + org.apache.logging.log4j + log4j-core + ${log4j2.version} + compile + + + org.apache.logging.log4j + log4j-jul + ${log4j2.version} + compile + + + org.slf4j + jul-to-slf4j + 1.7.32 + compile + + + + org.springframework.boot + ${spring.boot.version} + spring-boot-starter-test + test + + + org.springframework.security + spring-security-test + 5.3.3.RELEASE + test + + + org.mockito + mockito-core + 3.5.11 + test + + + org.mock-server + mockserver-netty + 5.11.1 + test + + + org.mock-server + mockserver-junit-jupiter + 5.11.1 + test + + + io.projectreactor + reactor-test + test + 3.4.2 + + + org.junit.jupiter + junit-jupiter + 5.6.2 + test + + + com.squareup.okhttp3 + mockwebserver + 4.7.2 + test + + + com.squareup.okhttp3 + okhttp + 4.7.2 + test + + + com.github.fridujo + rabbitmq-mock + 1.1.0 + test + + + org.awaitility + awaitility + 4.0.3 + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + + repackage + + + + eu.arrowhead.core.hbconfmgr.ConfigurationSystemApplication + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.2 + + + *.java + + + + + maven-resources-plugin + ${mvn.resources.version} + + + copy-resources + validate + + copy-resources + + + ${basedir}/target + + + src/main/resources + + application.properties + certificates/ + log4j2.xml + + + + + + + + + jdeb + org.vafer + 1.8 + + + package + + jdeb + + + true + [YYMMddHHmm].${git.commit.id.abbrev} + target/${project.artifactId}_${revision}.deb + true + ${basedir}/src/deb/control + + + file + ${project.build.directory}/${project.build.finalName}.jar + + perm + /usr/share/arrowhead/hawkbitconfigurationmanager + 755 + + + + + link + /usr/share/arrowhead/hawkbitconfigurationmanager/${project.artifactId}.jar + /usr/share/arrowhead/hawkbitconfigurationmanager/${project.build.finalName}.jar + + + + file + ${project.build.directory}/log4j2.xml + + perm + /etc/arrowhead/systems/hawkbitconfigurationmanager + 644 + + + + + file + ${project.build.directory}/application.properties + + perm + /etc/arrowhead/systems/hawkbitconfigurationmanager + 644 + + + + + link + /usr/share/arrowhead/hawkbitconfigurationmanager/application.properties + /etc/arrowhead/systems/hawkbitconfigurationmanager/application.properties + + + + file + ${project.basedir}/src/deb/arrowhead-hawkbit-configuration-manager.service + + perm + /etc/systemd/system + 644 + + + + + + + + + + \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/deb/arrowhead-hawkbit-configuration-manager.service b/hawkbit-configuration-manager/src/deb/arrowhead-hawkbit-configuration-manager.service new file mode 100644 index 000000000..668b5c228 --- /dev/null +++ b/hawkbit-configuration-manager/src/deb/arrowhead-hawkbit-configuration-manager.service @@ -0,0 +1,16 @@ +[Unit] +Description=arrowhead-hawkbitconfigurationmanager +After=network.target + +[Service] +WorkingDirectory=/etc/arrowhead/systems/hawkbitconfigurationmanager +ExecStart=/usr/bin/java -Dlog4j.configurationFile=file:/etc/arrowhead/systems/hawkbitconfigurationmanager/log4j2.xml -jar /usr/share/arrowhead/hawkbitconfigurationmanager/arrowhead-hawkbit-configuration-manager.jar +TimeoutStopSec=5 +Restart=on-failure +RestartSec=20s +Type=simple +User=arrowhead +Group=arrowhead + +[Install] +WantedBy=default.target diff --git a/hawkbit-configuration-manager/src/deb/control/config b/hawkbit-configuration-manager/src/deb/control/config new file mode 100644 index 000000000..ad205a4ff --- /dev/null +++ b/hawkbit-configuration-manager/src/deb/control/config @@ -0,0 +1,27 @@ +#!/bin/bash -e + +. /usr/share/debconf/confmodule + +if logname 2>/dev/null; then + USER=$(logname) + HOME=$(eval echo "~$USER") +else + USER="root" + HOME="/root" +fi + +# Get installation type +db_input high arrowhead-core-common/sr_host || true +db_input high arrowhead-core-common/provided_service_definition || true +db_input high arrowhead-core-common/provided_service_uri || true +db_input high arrowhead-core-common/provided_service_version || true +db_input high arrowhead-core-common/provided_service_interface || true +db_input high arrowhead-core-common/hawkbit_host || true +db_input high arrowhead-core-common/hawkbit_port || true +db_input high arrowhead-core-common/hawkbit_username || true +db_input high arrowhead-core-common/hawkbit_password || true +db_input high arrowhead-core-common/hawkbit_tenant || true + +# Run dialogs +db_go || true + diff --git a/hawkbit-configuration-manager/src/deb/control/control b/hawkbit-configuration-manager/src/deb/control/control new file mode 100644 index 000000000..1566a4697 --- /dev/null +++ b/hawkbit-configuration-manager/src/deb/control/control @@ -0,0 +1,10 @@ +Package: arrowhead-hawkbit-configuration-manager +Version: [[version]] +Section: contrib/java +Priority: optional +Architecture: all +Maintainer: Lukas Römer +Homepage: http://www.arrowhead.eu +Description: Arrowhead HawkBit Configuration Manager System +Distribution: development +Depends: java-runtime-headless, arrowhead-core-common diff --git a/hawkbit-configuration-manager/src/deb/control/postinst b/hawkbit-configuration-manager/src/deb/control/postinst new file mode 100644 index 000000000..38264a534 --- /dev/null +++ b/hawkbit-configuration-manager/src/deb/control/postinst @@ -0,0 +1,115 @@ +#!/bin/bash +# postinst script for arrowhead-hawkbitconfigurationmanager +# +# see: dh_installdeb(1) + +set -e + +. /usr/share/debconf/confmodule + +SYSTEM_NAME="hawkbitconfigurationmanager" +PKG_NAME="arrowhead-hawkbit-configuration-manager" + +# summary of how this script can be called: +# * `configure' +# * `abort-upgrade' +# * `abort-remove' `in-favour' +# +# * `abort-remove' +# * `abort-deconfigure' `in-favour' +# `removing' +# +# for details, see https://www.debian.org/doc/debian-policy/ or +# the debian-policy package + +case "$1" in + configure) + dos2unix -q /etc/systemd/system/${PKG_NAME}.service + + . /usr/share/arrowhead/conf/ahconf.sh + + SYSTEM_DIR="${AH_SYSTEMS_DIR}/${SYSTEM_NAME}" + + systems="$(ah_get_conf_prop systems)" + if [[ "${systems}" != *"${SYSTEM_NAME}"* ]]; then + systems="${systems}${SYSTEM_NAME} " + fi + ah_set_conf_prop systems "${systems}" + + if [ ! -d "${SYSTEM_DIR}" ]; then + mkdir -p ${SYSTEM_DIR} + fi + + domain_name=${AH_DOMAIN_NAME} + if [ -z ${domain_name} ]; then + domain_name=${OWN_IP} + fi + + ah_cert_signed_system ${SYSTEM_NAME} + + # modify log config and application.properties + + ah_transform_log_file ${SYSTEM_DIR} + + db_get arrowhead-core-common/sr_host; sr_host=$RET || true + + if [ -z ${sr_host} ]; then + sr_host=${OWN_IP} + fi + + db_get arrowhead-core-common/provided_service_definition; provided_service_definition=$RET || true + db_get arrowhead-core-common/provided_service_uri; provided_service_uri=$RET || true + db_get arrowhead-core-common/provided_service_version; provided_service_version=$RET || true + db_get arrowhead-core-common/provided_service_interface; provided_service_interface=$RET || true + + db_get arrowhead-core-common/hawkbit_host; hawkbit_host=$RET || true + db_get arrowhead-core-common/hawkbit_port; hawkbit_port=$RET || true + db_get arrowhead-core-common/hawkbit_username; hawkbit_username=$RET || true + db_get arrowhead-core-common/hawkbit_password; hawkbit_password=$RET || true + db_unregister arrowhead-core-common/hawkbit_password + db_get arrowhead-core-common/hawkbit_tenant; hawkbit_tenant=$RET || true + + mv ${SYSTEM_DIR}/application.properties ${SYSTEM_DIR}/application.properties.orig + cat ${SYSTEM_DIR}/application.properties.orig | \ + sed -r -e "\#^.*system\.address=#s#.*\$#system.address=${domain_name}#" \ + -e "\#^.*sr_address=#s#.*\$#sr_address=${sr_host}#" \ + -e "\#^.*system\.providedServiceInterface=#s#.*\$#system.providedServiceInterface=${provided_service_interface}#" \ + -e "\#^.*system\.providedServiceDefinition=#s#.*\$#system.providedServiceDefinition=${provided_service_definition}#" \ + -e "\#^.*system\.providedServiceUri=#s#.*\$#system.providedServiceUri=${provided_service_uri}#" \ + -e "\#^.*system\.providedServiceVersion=#s#.*\$#system.providedServiceVersion=${provided_service_version}#" \ + -e "\#^.*hawkbit\.host=#s#.*\$#hawkbit.host=${hawkbit_host}#" \ + -e "\#^.*hawkbit\.port=#s#.*\$#hawkbit.port=${hawkbit_port}#" \ + -e "\#^.*hawkbit\.username=#s#.*\$#hawkbit.username=${hawkbit_username}#" \ + -e "\#^.*hawkbit\.password=#s#.*\$#hawkbit.password=${hawkbit_password}#" \ + -e "\#^.*hawkbit\.tenant=#s#.*\$#hawkbit.tenant=${hawkbit_tenant}#" \ + -e "\#^.*server\.ssl\.key-store=#s#.*\$#server.ssl.key-store=file:${SYSTEM_DIR}/${SYSTEM_NAME}.p12#" \ + -e "\#^.*server\.ssl\.key-store-password=#s#.*\$#server.ssl.key-store-password=${AH_PASS_CERT}#" \ + -e "\#^.*server\.ssl\.key-password=#s#.*\$#server.ssl.key-password=${AH_PASS_CERT}#" \ + -e "\#^.*server\.ssl\.trust-store=#s#.*\$#server.ssl.trust-store=file:${AH_CONF_DIR}/truststore.p12#" \ + -e "\#^.*server\.ssl\.trust-store-password=#s#.*\$#server.ssl.trust-store-password=${AH_PASS_CERT}#" > ${SYSTEM_DIR}/application.properties + + rm ${SYSTEM_DIR}/application.properties.orig + chown arrowhead:arrowhead ${SYSTEM_DIR}/application.properties + chmod 640 ${SYSTEM_DIR}/application.properties + + echo "Starting ${PKG_NAME}..." >&2 + systemctl daemon-reload + systemctl enable ${PKG_NAME}.service + systemctl start ${PKG_NAME}.service + ;; + + abort-upgrade|abort-remove|abort-deconfigure) + ;; + + *) + echo "postinst called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 diff --git a/hawkbit-configuration-manager/src/deb/control/postrm b/hawkbit-configuration-manager/src/deb/control/postrm new file mode 100644 index 000000000..f635d210d --- /dev/null +++ b/hawkbit-configuration-manager/src/deb/control/postrm @@ -0,0 +1,56 @@ +#!/bin/bash +# postrm script for arrowhead-hawkbitconfigurationmanager +# +# see: dh_installdeb(1) + +set -e + +. /usr/share/debconf/confmodule + +SYSTEM_NAME="hawkbitconfigurationmanager" + +# summary of how this script can be called: +# * `remove' +# * `purge' +# * `upgrade' +# * `failed-upgrade' +# * `abort-install' +# * `abort-install' +# * `abort-upgrade' +# * `disappear' +# +# for details, see https://www.debian.org/doc/debian-policy/ or +# the debian-policy package + + +case "$1" in + purge) + AH_CONF_DIR="/etc/arrowhead" + AH_SYSTEMS_DIR="${AH_CONF_DIR}/systems" + SYSTEM_DIR="${AH_SYSTEMS_DIR}/${SYSTEM_NAME}" + + rm -f \ + /var/log/arrowhead/${SYSTEM_NAME}*.log \ + ${SYSTEM_DIR}/log4j2.xml \ + ${SYSTEM_DIR}/application.properties \ + ${SYSTEM_DIR}/${SYSTEM_NAME}.p12 \ + rmdir ${SYSTEM_DIR} 2>/dev/null || true + + db_purge + ;; + + remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear) + ;; + + *) + echo "postrm called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 diff --git a/hawkbit-configuration-manager/src/deb/control/preinst b/hawkbit-configuration-manager/src/deb/control/preinst new file mode 100644 index 000000000..5e21bb7bc --- /dev/null +++ b/hawkbit-configuration-manager/src/deb/control/preinst @@ -0,0 +1,35 @@ +#!/bin/bash +# preinst script for arrowhead-hawkbitconfigurationmanager +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +# * `install' +# * `install' +# * `upgrade' +# * `abort-upgrade' +# for details, see https://www.debian.org/doc/debian-policy/ or +# the debian-policy package + + +case "$1" in + install|upgrade) + ;; + + abort-upgrade) + ;; + + *) + echo "preinst called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 diff --git a/hawkbit-configuration-manager/src/deb/control/prerm b/hawkbit-configuration-manager/src/deb/control/prerm new file mode 100644 index 000000000..b76bdcdb2 --- /dev/null +++ b/hawkbit-configuration-manager/src/deb/control/prerm @@ -0,0 +1,45 @@ +#!/bin/bash +# prerm script for arrowhead-hawkbitconfigurationmanager +# +# see: dh_installdeb(1) + +set -e + +PKG_NAME="arrowhead-hawkbit-configuration-manager" + +# summary of how this script can be called: +# * `remove' +# * `upgrade' +# * `failed-upgrade' +# * `remove' `in-favour' +# * `deconfigure' `in-favour' +# `removing' +# +# for details, see https://www.debian.org/doc/debian-policy/ or +# the debian-policy package + + +case "$1" in + remove) + systemctl stop ${PKG_NAME}.service + systemctl disable ${PKG_NAME}.service + ;; + + upgrade|deconfigure) + ;; + + failed-upgrade) + ;; + + *) + echo "prerm called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/ConfigurationSystemApplication.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/ConfigurationSystemApplication.java new file mode 100644 index 000000000..3c7ead6f8 --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/ConfigurationSystemApplication.java @@ -0,0 +1,23 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class ConfigurationSystemApplication { + + public static void main(final String[] args) { + SpringApplication.run(ConfigurationSystemApplication.class); + } + +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/Constants.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/Constants.java new file mode 100644 index 000000000..1fa52a8db --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/Constants.java @@ -0,0 +1,44 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr; + +public class Constants { + + public static final String GUARD_BEAN = "guardBean"; + + public static final String HTTPS = "https"; + + public static final String APPLICATION_JSON = "application/json"; + + public static final String SSL_ENABLED = "${server.ssl.enabled:true}"; + public static final String KEYSTORE_TYPE = "${server.ssl.key-store-type:PKCS12}"; + public static final String KEYSTORE_PATH ="${server.ssl.key-store:}"; + public static final String KEYSTORE_PASSWORD = "${server.ssl.key-store-password:}"; + public static final String KEY_PASSWORD = "${server.ssl.key-password:}"; + public static final String KEY_ALIAS = "${server.ssl.key-alias:}"; + public static final String TRUSTSTORE_PATH = "${server.ssl.trust-store:}"; + public static final String TRUSTSTORE_PASSWORD = "${server.ssl.trust-store-password:}"; + + public static final String SERVICE_REGISTRY_ADDRESS = "${sr_address:localhost}"; + public static final String SERVICE_REGISTRY_PORT = "${sr_port:8443}"; + + public static final String HAWKBIT_HOST = "${hawkbit.host:localhost}"; + public static final String HAWKBIT_PORT = "${hawkbit.port:8080}"; + public static final String HAWKBIT_USER = "${hawkbit.username:}"; + public static final String HAWKBIT_PASSWORD = "${hawkbit.password:}"; + public static final String HAWKBIT_TENANT = "${hawkbit.tenant:DEFAULT}"; + + public static final String CORE_SERVICE_AUTH_PUBLIC_KEY = "auth-public-key"; + + private Constants() { + throw new UnsupportedOperationException(); + } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/Guard.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/Guard.java new file mode 100644 index 000000000..9ddff2420 --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/Guard.java @@ -0,0 +1,38 @@ +/******************************************************************************** + * Copyright (c) 2021 AITIA + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * AITIA - implementation + * Arrowhead Consortia - conceptualization + ********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr; + +import java.util.Optional; +import java.util.ServiceConfigurationError; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class Guard { + + @Value(Constants.SSL_ENABLED) + private boolean sslEnabled; + + @Bean + public Optional guardBean() { + if (!sslEnabled) { + throw new ServiceConfigurationError("HawkBit Configuration Manager only supports SECURE mode. Please set server.ssl.enabled to true in the application.properties file."); + } + + return Optional.empty(); + } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/Initialization.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/Initialization.java new file mode 100644 index 000000000..9cd8e20f0 --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/Initialization.java @@ -0,0 +1,114 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr; + +import java.io.IOException; +import java.security.PublicKey; +import java.util.Base64; +import java.util.Collections; + +import javax.annotation.PreDestroy; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.client.WebClientResponseException; + +import eu.arrowhead.core.hbconfmgr.arrowhead.ArrowheadServiceRegistryClient; +import eu.arrowhead.core.hbconfmgr.arrowhead.model.request.ServiceRegistryRequestDTO; +import eu.arrowhead.core.hbconfmgr.arrowhead.model.request.SystemRequestDTO; +import eu.arrowhead.core.hbconfmgr.hawkbit.HawkbitDmfConsumer; +import eu.arrowhead.core.hbconfmgr.properties.SystemProperties; +import lombok.extern.log4j.Log4j2; + +/** + * This class runs during the startup of the configuration system. It contains + * all logic for initialization, like the registration of the configuration + * system in Arrowhead. + */ +@Log4j2 +@Component +public class Initialization implements ApplicationRunner { + + private final HawkbitDmfConsumer hawkbitDmfConsumer; + private final ArrowheadServiceRegistryClient arrowheadServiceRegistryClient; + private final SystemProperties systemProperties; + private final PublicKey publicKey; + + @Autowired + public Initialization(final ArrowheadServiceRegistryClient arrowheadServiceRegistryClient, final HawkbitDmfConsumer hawkbitDmfConsumer, final SystemProperties systemProperties, final PublicKey publicKey) { + this.arrowheadServiceRegistryClient = arrowheadServiceRegistryClient; + this.hawkbitDmfConsumer = hawkbitDmfConsumer; + this.systemProperties = systemProperties; + this.publicKey = publicKey; + } + + @Override + public void run(final ApplicationArguments args) throws Exception { + registerOwnSystemInArrowhead(); + connectToHawkBit(); + log.info("Arrowhead HawkBit Configuration Manager initialization done."); + } + + @PreDestroy + public void destroy() { + try { + this.arrowheadServiceRegistryClient.unregisterService(this.systemProperties.getProvidedServiceDefinition(), this.systemProperties.getName(), this.systemProperties.getAddress(), this.systemProperties.getPort(), + this.systemProperties.getProvidedServiceUri()); + } catch (final WebClientResponseException e) { + log.error("Error during unregistration of own system in Arrowhead", e); + } + } + + private void registerOwnSystemInArrowhead() { + final String publicKeyString = Base64.getEncoder().encodeToString(this.publicKey.getEncoded()); + + final ServiceRegistryRequestDTO requestDTO = ServiceRegistryRequestDTO.builder() + .serviceDefinition(this.systemProperties.getProvidedServiceDefinition()) + .providerSystem(SystemRequestDTO.builder() + .systemName(this.systemProperties.getName()) + .address(this.systemProperties.getAddress()) + .port(this.systemProperties.getPort()) + .authenticationInfo(publicKeyString) + .build()) + .serviceUri(this.systemProperties.getProvidedServiceUri()) + .secure(ServiceRegistryRequestDTO.SecurityLevel.TOKEN) + .version(this.systemProperties.getProvidedServiceVersion()) + .interfaces(Collections.singletonList(this.systemProperties.getProvidedServiceInterface())) + .build(); + + try { + log.info("Registering own system in Arrowhead"); + this.arrowheadServiceRegistryClient.registerService(requestDTO); + } catch (final WebClientResponseException e) { + if (HttpStatus.BAD_REQUEST.equals(e.getStatusCode())) { + log.warn("Own system is already registered in Arrowhead"); + this.arrowheadServiceRegistryClient.unregisterService(this.systemProperties.getProvidedServiceDefinition(), this.systemProperties.getName(), this.systemProperties.getAddress(), this.systemProperties.getPort(), + this.systemProperties.getProvidedServiceUri()); + this.arrowheadServiceRegistryClient.registerService(requestDTO); + } else { + log.error("Error during registration of own system in Arrowhead", e); + } + } catch (final Exception e) { + log.error("Error during registration of own system in Arrowhead", e); + } + } + + private void connectToHawkBit() { + try { + this.hawkbitDmfConsumer.subscribeToDownloadEvents(); + } catch (final IOException e) { + log.error("Could not subscribe to Hawkbit DMF API"); + } + } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/SSLProperties.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/SSLProperties.java new file mode 100644 index 000000000..97c5fca9e --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/SSLProperties.java @@ -0,0 +1,117 @@ +/******************************************************************************** + * Copyright (c) 2021 AITIA + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * AITIA - implementation + * Arrowhead Consortia - conceptualization + ********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr; + +import javax.annotation.PostConstruct; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.DependsOn; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.stereotype.Component; + +@Component +@DependsOn(Constants.GUARD_BEAN) +public class SSLProperties { + + //================================================================================================= + // members + + @Value(Constants.SSL_ENABLED) + private boolean sslEnabled; + + @Value(Constants.KEYSTORE_TYPE) + private String keyStoreType; + + @Value(Constants.KEYSTORE_PATH) + private String keyStorePath; + private Resource keyStore; + + @Value(Constants.KEYSTORE_PASSWORD) + private String keyStorePassword; + + @Value(Constants.KEY_ALIAS) + private String keyAlias; + + @Value(Constants.KEY_PASSWORD) + private String keyPassword; + + @Value(Constants.TRUSTSTORE_PATH) + private String trustStorePath; + private Resource trustStore; + + @Value(Constants.TRUSTSTORE_PASSWORD) + private String trustStorePassword; + + @Autowired + private ResourceLoader resourceLoader; + + //================================================================================================= + // methods + + //------------------------------------------------------------------------------------------------- + public boolean isSslEnabled() { return sslEnabled; } + public String getKeyStoreType() { return keyStoreType; } + public Resource getKeyStore() { return keyStore; } + public String getKeyStorePassword() { return keyStorePassword; } + public String getKeyAlias() { return keyAlias; } + public String getKeyPassword() { return keyPassword; } + public Resource getTrustStore() { return trustStore; } + public String getTrustStorePassword() { return trustStorePassword; } + + //------------------------------------------------------------------------------------------------- + @PostConstruct + private void validate() { + if (sslEnabled) { + if (isEmpty(keyStoreType)) { + throw new RuntimeException("keyStoreType is missing"); + } + + if (isEmpty(keyStorePath)) { + throw new RuntimeException("keyStorePath is missing"); + } else { + keyStore = resourceLoader.getResource(keyStorePath); + } + + if (isEmpty(keyStorePassword)) { + throw new RuntimeException("keyStorePassword is missing"); + } + + if (isEmpty(keyAlias)) { + throw new RuntimeException("keyAlias is missing"); + } + + if (isEmpty(keyPassword)) { + throw new RuntimeException("keyPassword is missing"); + } + + if (isEmpty(trustStorePath)) { + throw new RuntimeException("trustStorePath is missing"); + } else { + trustStore = resourceLoader.getResource(trustStorePath); + } + + if (isEmpty(trustStorePassword)) { + throw new RuntimeException("trustStorePassword is missing"); + } + } + } + + //------------------------------------------------------------------------------------------------- + private boolean isEmpty(final String str) { + return str == null || str.trim().isEmpty(); + } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/arrowhead/ArrowheadAuthorizationSystemClient.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/arrowhead/ArrowheadAuthorizationSystemClient.java new file mode 100644 index 000000000..8b73f8d37 --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/arrowhead/ArrowheadAuthorizationSystemClient.java @@ -0,0 +1,99 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.arrowhead; + +import org.springframework.http.client.reactive.ReactorClientHttpConnector; +import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.reactive.function.client.WebClientResponseException; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.netty.handler.ssl.SslContext; +import lombok.extern.log4j.Log4j2; +import reactor.netty.http.client.HttpClient; + + +/** + * Client class to directly interact with the arrowhead authorization system. + * Used solely to request its public key for JWT validation. + */ +@Log4j2 +public class ArrowheadAuthorizationSystemClient { + + private static final String URI_PUBLIC_KEY = "/authorization/publickey"; + + private final WebClient webClient; + private final String baseUrl; + + /** + * Initializes a new rest client to communicate with the Arrowhead Authorization System. + * Insecure (HTTP) and secure (HTTPS) communication is supported, depending on the baseUrl. + * + * @param baseUrl specifies the base url where arrowhead is available + */ + public ArrowheadAuthorizationSystemClient(final String baseUrl) { + log.debug("Initialize ArrowheadAuthorizationSystemClient with baseUrl {}", baseUrl); + + this.webClient = WebClient.create(baseUrl); + this.baseUrl = baseUrl; + } + + /** + * Initializes a new rest client to communicate with the Arrowhead Authorization System with customized SSL + * handling. Insecure (HTTP) and secure (HTTPS) communication is supported, depending on the baseUrl. + * + * @param baseUrl specifies the base url where arrowhead is available + * @param sslContext specifies the ssl context the web client should use + */ + public ArrowheadAuthorizationSystemClient(final String baseUrl, final SslContext sslContext) { + log.debug("Initialize ArrowheadAuthorizationSystemClient with baseUrl {} and customized sslContext", baseUrl); + + final HttpClient httpClient = HttpClient.create() + .secure(sslContextSpec -> sslContextSpec.sslContext(sslContext)); + this.webClient = WebClient.builder() + .baseUrl(baseUrl) + .clientConnector(new ReactorClientHttpConnector(httpClient)) + .build(); + this.baseUrl = baseUrl; + } + + /** + * Returns the public key of the Authorization core service as a (Base64 encoded) text. + *

This service is necessary for providers if they want to utilize the token based security. + * A more detailed description of this REST endpoint can be found in Github under the + * client endpoint description for + * getting the public key. + * + * @return a string containing the (Base64 encoded) public key + * @throws WebClientResponseException if the status code is 4xx or 5xx + */ + public String getPublicKey() throws WebClientResponseException { + log.debug("Start HTTP GET request against {} on uri: {}", baseUrl, URI_PUBLIC_KEY); + + final String key = this.webClient + .get() + .uri(URI_PUBLIC_KEY) + .retrieve() + .bodyToMono(String.class) + .doOnNext(response -> log.debug("Finished HTTP GET request with response: {}", response)) + .block(); + + try { + final ObjectMapper mapper = new ObjectMapper(); + return mapper.readValue(key, String.class); + } catch (final JsonProcessingException e) { + log.error("Invalid authorization public key", e); + + return null; + } + } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/arrowhead/ArrowheadServiceRegistryClient.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/arrowhead/ArrowheadServiceRegistryClient.java new file mode 100644 index 000000000..18d01d13a --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/arrowhead/ArrowheadServiceRegistryClient.java @@ -0,0 +1,180 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.arrowhead; + +import java.util.Optional; +import java.util.Set; + +import javax.validation.ConstraintViolation; +import javax.validation.ConstraintViolationException; +import javax.validation.Validation; +import javax.validation.Validator; + +import org.springframework.http.MediaType; +import org.springframework.http.client.reactive.ReactorClientHttpConnector; +import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.reactive.function.client.WebClientResponseException; + +import eu.arrowhead.core.hbconfmgr.arrowhead.model.request.ServiceQueryFormDTO; +import eu.arrowhead.core.hbconfmgr.arrowhead.model.request.ServiceRegistryRequestDTO; +import eu.arrowhead.core.hbconfmgr.arrowhead.model.response.ServiceQueryResultDTO; +import eu.arrowhead.core.hbconfmgr.arrowhead.model.response.ServiceRegistryResponseDTO; +import io.netty.handler.ssl.SslContext; +import lombok.extern.log4j.Log4j2; +import reactor.netty.http.client.HttpClient; + + +/** + * Client class to establish a connection with the Arrowhead Service Registry REST API and transmit requests. + */ +@Log4j2 +public class ArrowheadServiceRegistryClient { + + private static final String URI_REGISTER_SERVICE = "/serviceregistry/register"; + private static final String URI_UNREGISTER_SERVICE = "/serviceregistry/unregister"; + private static final String URI_QUERY_SERVICE = "/serviceregistry/query"; + + private static final String UNREGISTER_REQUEST_PARAM_SYSTEM_NAME = "system_name"; + private static final String UNREGISTER_REQUEST_PARAM_ADDRESS = "address"; + private static final String UNREGISTER_REQUEST_PARAM_PORT = "port"; + private static final String UNREGISTER_REQUEST_PARAM_SERVICE_DEFINITION = "service_definition"; + private static final String UNREGISTER_REQUEST_PARAM_SERVICE_URI = "service_uri"; + + private static final int SYSTEM_PORT_RANGE_MIN = 0; + private static final int SYSTEM_PORT_RANGE_MAX = 65535; + + private final WebClient webClient; + private final String baseUrl; + + /** + * Initializes a new rest client to communicate with the Arrowhead Service Registry. + * Insecure (HTTP) and secure (HTTPS) communication is supported, depending on the baseUrl. + * + * @param baseUrl specifies the base url where arrowhead is available + */ + public ArrowheadServiceRegistryClient(final String baseUrl) { + log.debug("Initialize ArrowheadServiceRegistryClient with baseUrl {}", baseUrl); + + this.webClient = WebClient.create(baseUrl); + this.baseUrl = baseUrl; + } + + /** + * Initializes a new rest client to communicate with the Arrowhead Service Registry with customized SSL handling. + * Insecure (HTTP) and secure (HTTPS) communication is supported, depending on the baseUrl. + * + * @param baseUrl specifies the base url where arrowhead is available + * @param sslContext specifies the ssl context the web client should use + */ + public ArrowheadServiceRegistryClient(final String baseUrl, final SslContext sslContext) { + log.debug("Initialize ArrowheadServiceRegistryClient with baseUrl {} and customized sslContext", baseUrl); + + final HttpClient httpClient = HttpClient.create() + .secure(sslContextSpec -> sslContextSpec.sslContext(sslContext)); + this.webClient = WebClient.builder() + .baseUrl(baseUrl) + .clientConnector(new ReactorClientHttpConnector(httpClient)) + .build(); + this.baseUrl = baseUrl; + } + + /** + * Registers a service. A provider is allowed to register only its own services. + * It means that provider system name and certificate common name must match for successful registration. + * A more detailed description of this REST endpoint can be found in Github under the + * client endpoint description for + * registering a service. + * + * @param serviceRegistryRequestDTO contains the information about the service that should be registered + * @return a string containing the full response body + * @throws ConstraintViolationException if the serviceRegistryRequestDTO is not valid + * @throws WebClientResponseException if the status code is 4xx or 5xx + */ + public ServiceRegistryResponseDTO registerService(final ServiceRegistryRequestDTO serviceRegistryRequestDTO) throws ConstraintViolationException, WebClientResponseException { + final Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); + final Set> violations = validator.validate(serviceRegistryRequestDTO); + if (!violations.isEmpty()) { + throw new ConstraintViolationException(violations); + } + + log.debug("Start HTTP POST request against {} on uri: {}", baseUrl, URI_REGISTER_SERVICE); + + return this.webClient + .post() + .uri(URI_REGISTER_SERVICE) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(serviceRegistryRequestDTO) + .retrieve() + .bodyToMono(ServiceRegistryResponseDTO.class) + .doOnNext(response -> log.debug("Finished HTTP POST request with response: {}", response)) + .block(); + } + + public void unregisterService(final String serviceDefinition, final String providerName, final String providerAddress, final int providerPort, final String serviceUri) { + if (isEmpty(serviceDefinition)) { + throw new RuntimeException("Service definition is blank"); + } + + if (isEmpty(providerName)) { + throw new RuntimeException("Name of the provider system is blank"); + } + + if (isEmpty(providerAddress)) { + throw new RuntimeException("Address of the provider system is blank"); + } + + if (providerPort < SYSTEM_PORT_RANGE_MIN || providerPort > SYSTEM_PORT_RANGE_MAX) { + throw new RuntimeException("Port must be between " + SYSTEM_PORT_RANGE_MIN + " and " + SYSTEM_PORT_RANGE_MAX + "."); + } + + log.debug("Start HTTP DELETE request against {} on uri: {}", baseUrl, URI_UNREGISTER_SERVICE); + + this.webClient + .delete() + .uri(uriBuilder -> uriBuilder + .path(URI_UNREGISTER_SERVICE) + .queryParam(UNREGISTER_REQUEST_PARAM_SERVICE_DEFINITION, serviceDefinition) + .queryParam(UNREGISTER_REQUEST_PARAM_SYSTEM_NAME, providerName) + .queryParam(UNREGISTER_REQUEST_PARAM_ADDRESS, providerAddress) + .queryParam(UNREGISTER_REQUEST_PARAM_PORT, providerPort) + .queryParamIfPresent(UNREGISTER_REQUEST_PARAM_SERVICE_URI, Optional.ofNullable(serviceUri)) + .build()) + .retrieve() + .bodyToMono(Void.class) + .doOnNext(response -> log.debug("Finished HTTP DELETE request with response: {}", response)) + .block(); + } + + public ServiceQueryResultDTO queryService(final ServiceQueryFormDTO form) throws ConstraintViolationException { + final Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); + final Set> violations = validator.validate(form); + if (!violations.isEmpty()) { + throw new ConstraintViolationException(violations); + } + + log.debug("Start HTTP POST request against {} on uri: {}", baseUrl, URI_QUERY_SERVICE); + + return this.webClient + .post() + .uri(URI_QUERY_SERVICE) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(form) + .retrieve() + .bodyToMono(ServiceQueryResultDTO.class) + .doOnNext(response -> log.debug("Finished HTTP POST request with response: {}", response)) + .block(); + } + + //------------------------------------------------------------------------------------------------- + private boolean isEmpty(final String str) { + return str == null || str.trim().isEmpty(); + } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/arrowhead/model/request/ServiceQueryFormDTO.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/arrowhead/model/request/ServiceQueryFormDTO.java new file mode 100644 index 000000000..bc9dfac91 --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/arrowhead/model/request/ServiceQueryFormDTO.java @@ -0,0 +1,34 @@ +/******************************************************************************** + * Copyright (c) 2021 AITIA + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * AITIA - implementation + * Arrowhead Consortia - conceptualization + ********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.arrowhead.model.request; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +@Data +@Builder +public class ServiceQueryFormDTO { + + @NotNull + @NotEmpty + private String serviceDefinitionRequirement; +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/arrowhead/model/request/ServiceRegistryRequestDTO.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/arrowhead/model/request/ServiceRegistryRequestDTO.java new file mode 100644 index 000000000..e43ac0733 --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/arrowhead/model/request/ServiceRegistryRequestDTO.java @@ -0,0 +1,60 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.arrowhead.model.request; + +import java.util.List; +import java.util.Map; + +import javax.validation.Valid; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + + +/** + * DTO class for Service Registry requests. Carries Arrowhead-specific information, + * necessary for the registration of the configuration system wrapper in the Service Registry. + */ +@AllArgsConstructor +@NoArgsConstructor +@Data +@Builder +public class ServiceRegistryRequestDTO { + + @NotNull + private String serviceDefinition; + + @Valid + @NotNull + private SystemRequestDTO providerSystem; + + @NotNull + private String serviceUri; + + private String endOfValidity; + + private Enum secure; + + private Map metadata; + + private Integer version; + + @NotEmpty + private List interfaces; + + public enum SecurityLevel { + NOT_SECURE, CERTIFICATE, TOKEN + } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/arrowhead/model/request/SystemRequestDTO.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/arrowhead/model/request/SystemRequestDTO.java new file mode 100644 index 000000000..7de588fb1 --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/arrowhead/model/request/SystemRequestDTO.java @@ -0,0 +1,41 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.arrowhead.model.request; + +import java.util.Map; + +import javax.validation.constraints.NotNull; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +@Data +@Builder +public class SystemRequestDTO { + + @NotNull + private String systemName; + + @NotNull + private String address; + + @NotNull + private Integer port; + + @NotNull + private String authenticationInfo; + + private Map metadata; +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/arrowhead/model/response/ServiceDefinitionResponseDTO.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/arrowhead/model/response/ServiceDefinitionResponseDTO.java new file mode 100644 index 000000000..4a5c65632 --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/arrowhead/model/response/ServiceDefinitionResponseDTO.java @@ -0,0 +1,28 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.arrowhead.model.response; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +@Data +@Builder +public class ServiceDefinitionResponseDTO { + + private long id; + private String serviceDefinition; + private String createdAt; + private String updatedAt; +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/arrowhead/model/response/ServiceInterfaceResponseDTO.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/arrowhead/model/response/ServiceInterfaceResponseDTO.java new file mode 100644 index 000000000..f027023e1 --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/arrowhead/model/response/ServiceInterfaceResponseDTO.java @@ -0,0 +1,28 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.arrowhead.model.response; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +@Data +@Builder +public class ServiceInterfaceResponseDTO { + + private long id; + private String interfaceName; + private String createdAt; + private String updatedAt; +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/arrowhead/model/response/ServiceQueryResultDTO.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/arrowhead/model/response/ServiceQueryResultDTO.java new file mode 100644 index 000000000..bb6266691 --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/arrowhead/model/response/ServiceQueryResultDTO.java @@ -0,0 +1,32 @@ +/******************************************************************************** + * Copyright (c) 2021 AITIA + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * AITIA - implementation + * Arrowhead Consortia - conceptualization + ********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.arrowhead.model.response; + +import java.util.List; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +@Data +@Builder +public class ServiceQueryResultDTO { + + private List serviceQueryData; + private int unfilteredHits; +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/arrowhead/model/response/ServiceRegistryResponseDTO.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/arrowhead/model/response/ServiceRegistryResponseDTO.java new file mode 100644 index 000000000..43e185971 --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/arrowhead/model/response/ServiceRegistryResponseDTO.java @@ -0,0 +1,43 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.arrowhead.model.response; + +import java.util.List; +import java.util.Map; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + + +/** + * DTO class for the Service Registry responses. Carries necessary information about the successful registration + * of the configuration system in the Service Registry. + */ +@AllArgsConstructor +@NoArgsConstructor +@Data +@Builder +public class ServiceRegistryResponseDTO { + + private long id; + private ServiceDefinitionResponseDTO serviceDefinition; + private SystemResponseDTO provider; + private String serviceUri; + private String endOfValidity; + private String secure; + private Map metadata; + private Integer version; + private List interfaces; + private String createdAt; + private String updatedAt; +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/arrowhead/model/response/SystemResponseDTO.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/arrowhead/model/response/SystemResponseDTO.java new file mode 100644 index 000000000..8cfe748b5 --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/arrowhead/model/response/SystemResponseDTO.java @@ -0,0 +1,34 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.arrowhead.model.response; + +import java.util.Map; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +@Data +@Builder +public class SystemResponseDTO { + + private long id; + private String systemName; + private String address; + private Integer port; + private String authenticationInfo; + private Map metadata; + private String createdAt; + private String updatedAt; +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/config/ArrowheadConfig.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/config/ArrowheadConfig.java new file mode 100644 index 000000000..1ce242b95 --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/config/ArrowheadConfig.java @@ -0,0 +1,144 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.config; + +import java.io.IOException; +import java.util.ServiceConfigurationError; + +import javax.annotation.PostConstruct; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLException; +import javax.net.ssl.TrustManagerFactory; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import eu.arrowhead.core.hbconfmgr.Constants; +import eu.arrowhead.core.hbconfmgr.arrowhead.ArrowheadAuthorizationSystemClient; +import eu.arrowhead.core.hbconfmgr.arrowhead.ArrowheadServiceRegistryClient; +import eu.arrowhead.core.hbconfmgr.arrowhead.model.request.ServiceQueryFormDTO; +import eu.arrowhead.core.hbconfmgr.arrowhead.model.response.ServiceQueryResultDTO; +import eu.arrowhead.core.hbconfmgr.arrowhead.model.response.ServiceRegistryResponseDTO; +import io.netty.handler.ssl.ClientAuth; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslContextBuilder; +import lombok.extern.log4j.Log4j2; + +/** + * This class provides the clients for interaction with Arrowhead via beans managed by the Spring container. + */ +@Log4j2 +@Configuration +public class ArrowheadConfig { + + private static final int RETRIES = 3; + private static final int PERIOD = 5000; + + @Value(Constants.SSL_ENABLED) + private boolean sslEnabled; + + @Autowired + private KeyManagerFactory keyManagerFactory; + + @Autowired + private TrustManagerFactory trustManagerFactory; + + private final String authorizationSystemProtocol = Constants.HTTPS + "://"; + private String authorizationSystemAddress; + private int authorizationSystemPort; + + private final String serviceRegistryProtocol = Constants.HTTPS + "://";; + + @Value(Constants.SERVICE_REGISTRY_ADDRESS) + private String serviceRegistryAddress; + + @Value(Constants.SERVICE_REGISTRY_PORT) + private int serviceRegistryPort; + + private SslContext sslContext; + + @PostConstruct + public void init() throws IOException, InterruptedException { + final ArrowheadServiceRegistryClient srClient = arrowheadServiceRegistryClient(); + for (int i = 0; i <= RETRIES; ++i) { + try { + final ServiceQueryFormDTO form = ServiceQueryFormDTO.builder() + .serviceDefinitionRequirement(Constants.CORE_SERVICE_AUTH_PUBLIC_KEY) + .build(); + + final ServiceQueryResultDTO response = srClient.queryService(form); + + if (!response.getServiceQueryData().isEmpty()) { + final ServiceRegistryResponseDTO authResponse = response.getServiceQueryData().get(0); + this.authorizationSystemAddress = authResponse.getProvider().getAddress(); + this.authorizationSystemPort = authResponse.getProvider().getPort(); + return; + } else if (i >= RETRIES) { + log.error("Authorization system is not accessible."); + throw new ServiceConfigurationError("HawkBit Configuration Manager cannot work without the Authorization core system."); + } else { + log.info("Authorization system is unavailable at the moment, retrying in {} seconds...", PERIOD / 1000); + Thread.sleep(PERIOD); + } + } catch (final Exception e) { + if (i >= RETRIES) { + log.error("Service Registry is not accessible.", e); + throw e; + } else { + log.info("Service Registry is unavailable at the moment, retrying in {} seconds...", PERIOD / 1000); + Thread.sleep(PERIOD); + } + } + } + } + + @Bean + public ArrowheadAuthorizationSystemClient arrowheadAuthorizationSystemClient() throws IOException { + final String baseUrl = this.authorizationSystemProtocol + this.authorizationSystemAddress + ":" + this.authorizationSystemPort; + log.debug("Registering bean for ArrowheadAuthorizationSystemClient with baseUrl {} and custom ssl context", baseUrl); + + final SslContext sslContext = loadSslContext(); + return new ArrowheadAuthorizationSystemClient(baseUrl, sslContext); + } + + @Bean + public ArrowheadServiceRegistryClient arrowheadServiceRegistryClient() throws IOException { + final String baseUrl = this.serviceRegistryProtocol + this.serviceRegistryAddress + ":" + this.serviceRegistryPort; + log.debug("Registering bean for ArrowheadServiceRegistryClient with baseUrl {} and custom ssl context", baseUrl); + + final SslContext sslContext = loadSslContext(); + return new ArrowheadServiceRegistryClient(baseUrl, sslContext); + } + + /** + * Load the ssl context with a defined client certificate and a defined server certificate. + * This allows to use self signed certificates. + * + * @param keyManagerFactory contains the client private and public certificates + * @param trustManagerFactory contains the server public certificate + * @return a ssl context for a {@link reactor.netty.http.client.HttpClient HttpClient} + * @throws SSLException if the ssl context could not be loaded correctly + */ + private SslContext loadSslContext() throws IOException { + if (sslContext == null) { + sslContext = SslContextBuilder + .forClient() + .clientAuth(ClientAuth.REQUIRE) + .keyManager(keyManagerFactory) + .trustManager(trustManagerFactory) + .build(); + } + + return sslContext; + } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/config/HawkbitConfig.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/config/HawkbitConfig.java new file mode 100644 index 000000000..bd872dd22 --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/config/HawkbitConfig.java @@ -0,0 +1,73 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.config; + +import java.io.IOException; +import java.util.concurrent.TimeoutException; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.DependsOn; +import org.springframework.context.annotation.Scope; + +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionFactory; + +import eu.arrowhead.core.hbconfmgr.Constants; + +/** + * This class provides the clients for interaction with hawkBit via beans managed by the Spring container. + */ +@Configuration +public class HawkbitConfig { + + @Value(Constants.HAWKBIT_HOST) + private String host; + + @Value(Constants.HAWKBIT_PORT) + private int port; + + @Value(Constants.HAWKBIT_USER) + private String username; + + @Value(Constants.HAWKBIT_PASSWORD) + private String password; + + @Bean + @DependsOn(Constants.GUARD_BEAN) + public ConnectionFactory connectionFactory() { + final ConnectionFactory connectionFactory = new ConnectionFactory(); + connectionFactory.setHost(this.host); + connectionFactory.setPort(this.port); + connectionFactory.setUsername(this.username); + connectionFactory.setPassword(this.password); + connectionFactory.setAutomaticRecoveryEnabled(true); + + return connectionFactory; + } + + @Bean + @DependsOn(Constants.GUARD_BEAN) + public Connection getConnection(final ConnectionFactory connectionFactory) throws IOException, TimeoutException { + return connectionFactory.newConnection(); + } + + @Bean + @DependsOn(Constants.GUARD_BEAN) + @Scope("prototype") + @Autowired + public Channel createChannel(final Connection connection) throws IOException { + return connection.createChannel(); + } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/config/KeyStoreConfig.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/config/KeyStoreConfig.java new file mode 100644 index 000000000..787cfcc44 --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/config/KeyStoreConfig.java @@ -0,0 +1,77 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.config; + +import java.io.IOException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; + +import javax.net.ssl.KeyManagerFactory; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import eu.arrowhead.core.hbconfmgr.SSLProperties; + +/** + * This class provides the key store of the configuration system in different formats via beans managed by the Spring + * container. The key store contains the private and the public key of the configuration system itself. + */ +@Configuration +public class KeyStoreConfig { + + @Autowired + private SSLProperties sslProps; + + private KeyStore keystore; + + @Bean + public KeyManagerFactory hawkbitConfigurationSystemKeyManagerFactory() throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException, IOException, CertificateException { + final KeyStore keyStore = loadKeyStore(); + final KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + keyManagerFactory.init(keyStore, sslProps.getKeyStorePassword().toCharArray()); + + return keyManagerFactory; + } + + @Bean + public PrivateKey hawkbitConfigurationSystemPrivateKey() throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, IOException, CertificateException { + final KeyStore keyStore = loadKeyStore(); + + return (PrivateKey) keyStore.getKey(sslProps.getKeyAlias(), sslProps.getKeyPassword().toCharArray()); + } + + @Bean + public PublicKey hawkbitConfigurationSystemPublicKey() throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException { + final KeyStore keyStore = loadKeyStore(); + + return keyStore.getCertificate(sslProps.getKeyAlias()).getPublicKey(); + } + + private KeyStore loadKeyStore() throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException { + if (sslProps.isSslEnabled()) { + if (keystore == null) { + keystore = KeyStore.getInstance(sslProps.getKeyStoreType()); + keystore.load(sslProps.getKeyStore().getInputStream(), sslProps.getKeyStorePassword().toCharArray()); + } + + return keystore; + } + + return null; + } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/config/PropertiesConfig.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/config/PropertiesConfig.java new file mode 100644 index 000000000..4c858a8ea --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/config/PropertiesConfig.java @@ -0,0 +1,41 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import eu.arrowhead.core.hbconfmgr.properties.SystemProperties; + +/** + * This class instantiates properties classes, populates them from Spring properties files (like application.yml) + * and provides them as beans managed by the Spring container. + */ +@Configuration +public class PropertiesConfig { + + private static final String SYSTEM = "system"; + + /** + * This makes use of Spring properties files (like application.yml), loads all properties under the + * prefix "system" into the newly instantiated class {@link SystemProperties} and provides it as a bean + * managed by the Spring container. + * + * @return a populated and validated instance of {@link SystemProperties} + */ + @Bean + @ConfigurationProperties(prefix = SYSTEM) + public SystemProperties systemProperties() { + return new SystemProperties(); + } + +} diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/config/TrustStoreConfig.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/config/TrustStoreConfig.java new file mode 100644 index 000000000..a0ffab579 --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/config/TrustStoreConfig.java @@ -0,0 +1,60 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.config; + +import java.io.IOException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; + +import javax.net.ssl.TrustManagerFactory; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import eu.arrowhead.core.hbconfmgr.SSLProperties; + +/** + * This class provides the trust store of the configuration system in different formats via beans managed by the Spring + * container. The trust store contains the public keys that are trusted. + */ +@Configuration +public class TrustStoreConfig { + + @Autowired + private SSLProperties sslProps; + + private KeyStore truststore; + + @Bean + public TrustManagerFactory trustManagerFactory() throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException { + final KeyStore trustStore = loadTrustStore(); + final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(trustStore); + + return trustManagerFactory; + } + + private KeyStore loadTrustStore() throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException { + if (sslProps.isSslEnabled()) { + if (truststore == null) { + truststore = KeyStore.getInstance(sslProps.getKeyStoreType()); + truststore.load(sslProps.getTrustStore().getInputStream(), sslProps.getTrustStorePassword().toCharArray()); + } + + return truststore; + } + + return null; + } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/config/WebSocketConfig.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/config/WebSocketConfig.java new file mode 100644 index 000000000..d1a625796 --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/config/WebSocketConfig.java @@ -0,0 +1,32 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.config; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.DependsOn; +import org.springframework.context.annotation.Scope; +import org.springframework.web.socket.WebSocketSession; + +import eu.arrowhead.core.hbconfmgr.Constants; + +@Configuration +public class WebSocketConfig { + @Bean + @DependsOn(Constants.GUARD_BEAN) + @Scope("singleton") + public Map getWebSocketSessionMap() { + return new ConcurrentHashMap<>(); + } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/config/WebSocketSecurityConfig.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/config/WebSocketSecurityConfig.java new file mode 100644 index 000000000..4e308879c --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/config/WebSocketSecurityConfig.java @@ -0,0 +1,83 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.config; + +import java.security.PrivateKey; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; +import org.springframework.web.socket.config.annotation.EnableWebSocket; +import org.springframework.web.socket.config.annotation.WebSocketConfigurer; +import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; + +import eu.arrowhead.core.hbconfmgr.security.JwtAuthenticationFilter; +import eu.arrowhead.core.hbconfmgr.service.ArrowheadService; +import eu.arrowhead.core.hbconfmgr.websocket.WebSocketController; + +/** + * This is the configuration for web sockets. + *

Web socket support can be enabled/disabled with the annotation {@link EnableWebSocket @EnableWebSocket}

+ *

Web socket security can be enabled/disabled with the annotation {@link EnableWebSecurity @EnableWebSecurity}

+ */ +@Configuration +@EnableWebSecurity +@EnableWebSocket +public class WebSocketSecurityConfig extends WebSecurityConfigurerAdapter implements WebSocketConfigurer { + + private final ArrowheadService arrowheadService; + private final PrivateKey privateKey; + private final WebSocketController webSocketController; + + @Autowired + public WebSocketSecurityConfig(final ArrowheadService arrowheadService, final PrivateKey privateKey, final WebSocketController webSocketController) { + this.arrowheadService = arrowheadService; + this.privateKey = privateKey; + this.webSocketController = webSocketController; + } + + /** + * Registers the web socket controller as web socket handler. + */ + @Override + public void registerWebSocketHandlers(final WebSocketHandlerRegistry registry) { + registry.addHandler(this.webSocketController, "/"); + } + + /** + * Configuration of the HTTP security for the whole configuration system, including web socket connections. + * Many default security settings are disabled as the clients are connecting non-interactive to the configuration + * system and don't support all default security settings. + */ + @Override + protected void configure(final HttpSecurity http) throws Exception { + http + // Disable form login, as only non-interactive clients connect + .formLogin().disable() + // Disable HTTP basic authentication, as only JWT authentication is supported + .httpBasic().disable() + // CORS protection is not required, as only non-interactive clients connect + .cors().disable() + // CSRF protection is not required, as only non-interactive clients connect + .csrf(AbstractHttpConfigurer::disable) + // Register custom JWT authentication filter, so JWT authentication is supported + .addFilterBefore(new JwtAuthenticationFilter(this.arrowheadService, this.privateKey), BasicAuthenticationFilter.class) + // Disable Spring Sessions, as sessions should not be supported + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + // Require full authentication on every request + .authorizeRequests().anyRequest().fullyAuthenticated(); + } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/hawkbit/HawkbitDmfConstants.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/hawkbit/HawkbitDmfConstants.java new file mode 100644 index 000000000..996b1d0f3 --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/hawkbit/HawkbitDmfConstants.java @@ -0,0 +1,39 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.hawkbit; + +public class HawkbitDmfConstants { + + /** + * The exchange name for all messages sent to hawkBit. It is predefined from hawkBit and can not be changed. + */ + public static final String SENDING_EXCHANGE = "dmf.exchange"; + + /** + * The routing key for all messages sent to hawkBit. It is predefined from hawkBit and can not be changed. + */ + public static final String SENDING_ROUTING_KEY = ""; + + /** + * The exchange name for all messages sent from hawkBit to the configuration system. It can be modified. + */ + public static final String RECEIVING_EXCHANGE = "configuration_system.direct.exchange"; + + /** + * The queue name for all messages sent from hawkBit to the configuration system. It can be modified. + */ + public static final String RECEIVING_QUEUE = "configuration_system_direct_queue"; + + /** + * The routing key for all messages sent from hawkBit to the configuration system. It can be modified. + */ + public static final String RECEIVING_ROUTING_KEY = ""; +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/hawkbit/HawkbitDmfConsumer.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/hawkbit/HawkbitDmfConsumer.java new file mode 100644 index 000000000..92a0b6c1f --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/hawkbit/HawkbitDmfConsumer.java @@ -0,0 +1,331 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.hawkbit; + +import java.io.IOException; +import java.util.Map; + +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.BuiltinExchangeType; +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.DefaultConsumer; +import com.rabbitmq.client.Envelope; + +import eu.arrowhead.core.hbconfmgr.hawkbit.model.EventType; +import eu.arrowhead.core.hbconfmgr.hawkbit.model.MessageHeader; +import eu.arrowhead.core.hbconfmgr.hawkbit.model.inbound.CancelDownloadInboundMessage; +import eu.arrowhead.core.hbconfmgr.hawkbit.model.inbound.DownloadRequestInboundMessage; +import eu.arrowhead.core.hbconfmgr.hawkbit.model.inbound.MessageTypeInbound; +import eu.arrowhead.core.hbconfmgr.hawkbit.model.inbound.ThingDeletedInboundMessage; +import eu.arrowhead.core.hbconfmgr.service.WebSocketService; +import eu.arrowhead.core.hbconfmgr.websocket.DeviceNotConnectedException; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import lombok.extern.log4j.Log4j2; + +/** + * Handles incoming messages on the connected HawkBit (RabbitMQ) queue. + * Responsible for further message routing, depending on the message type and + * topic. + */ +@Log4j2 +@Component +public class HawkbitDmfConsumer extends DefaultConsumer { + + private final Channel channel; + private final ObjectMapper objectMapper; + + private final WebSocketService wsService; + + /** + * Initializes a new hawkBit consumer that operates on the hawkBit device + * management federation (DMF) API. It is used for downstream communication + * (receiving messages from hawkBit). Further information to the downstream DMF + * API can be found in the DMF API documentation + * under "Messages sent by hawkBit (hawkBit -> Client)" + * + * @param channel used for communication with hawkBit + */ + @Autowired + public HawkbitDmfConsumer(final Channel channel, final WebSocketService wsService) { + super(channel); + this.channel = channel; + this.objectMapper = new ObjectMapper(); + this.wsService = wsService; + } + + /** + * Subscribe to download event messages from hawkBit. + *

+ * Important: The handler is responsible to acknowledge + * the successful retrieval and processing of the message via the method + * {@link #acknowledgeMessage(long)}. + * + * @throws IOException if there is a connection problem with hawkBit + */ + public void subscribeToDownloadEvents() throws IOException { + this.initializeQueueOnHawkbit(); + this.consumeFromQueueOnHawkbit(); + } + + /** + * Initialize the queue on hawkBit DMF API that is used from the configuration + * system to receive messages. + * + * @throws IOException if there is a connection problem with hawkBit + */ + private void initializeQueueOnHawkbit() throws IOException { + this.channel.queueDeclare(HawkbitDmfConstants.RECEIVING_QUEUE, true, false, false, null); + this.channel.exchangeDeclare(HawkbitDmfConstants.RECEIVING_EXCHANGE, BuiltinExchangeType.DIRECT, true, false, null); + this.channel.queueBind(HawkbitDmfConstants.RECEIVING_QUEUE, HawkbitDmfConstants.RECEIVING_EXCHANGE, HawkbitDmfConstants.RECEIVING_ROUTING_KEY); + } + + /** + * Start consuming from the configuration system queue on the hawkBit DMF API. + * + * @throws IOException if there is a connection problem with hawkBit + */ + private void consumeFromQueueOnHawkbit() throws IOException { + this.channel.basicConsume(HawkbitDmfConstants.RECEIVING_QUEUE, false, this); + } + + /** + * Overrides the method + * {@link DefaultConsumer#handleDelivery(String, Envelope, AMQP.BasicProperties, byte[])} + * with an implementation to handle hawkBit DMF API messages. + */ + @Override + public void handleDelivery(final String consumerTag, final Envelope envelope, final AMQP.BasicProperties properties, final byte[] body) { + try { + log.debug("Received message"); + handleMessage(envelope, properties, body); + } catch (final IOException e) { + log.atError().withThrowable(e).log( + "Handling of message delivery failed. Message has the following properties: {} and body: {}", properties, body); + } + } + + /** + * Handle a hawkBit message. + * + * @param envelope envelope of the AMQP message + * @param properties properties (including the headers) of the AMQP message + * @param body body of the AMQP message + * @throws IOException if a problem occurs with the message itself, like a + * missing header or a malformed body + */ + private void handleMessage(final Envelope envelope, final AMQP.BasicProperties properties, final byte[] body) throws IOException { + final Map headers = properties.getHeaders(); + if (headers == null || !headers.containsKey(MessageHeader.TYPE.toString())) { + throw new IOException("Message doesn't contain header " + MessageHeader.TYPE.toString()); + } + + final MessageTypeInbound messageType = MessageTypeInbound.valueOf(String.valueOf(headers.get(MessageHeader.TYPE.toString()))); + switch (messageType) { + case EVENT: + handleEvent(envelope, properties, body); + break; + case THING_DELETED: + handleThingDeleted(envelope, properties); + break; + case PING_RESPONSE: + default: + handleUnsupportedMessageType(envelope, messageType); + break; + } + } + + /** + * Handle a hawkBit message from type {@link MessageTypeInbound#EVENT}. + * + * @param envelope envelope of the AMQP message + * @param properties properties (including the headers) of the AMQP message + * @param body body of the AMQP message + * @throws IOException if a problem occurs with the message itself, like a + * missing header or a malformed body + */ + private void handleEvent(final Envelope envelope, final AMQP.BasicProperties properties, final byte[] body) throws IOException { + final Map headers = properties.getHeaders(); + if (headers == null || !headers.containsKey(MessageHeader.TOPIC.toString())) { + throw new IOException("Message doesn't contain header " + MessageHeader.TOPIC.toString()); + } + + final EventType eventType = EventType.valueOf(String.valueOf(headers.get(MessageHeader.TOPIC.toString()))); + switch (eventType) { + case DOWNLOAD: + case DOWNLOAD_AND_INSTALL: + handleEventWithTopicDownload(envelope, properties, body); + break; + case CANCEL_DOWNLOAD: + handleEventWithTopicCancelDownload(envelope, properties, body); + break; + case MULTI_ACTION: + case REQUEST_ATTRIBUTES_UPDATE: + default: + handleEventWithUnsupportedTopic(envelope, eventType); + break; + } + } + + /** + * Handle a hawkBit download request message. + * + * @param envelope envelope of the AMQP message + * @param properties properties (including the headers) of the AMQP message + * @param body body of the AMQP message + * @throws IOException if a problem occurs during mapping the body to + * {@link DownloadRequestInboundMessage.Body} with a json + * object mapper + */ + private void handleEventWithTopicDownload(final Envelope envelope, final AMQP.BasicProperties properties, final byte[] body) { + final Map headers = properties.getHeaders(); + + DownloadRequestInboundMessage message; + try { + message = DownloadRequestInboundMessage.builder() + .headers(DownloadRequestInboundMessage.Headers.builder() + .type(headers.get(MessageHeader.TYPE.toString()).toString()) + .thingId(headers.get(MessageHeader.THING_ID.toString()).toString()) + .topic(headers.get(MessageHeader.TOPIC.toString()).toString()) + .tenant(headers.get(MessageHeader.TENANT.toString()).toString()).build()) + .body(this.objectMapper.readValue(body, DownloadRequestInboundMessage.Body.class)) + .deliveryTag(envelope.getDeliveryTag()).build(); + + wsService.sendDownloadEventMessage(message); + + acknowledgeMessageDelivery(envelope.getDeliveryTag()); + } catch (final JsonParseException e) { + log.error("Could not parse inbound message: {}", e); + } catch (final JsonMappingException e) { + log.error("Could not map inbound message: {}", e); + } catch (final IOException e) { + log.error("Could not send message to device {}, as it was not reachable.", headers.get(MessageHeader.THING_ID.toString()).toString()); + } catch (final DeviceNotConnectedException e) { + log.error("Device {} is currently not connected to HawkBit.", headers.get(MessageHeader.THING_ID.toString()).toString()); + } + } + + /** + * Handle CANCEL_DOWNLOAD messages. Send ACK only if parsing of the message and further + * transport via WebSocket was successful. + * + * @param envelope AMQP envelope + * @param properties AMQP properties + * @param body the actual amqp message payload + */ + private void handleEventWithTopicCancelDownload(final Envelope envelope, final AMQP.BasicProperties properties, final byte[] body) { + final Map headers = properties.getHeaders(); + + CancelDownloadInboundMessage message; + try { + message = CancelDownloadInboundMessage.builder() + .headers( + CancelDownloadInboundMessage.Headers.builder() + .type(headers.get(MessageHeader.TYPE.toString()).toString()) + .thingId(headers.get(MessageHeader.THING_ID.toString()).toString()) + .topic(headers.get(MessageHeader.TOPIC.toString()).toString()) + .tenant(headers.get(MessageHeader.TENANT.toString()).toString()) + .build() + ) + .body(this.objectMapper.readValue(body, CancelDownloadInboundMessage.Body.class)).build(); + + this.wsService.sendCancelDownloadMessage(message); + + acknowledgeMessageDelivery(envelope.getDeliveryTag()); + } catch (final JsonParseException e) { + log.error("Could not parse inbound message: {}", e); + } catch (final JsonMappingException e) { + log.error("Could not map inbound message: {}", e); + } catch (final IOException e) { + log.error("Could not send message to device {}, as it was not reachable.", headers.get(MessageHeader.THING_ID.toString()).toString()); + } catch (final DeviceNotConnectedException e) { + log.error("Device {} is currently not connected to HawkBit.", headers.get(MessageHeader.THING_ID.toString()).toString()); + } + } + + /** + * Handle THING_DELETED messages. Send ACK only if parsing of the message and further + * transport via Websocket was successful + * + * @param envelope AMQP envelope + * @param properties AMQP properties + * @param body the actual AMQP message payload + */ + private void handleThingDeleted(final Envelope envelope, final AMQP.BasicProperties properties) { + final Map headers = properties.getHeaders(); + + ThingDeletedInboundMessage message; + try { + message = ThingDeletedInboundMessage.builder() + .headers( + ThingDeletedInboundMessage.Headers.builder() + .thingId(headers.get(MessageHeader.THING_ID.toString()).toString()) + .build() + ) + .build(); + + this.wsService.sendThingDeletedMessage(message); + + acknowledgeMessageDelivery(envelope.getDeliveryTag()); + } catch (final JsonParseException e) { + log.error("Could not parse inbound message: {}", e); + } catch (final JsonMappingException e) { + log.error("Could not map inbound message: {}", e); + } catch (final IOException e) { + log.error("Could not send message to device {}, as it was not reachable.", headers.get(MessageHeader.THING_ID.toString()).toString()); + } catch (final DeviceNotConnectedException e) { + log.error("Device {} is currently not connected to HawkBit.", headers.get(MessageHeader.THING_ID.toString()).toString()); + } + } + + + /** + * Handle a hawkBit message with unsupported message type. Send a negative acknowledgement to hawkBit for the + * message with no requeuing. + * + * @param envelope envelope of the AMQP message + * @param messageType message type of the AMQP message + * @throws IOException if there is a connection problem with hawkBit + */ + private void handleUnsupportedMessageType(final Envelope envelope, final MessageTypeInbound messageType) throws IOException { + log.error("Message with unsupported message type {} was received, responding with negative acknowledgement and no requeuing", messageType.toString()); + + super.getChannel().basicNack(envelope.getDeliveryTag(), false, false); + } + + /** + * Handle a hawkBit message from message type {@link MessageTypeInbound#EVENT} and with unsupported event type. Send a + * negative acknowledgement to hawkBit for the message with no requeuing. + * + * @param envelope envelope of the AMQP message + * @param eventType event type of the AMQP message + * @throws IOException if there is a connection problem with hawkBit + */ + private void handleEventWithUnsupportedTopic(final Envelope envelope, final EventType eventType) throws IOException { + log.error("Message with message type {} and unsupported event topic {} was received, responding with negative acknowledgement and no requeuing", MessageTypeInbound.EVENT.toString(), eventType.toString()); + + super.getChannel().basicNack(envelope.getDeliveryTag(), false, false); + } + + private void acknowledgeMessageDelivery(final Long deliveryTag) { + try { + super.getChannel().basicAck(deliveryTag, false); + } catch (final IOException e) { + log.error("Could not acknowledge message delivery to HawkBit."); + } + } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/hawkbit/HawkbitDmfOutboundClient.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/hawkbit/HawkbitDmfOutboundClient.java new file mode 100644 index 000000000..65de4b845 --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/hawkbit/HawkbitDmfOutboundClient.java @@ -0,0 +1,138 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.hawkbit; + +import java.io.IOException; +import java.util.Set; + +import javax.validation.ConstraintViolation; +import javax.validation.ConstraintViolationException; +import javax.validation.Validation; +import javax.validation.Validator; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.Channel; + +import eu.arrowhead.core.hbconfmgr.Constants; +import eu.arrowhead.core.hbconfmgr.hawkbit.model.outbound.ThingCreatedOutboundMessage; +import eu.arrowhead.core.hbconfmgr.hawkbit.model.outbound.ThingRemovedOutboundMessage; +import eu.arrowhead.core.hbconfmgr.hawkbit.model.outbound.UpdateActionStatusOutboundMessage; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import lombok.extern.log4j.Log4j2; + + +/** + * Handles all outbound interaction with Eclipse HawkBit via its DMF API. + */ +@Log4j2 +@Component +public class HawkbitDmfOutboundClient { + + private final Channel channel; + private final ObjectMapper objectMapper; + private final Validator validator; + + /** + * Initialize a new hawkBit client that operates on the hawkBit device management federation (DMF) API. It is + * used for upstream communication (sending messages to hawkBit). Further information to the upstream DMF API can + * be found in the DMF API documentation under + * "Messages sent to hawkBit (Client -> hawkBit)" + * + * @param channel used for communication with hawkBit + */ + @Autowired + public HawkbitDmfOutboundClient(final Channel channel) { + this.channel = channel; + this.objectMapper = new ObjectMapper(); + this.validator = Validation.buildDefaultValidatorFactory().getValidator(); + } + + /** + * Send a message to hawkBit on the device management federation (DMF) API to create a new thing. A description of + * this API can be found in the DMF API documentation + * under "Messages sent to hawkBit (Client -> hawkBit)" -> "THING_CREATED". + * + * @param message the message to publish with body and headers + * @throws ConstraintViolationException if message is not valid + * @throws IOException if there is a connection problem with hawkBit + */ + public void createThing(final ThingCreatedOutboundMessage message) throws ConstraintViolationException, IOException { + log.debug("Validating ThingCreatedOutboundMessage"); + + final Set> violations = validator.validate(message); + if (!violations.isEmpty()) { + throw new ConstraintViolationException(violations); + } + + final byte[] byteBody = this.objectMapper.writeValueAsBytes(message.getBody()); + final AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder() + .contentType(Constants.APPLICATION_JSON) + .headers(message.getHeaders().asMap()) + .replyTo(HawkbitDmfConstants.RECEIVING_EXCHANGE) + .build(); + + log.debug("Sending ThingCreatedOutboundMessage to exchange {}", HawkbitDmfConstants.SENDING_EXCHANGE); + this.channel.basicPublish(HawkbitDmfConstants.SENDING_EXCHANGE, HawkbitDmfConstants.SENDING_ROUTING_KEY, properties, byteBody); + } + + /** + * Send a message to hawkBit on the device management federation (DMF) API to create a new thing. A description of + * this API can be found in the DMF API documentation + * under "Messages sent to hawkBit (Client -> hawkBit)" -> "UPDATE_ACTION_STATUS". + * + * @param message the message to publish with body and headers + * @throws ConstraintViolationException if message is not valid + * @throws IOException if there is a connection problem with hawkBit + */ + public void updateActionStatus(final UpdateActionStatusOutboundMessage message) throws IOException { + log.debug("Validating UpdateActionStatusOutboundMessage"); + + final Set> violations = validator.validate(message); + if (!violations.isEmpty()) { + throw new ConstraintViolationException(violations); + } + + final byte[] byteBody = this.objectMapper.writeValueAsBytes(message.getBody()); + final AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder() + .contentType(Constants.APPLICATION_JSON) + .headers(message.getHeaders().asMap()) + .build(); + + log.debug("Sending UpdateActionStatusOutboundMessage to exchange {}", HawkbitDmfConstants.SENDING_EXCHANGE); + this.channel.basicPublish(HawkbitDmfConstants.SENDING_EXCHANGE, HawkbitDmfConstants.SENDING_ROUTING_KEY, properties, byteBody); + } + + /** + * Send a message to hawkBit via the device management federation (DMF) API to remove a thing. A description of + * this API can be found in the DMF API documentation + * under "Messages sent to hawkBit (Client -> hawkBit)" -> "THING_REMOVED". + * @param message the actual message to be published + * @throws IOException + */ + public void removeThing(final ThingRemovedOutboundMessage message) throws IOException { + final Set> violations = validator.validate(message); + if (!violations.isEmpty()) { + throw new ConstraintViolationException(violations); + } + + final byte[] byteBody = "".getBytes(); + final AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder() + .contentType(Constants.APPLICATION_JSON) + .headers(message.getHeaders().asMap()) + .build(); + + this.channel.basicPublish(HawkbitDmfConstants.SENDING_EXCHANGE, HawkbitDmfConstants.SENDING_ROUTING_KEY, properties, byteBody); + } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/hawkbit/model/ActionStatus.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/hawkbit/model/ActionStatus.java new file mode 100644 index 000000000..e1f25e5df --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/hawkbit/model/ActionStatus.java @@ -0,0 +1,38 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.hawkbit.model; + + +/** + * Enum for outbound UPDATE_ACTION_STATUS type classification. + */ +public enum ActionStatus { + DOWNLOAD("DOWNLOAD"), + RETRIEVED("RETRIEVED"), + RUNNING("RUNNING"), + FINISHED("FINISHED"), + ERROR("ERROR"), + WARNING("WARNING"), + CANCELED("CANCELED"), + CANCEL_REJECTED("CANCEL_REJECTED"), + DOWNLOADED("DOWNLOADED"); + + private final String stringValue; + + private ActionStatus(final String stringValue) { + this.stringValue = stringValue; + } + + @Override + public String toString() { + return this.stringValue; + } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/hawkbit/model/EventType.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/hawkbit/model/EventType.java new file mode 100644 index 000000000..acc8b066a --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/hawkbit/model/EventType.java @@ -0,0 +1,31 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.hawkbit.model; + +public enum EventType { + CANCEL_DOWNLOAD("CANCEL_DOWNLOAD"), + DOWNLOAD("DOWNLOAD"), + DOWNLOAD_AND_INSTALL("DOWNLOAD_AND_INSTALL"), + MULTI_ACTION("MULTI_ACTION"), + REQUEST_ATTRIBUTES_UPDATE("REQUEST_ATTRIBUTES_UPDATE"), + UPDATE_ACTION_STATUS("UPDATE_ACTION_STATUS"); + + private final String stringValue; + + private EventType(final String stringValue) { + this.stringValue = stringValue; + } + + @Override + public String toString() { + return this.stringValue; + } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/hawkbit/model/MessageHeader.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/hawkbit/model/MessageHeader.java new file mode 100644 index 000000000..a84a2a845 --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/hawkbit/model/MessageHeader.java @@ -0,0 +1,34 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.hawkbit.model; + + +/** + * Enum for outbound header type classification. + */ +public enum MessageHeader { + SENDER("sender"), + TENANT("tenant"), + THING_ID("thingId"), + TOPIC("topic"), + TYPE("type"); + + private final String stringValue; + + private MessageHeader(final String stringValue) { + this.stringValue = stringValue; + } + + @Override + public String toString() { + return this.stringValue; + } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/hawkbit/model/inbound/CancelDownloadInboundMessage.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/hawkbit/model/inbound/CancelDownloadInboundMessage.java new file mode 100644 index 000000000..6cbe35f0b --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/hawkbit/model/inbound/CancelDownloadInboundMessage.java @@ -0,0 +1,45 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.hawkbit.model.inbound; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + + +/** + * Transfer object, encapsulating Cancel Download requests, received by the HawkBit consumer. + */ +@Builder +@Data +public class CancelDownloadInboundMessage implements InboundMessage { + + private Headers headers; + private Body body; + + @Builder + @Data + public static class Headers { + private String type; + private String thingId; + private String topic; + private String tenant; + } + + @AllArgsConstructor + @NoArgsConstructor + @Builder + @Data + public static class Body { + private Long actionId; + } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/hawkbit/model/inbound/DownloadRequestInboundMessage.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/hawkbit/model/inbound/DownloadRequestInboundMessage.java new file mode 100644 index 000000000..fdac5fcda --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/hawkbit/model/inbound/DownloadRequestInboundMessage.java @@ -0,0 +1,112 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.hawkbit.model.inbound; + +import java.util.Date; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + + +/** + * Transfer object, encapsulating Download Requests, received by the HawkBit consumer. + */ +@AllArgsConstructor +@NoArgsConstructor +@Data +@Builder +public class DownloadRequestInboundMessage implements InboundMessage { + + private Body body; + private Headers headers; + private Long deliveryTag; + + @AllArgsConstructor + @NoArgsConstructor + @Data + @Builder + public static class Body { + private Long actionId; + private String targetSecurityToken; + private List softwareModules; + + @AllArgsConstructor + @NoArgsConstructor + @Data + @Builder + public static class SoftwareModule { + private Long moduleId; + private String moduleType; + private String moduleVersion; + private List artifacts; + private List metadata; + } + + @AllArgsConstructor + @NoArgsConstructor + @Data + @Builder + public static class Artifact { + private String filename; + private Urls urls; + private Hashes hashes; + private Long size; + private Date lastModified; + } + + @AllArgsConstructor + @NoArgsConstructor + @Data + @Builder + public static class Urls { + @JsonProperty("COAP") + private String COAP; + @JsonProperty("HTTP") + private String HTTP; + @JsonProperty("HTTPS") + private String HTTPS; + } + + @AllArgsConstructor + @NoArgsConstructor + @Data + @Builder + public static class Hashes { + private String md5; + private String sha1; + } + + @AllArgsConstructor + @NoArgsConstructor + @Data + @Builder + public static class Metadata { + private String key; + private String value; + } + } + + @AllArgsConstructor + @NoArgsConstructor + @Data + @Builder + public static class Headers { + private String type; + private String thingId; + private String topic; + private String tenant; + } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/hawkbit/model/inbound/InboundMessage.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/hawkbit/model/inbound/InboundMessage.java new file mode 100644 index 000000000..cb3080877 --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/hawkbit/model/inbound/InboundMessage.java @@ -0,0 +1,17 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.hawkbit.model.inbound; + + +/** + * Interface for all incoming AMQP message types. + */ +public interface InboundMessage {} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/hawkbit/model/inbound/MessageTypeInbound.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/hawkbit/model/inbound/MessageTypeInbound.java new file mode 100644 index 000000000..8c6fd5a5d --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/hawkbit/model/inbound/MessageTypeInbound.java @@ -0,0 +1,32 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.hawkbit.model.inbound; + + +/** + * Enum for inbound message type classification. + */ +public enum MessageTypeInbound { + EVENT("EVENT"), + THING_DELETED("THING_DELETED"), + PING_RESPONSE("PING_RESPONSE"); + + private final String stringValue; + + private MessageTypeInbound(final String stringValue) { + this.stringValue = stringValue; + } + + @Override + public String toString() { + return this.stringValue; + } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/hawkbit/model/inbound/ThingDeletedInboundMessage.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/hawkbit/model/inbound/ThingDeletedInboundMessage.java new file mode 100644 index 000000000..39897c8bc --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/hawkbit/model/inbound/ThingDeletedInboundMessage.java @@ -0,0 +1,29 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.hawkbit.model.inbound; + +import lombok.Builder; +import lombok.Data; + +/** + * Transfer object, encapsulating Thing Deleted messages, received by the HawkBit consumer. + */ +@Builder +@Data +public class ThingDeletedInboundMessage implements InboundMessage { + private Headers headers; + + @Builder + @Data + public static class Headers { + private String thingId; + } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/hawkbit/model/outbound/MessageTypeOutbound.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/hawkbit/model/outbound/MessageTypeOutbound.java new file mode 100644 index 000000000..d4881fbbb --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/hawkbit/model/outbound/MessageTypeOutbound.java @@ -0,0 +1,32 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.hawkbit.model.outbound; + + +/** + * Enum for outbound message type classification. + */ +public enum MessageTypeOutbound { + EVENT("EVENT"), + THING_CREATED("THING_CREATED"), + THING_REMOVED("THING_REMOVED"); + + private final String stringValue; + + private MessageTypeOutbound(final String stringValue) { + this.stringValue = stringValue; + } + + @Override + public String toString() { + return this.stringValue; + } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/hawkbit/model/outbound/ThingCreatedOutboundMessage.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/hawkbit/model/outbound/ThingCreatedOutboundMessage.java new file mode 100644 index 000000000..e5d21e4b8 --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/hawkbit/model/outbound/ThingCreatedOutboundMessage.java @@ -0,0 +1,84 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.hawkbit.model.outbound; + +import java.util.HashMap; +import java.util.Map; + +import javax.validation.Valid; +import javax.validation.constraints.NotNull; + +import eu.arrowhead.core.hbconfmgr.hawkbit.model.MessageHeader; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + + +/** + * Transfer object, encapsulating THING_CREATED requests, sent by the HawkBit client. + */ +@AllArgsConstructor +@NoArgsConstructor +@Data +@Builder +public class ThingCreatedOutboundMessage { + + @Valid + @NotNull + private Body body; + + @Valid + @NotNull + private Headers headers; + + @AllArgsConstructor + @NoArgsConstructor + @Data + @Builder + public static class Body { + private String name; + } + + @AllArgsConstructor + @NoArgsConstructor + @Data + @Builder + public static class Headers { + @NotNull + private final String type = MessageTypeOutbound.THING_CREATED.toString(); + + @NotNull + private String thingId; + + private String sender; + + private String tenant; + + public Map asMap() { + final HashMap headers = new HashMap<>(); + if (this.type != null) { + headers.put(MessageHeader.TYPE.toString(), this.type); + } + if (this.thingId != null) { + headers.put(MessageHeader.THING_ID.toString(), this.thingId); + } + if (this.sender != null) { + headers.put(MessageHeader.SENDER.toString(), this.sender); + } + if (this.tenant != null) { + headers.put(MessageHeader.TENANT.toString(), this.tenant); + } + + return headers; + } + } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/hawkbit/model/outbound/ThingRemovedOutboundMessage.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/hawkbit/model/outbound/ThingRemovedOutboundMessage.java new file mode 100644 index 000000000..84018aeff --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/hawkbit/model/outbound/ThingRemovedOutboundMessage.java @@ -0,0 +1,55 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.hawkbit.model.outbound; + +import java.util.HashMap; +import java.util.Map; + +import javax.validation.constraints.NotNull; + +import eu.arrowhead.core.hbconfmgr.hawkbit.model.MessageHeader; +import lombok.Builder; +import lombok.Data; + + +/** + * Transfer object, encapsulating THING_REMOVED requests, sent by the HawkBit client. + */ +@Builder +@Data +public class ThingRemovedOutboundMessage { + + @NotNull + private Headers headers; + + @Builder + @Data + public static class Headers { + private final String type = MessageTypeOutbound.THING_REMOVED.toString(); + private String thingId; + private String tenant; + + public Map asMap() { + final HashMap headers = new HashMap<>(); + if (this.type != null) { + headers.put(MessageHeader.TYPE.toString(), this.type); + } + if (this.thingId != null) { + headers.put(MessageHeader.THING_ID.toString(), this.thingId); + } + if (this.tenant != null) { + headers.put(MessageHeader.TENANT.toString(), this.tenant); + } + + return headers; + } + } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/hawkbit/model/outbound/UpdateActionStatusOutboundMessage.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/hawkbit/model/outbound/UpdateActionStatusOutboundMessage.java new file mode 100644 index 000000000..0320d8a12 --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/hawkbit/model/outbound/UpdateActionStatusOutboundMessage.java @@ -0,0 +1,91 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.hawkbit.model.outbound; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.validation.ConstraintViolationException; +import javax.validation.Valid; +import javax.validation.constraints.NotNull; + +import eu.arrowhead.core.hbconfmgr.hawkbit.model.ActionStatus; +import eu.arrowhead.core.hbconfmgr.hawkbit.model.EventType; +import eu.arrowhead.core.hbconfmgr.hawkbit.model.MessageHeader; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + + +/** + * Transfer object, encapsulating UPDATE_ACTION_STATUS requests, sent by the HawkBit client. + */ +@AllArgsConstructor +@NoArgsConstructor +@Data +@Builder +public class UpdateActionStatusOutboundMessage { + + @Valid + @NotNull + private UpdateActionStatusOutboundMessageBody body; + + @Valid + @NotNull + private UpdateActionStatusOutboundMessageHeaders headers; + + @AllArgsConstructor + @NoArgsConstructor + @Data + @Builder + public static class UpdateActionStatusOutboundMessageBody { + @NotNull + private Long actionId; + + @NotNull + private ActionStatus actionStatus; + + private Long softwareModuleId; + + private List message; + } + + @AllArgsConstructor + @NoArgsConstructor + @Data + @Builder + public static class UpdateActionStatusOutboundMessageHeaders { + @NotNull + private final String type = MessageTypeOutbound.EVENT.toString(); + + @NotNull + private final String topic = EventType.UPDATE_ACTION_STATUS.toString(); + + private String tenant; + + public Map asMap() throws ConstraintViolationException { + final HashMap headers = new HashMap<>(); + if (this.type != null) { + headers.put(MessageHeader.TYPE.toString(), this.type); + } + if (this.topic != null) { + headers.put(MessageHeader.TOPIC.toString(), this.topic); + } + if (this.tenant != null) { + headers.put(MessageHeader.TENANT.toString(), this.tenant); + } + + return headers; + } + } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/model/HawkbitActionUpdateStatus.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/model/HawkbitActionUpdateStatus.java new file mode 100644 index 000000000..4f176dafe --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/model/HawkbitActionUpdateStatus.java @@ -0,0 +1,34 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.model; + +import java.util.List; + +import eu.arrowhead.core.hbconfmgr.hawkbit.model.ActionStatus; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Internal model for representation of the update of a status of an action in hawkBit. + */ +@AllArgsConstructor +@NoArgsConstructor +@Data +@Builder +public class HawkbitActionUpdateStatus { + + private Long actionId; + private Long softwareModuleId; + private ActionStatus actionStatus; + private List message; +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/properties/SystemProperties.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/properties/SystemProperties.java new file mode 100644 index 000000000..a2283d194 --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/properties/SystemProperties.java @@ -0,0 +1,74 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.properties; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +import lombok.Data; + +/** + * This class provides all properties of the configuration system itself. + */ +@Data +public class SystemProperties { + + /** + * This is the address of the configuration system itself, e.g. "192.168.1.1" or + * "subdomain.domain.tld". + *

It must be not blank (validated with {@link NotBlank @NotBlank}). + */ + @NotBlank + private String address; + + /** + * This is the port of the configuration system itself, e.g. 8447. + *

It must be not null (validated with {@link NotNull @NotNull}). + */ + @NotNull + private Integer port; + + /** + * This is the name of the configuration system itself, e.g. "HAWKBITCONFIGURATIONSYSTEM". + *

In the context of Arrowhead the system (provider) name and certificate common name must match. + *

It must be not blank (validated with {@link NotBlank @NotBlank}). + */ + @NotBlank + private String name; + + /** + * This is the protocol of the provided service from the configuration system itself, e.g. "HTTP-SECURE-JSON". + *

It must be not blank (validated with {@link NotBlank @NotBlank}). + */ + @NotBlank + private String providedServiceInterface; + + /** + * This is the definition of the provided service from the configuration system itself, e.g. "definition5". + *

It must be not blank (validated with {@link NotBlank @NotBlank}). + */ + @NotBlank + private String providedServiceDefinition; + + /** + * This is the uri of the provided service from the configuration system itself, e.g "/". + *

It must be not blank (validated with {@link NotBlank @NotBlank}). + */ + @NotBlank + private String providedServiceUri; + + /** + * This is the version of the provided service from the configuration system itself, e.g. 2. + *

It must be not null (validated with {@link NotNull @NotNull}). + */ + @NotNull + private Integer providedServiceVersion; +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/security/CidValidator.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/security/CidValidator.java new file mode 100644 index 000000000..8e17f31f1 --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/security/CidValidator.java @@ -0,0 +1,33 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.security; + +import org.jose4j.jwt.JwtClaims; +import org.jose4j.jwt.consumer.ErrorCodeValidator; +import org.jose4j.jwt.consumer.ErrorCodes; +import org.jose4j.jwt.consumer.JwtContext; + +/** + * The cid validator checks that the claim "cid" is available in a JWT. + */ +public class CidValidator implements ErrorCodeValidator { + + @Override + public Error validate(final JwtContext jwtContext) { + final JwtClaims jwtClaims = jwtContext.getJwtClaims(); + final Object clientIdentifier = jwtClaims.getClaimValue(JwtAuthenticationToken.CLIENT_IDENTIFIER_CLAIM); + if (clientIdentifier == null ) { + return new Error(ErrorCodes.MISCELLANEOUS, "No client identifier (cid) claim is present."); + } + + return null; + } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/security/JwtAuthenticationFilter.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/security/JwtAuthenticationFilter.java new file mode 100644 index 000000000..b698ad82e --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/security/JwtAuthenticationFilter.java @@ -0,0 +1,147 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.security; + +import java.io.IOException; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.jose4j.jwa.AlgorithmConstraints; +import org.jose4j.jwe.ContentEncryptionAlgorithmIdentifiers; +import org.jose4j.jwe.KeyManagementAlgorithmIdentifiers; +import org.jose4j.jws.AlgorithmIdentifiers; +import org.jose4j.jwt.JwtClaims; +import org.jose4j.jwt.consumer.InvalidJwtException; +import org.jose4j.jwt.consumer.JwtConsumer; +import org.jose4j.jwt.consumer.JwtConsumerBuilder; +import org.springframework.http.HttpHeaders; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.filter.OncePerRequestFilter; + +import eu.arrowhead.core.hbconfmgr.service.ArrowheadService; +import lombok.extern.log4j.Log4j2; + +/** + * Implementation of JWT authentication filter for Spring Security. It supports bearer tokens (JWT) in the standard + * HTTP authentication header "Authorization". A subject is authenticated if it provides a valid JWT with all required + * claims according to {@link #validateJwtAndRetrieveClaims(String, PublicKey, PrivateKey)}. + */ +@Log4j2 +public class JwtAuthenticationFilter extends OncePerRequestFilter { + + private static final String RSA = "RSA"; + + /** + * The value of the header "Authorization" must start with "Bearer " according to + * RFC6750. + */ + private static final String AUTHENTICATION_SCHEME_BEARER = "Bearer "; + + private final ArrowheadService arrowheadService; + private final PrivateKey hawkbitConfigurationSystemPrivateKey; + private PublicKey authorizationSystemPublicKey; + + /** + * A filter for validation JWT. + * + * @param arrowheadService required to receive the public key for JWT validation + * @param configurationSystemPrivateKey the private key for JWT encryption + */ + public JwtAuthenticationFilter(final ArrowheadService arrowheadService, final PrivateKey configurationSystemPrivateKey) { + this.arrowheadService = arrowheadService; + this.hawkbitConfigurationSystemPrivateKey = configurationSystemPrivateKey; + init(); + } + + private void init() { + try { + final String authorizationSystemPublicKeyString = arrowheadService.receiveAuthorizationSystemPublicKey(); + this.authorizationSystemPublicKey = loadPublicKeyFromString(authorizationSystemPublicKeyString); + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + log.error("Public key from JWT provider couldn't be loaded", e); + } + } + + @Override + protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain) throws IOException, ServletException { + final String authorizationHeader = request.getHeader(HttpHeaders.AUTHORIZATION); + + if (this.authorizationSystemPublicKey == null || authorizationHeader == null || !authorizationHeader.startsWith(AUTHENTICATION_SCHEME_BEARER)) { + log.debug("No bearer token found"); + chain.doFilter(request, response); + return; + } + final String clientJwtString = authorizationHeader.replaceFirst(AUTHENTICATION_SCHEME_BEARER, ""); + + try { + + final SecurityContext context = SecurityContextHolder.createEmptyContext(); + final JwtClaims clientJwtClaims = validateJwtAndRetrieveClaims(clientJwtString, this.authorizationSystemPublicKey, this.hawkbitConfigurationSystemPrivateKey); + final JwtAuthenticationToken authentication = new JwtAuthenticationToken(clientJwtClaims, true); + context.setAuthentication(authentication); + + SecurityContextHolder.setContext(context); + } catch (final InvalidJwtException e) { + log.error("The provided JWT is not valid", e); + } finally { + chain.doFilter(request, response); + } + } + + /** + * Validates a JWT and retrieves it claims. + * + * @param clientJwt the JWT that should be validated + * @param publicKey the public key for verification of the JWT + * @param privateKey the private key for encryption of the JWT + * @return on success: all claims of the JWT + * @throws InvalidJwtException if the JWT is not valid + */ + private JwtClaims validateJwtAndRetrieveClaims(final String clientJwt, final PublicKey publicKey, final PrivateKey privateKey) throws InvalidJwtException { + final JwtConsumer jwtConsumer = new JwtConsumerBuilder() + .registerValidator(new CidValidator()) + .setRequireJwtId() + .setRequireNotBefore() + .setEnableRequireEncryption() + .setEnableRequireIntegrity() + .setDecryptionKey(privateKey) + .setVerificationKey(publicKey) + .setJwsAlgorithmConstraints(new AlgorithmConstraints( + AlgorithmConstraints.ConstraintType.WHITELIST, + AlgorithmIdentifiers.RSA_USING_SHA512)) + .setJweAlgorithmConstraints(new AlgorithmConstraints( + AlgorithmConstraints.ConstraintType.WHITELIST, + KeyManagementAlgorithmIdentifiers.RSA_OAEP_256)) + .setJweContentEncryptionAlgorithmConstraints(new AlgorithmConstraints( + AlgorithmConstraints.ConstraintType.WHITELIST, + ContentEncryptionAlgorithmIdentifiers.AES_256_CBC_HMAC_SHA_512)) + .build(); + + return jwtConsumer.processToClaims(clientJwt); + } + + private PublicKey loadPublicKeyFromString(final String publicKeyString) throws NoSuchAlgorithmException, InvalidKeySpecException { + final KeyFactory kf = KeyFactory.getInstance(RSA); + final byte[] keyBytes = Base64.getDecoder().decode(publicKeyString); + return kf.generatePublic(new X509EncodedKeySpec(keyBytes)); + } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/security/JwtAuthenticationToken.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/security/JwtAuthenticationToken.java new file mode 100644 index 000000000..1e86612e4 --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/security/JwtAuthenticationToken.java @@ -0,0 +1,55 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.security; + +import org.jose4j.jwt.JwtClaims; +import org.springframework.security.authentication.AbstractAuthenticationToken; + +public class JwtAuthenticationToken extends AbstractAuthenticationToken { + private static final long serialVersionUID = 1L; + + public static final String CLIENT_IDENTIFIER_CLAIM = "cid"; + + private final String clientIdentifier; + private final JwtClaims jwtClaims; + + /** + * Creates a JWT (JSON Web Token) authentication token that can be used in the context of Spring Security. + * + * @param jwtClaims the claims of the JWT itself + * @param authenticated if the JWT is already validated and therefore authenticated + */ + public JwtAuthenticationToken(final JwtClaims jwtClaims, final boolean authenticated) { + super(null); + this.jwtClaims = jwtClaims; + this.clientIdentifier = String.valueOf(jwtClaims.getClaimValue(CLIENT_IDENTIFIER_CLAIM)); + super.setAuthenticated(authenticated); + } + + /** + * + * @return the client identifier (cid claim) of the JWT + */ + @Override + public String getName() { + return this.clientIdentifier; + } + + @Override + public Object getPrincipal() { + return this.jwtClaims; + } + + @Override + public Object getCredentials() { + return this.jwtClaims.toString(); + } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/service/ArrowheadService.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/service/ArrowheadService.java new file mode 100644 index 000000000..1010f538d --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/service/ArrowheadService.java @@ -0,0 +1,42 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import eu.arrowhead.core.hbconfmgr.arrowhead.ArrowheadAuthorizationSystemClient; + +/** + * This is the Arrowhead service that implements business logic specifically for the configuration system. + */ +@Service +public class ArrowheadService { + + private final ArrowheadAuthorizationSystemClient authorizationSystemClient; + + @Autowired + public ArrowheadService(final ArrowheadAuthorizationSystemClient arrowheadAuthorizationSystemClient) { + this.authorizationSystemClient = arrowheadAuthorizationSystemClient; + } + + /** + * Returns the public key of the Arrowhead authorization system. The public key is used for validating JWT issued + * by the authorization system itself. + *

+ * The result of this method is cached, the configuration for caching can be found in resources/ehcache.xml. + * + * @return public key of Arrowhead authorization system + */ + public String receiveAuthorizationSystemPublicKey() { + return this.authorizationSystemClient.getPublicKey(); + } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/service/HawkbitService.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/service/HawkbitService.java new file mode 100644 index 000000000..4124a9c29 --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/service/HawkbitService.java @@ -0,0 +1,111 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.service; + +import java.io.IOException; + +import javax.validation.ConstraintViolationException; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import eu.arrowhead.core.hbconfmgr.Constants; +import eu.arrowhead.core.hbconfmgr.hawkbit.HawkbitDmfOutboundClient; +import eu.arrowhead.core.hbconfmgr.hawkbit.model.outbound.ThingCreatedOutboundMessage; +import eu.arrowhead.core.hbconfmgr.hawkbit.model.outbound.ThingRemovedOutboundMessage; +import eu.arrowhead.core.hbconfmgr.hawkbit.model.outbound.UpdateActionStatusOutboundMessage; +import eu.arrowhead.core.hbconfmgr.model.HawkbitActionUpdateStatus; +import lombok.extern.log4j.Log4j2; + +/** + * This is the hawkBit service that implements business logic specifically for + * the configuration system. + */ +@Log4j2 +@Service +public class HawkbitService { + + private final HawkbitDmfOutboundClient hawkbitDmfClient; + private final String hawkbitTenant; + + @Autowired + public HawkbitService(@Value(Constants.HAWKBIT_TENANT) final String hawkbitTenant, final HawkbitDmfOutboundClient hawkbitDmfClient) { + this.hawkbitDmfClient = hawkbitDmfClient; + this.hawkbitTenant = hawkbitTenant; + } + + /** + * Create a new device in HawkBit. + * + * @param deviceId the id of the device that should be create + */ + public void createDevice(final String deviceId) { + try { + final ThingCreatedOutboundMessage message = ThingCreatedOutboundMessage.builder() + .body( + ThingCreatedOutboundMessage.Body.builder() + .name(deviceId) + .build() + ) + .headers( + ThingCreatedOutboundMessage.Headers.builder() + .thingId(deviceId) + .tenant(hawkbitTenant) + .build() + ) + .build(); + this.hawkbitDmfClient.createThing(message); + } catch (final IOException e) { + log.error("Creating new device was not possible", e); + } + } + + public void removeDevice(final String deviceId) { + final ThingRemovedOutboundMessage message = ThingRemovedOutboundMessage.builder() + .headers( + ThingRemovedOutboundMessage.Headers.builder() + .thingId(deviceId) + .tenant(hawkbitTenant) + .build() + ) + .build(); + + try { + this.hawkbitDmfClient.removeThing(message); + } catch (final IOException e) { + log.error("Deleting the device was not possible", e); + } + } + + /** + * Update the status of an action in hawkBit. + * + * @param actionUpdateStatus contains the information for the new status of an + * action + * @throws ConstraintViolationException if a specification for sending a message + * to hawkBit is not adhered + * @throws IOException if there is a connection problem with + * hawkBit + */ + public void updateActionStatus(final HawkbitActionUpdateStatus actionUpdateStatus) throws ConstraintViolationException, IOException { + final UpdateActionStatusOutboundMessage message = UpdateActionStatusOutboundMessage.builder() + .body(UpdateActionStatusOutboundMessage.UpdateActionStatusOutboundMessageBody.builder() + .actionId(actionUpdateStatus.getActionId()) + .actionStatus(actionUpdateStatus.getActionStatus()) + .softwareModuleId(actionUpdateStatus.getSoftwareModuleId()) + .message(actionUpdateStatus.getMessage()).build()) + .headers(UpdateActionStatusOutboundMessage.UpdateActionStatusOutboundMessageHeaders.builder() + .tenant(hawkbitTenant).build()) + .build(); + this.hawkbitDmfClient.updateActionStatus(message); + } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/service/WebSocketService.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/service/WebSocketService.java new file mode 100644 index 000000000..41081f3d8 --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/service/WebSocketService.java @@ -0,0 +1,80 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.service; + +import java.io.IOException; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import eu.arrowhead.core.hbconfmgr.hawkbit.model.inbound.CancelDownloadInboundMessage; +import eu.arrowhead.core.hbconfmgr.hawkbit.model.inbound.DownloadRequestInboundMessage; +import eu.arrowhead.core.hbconfmgr.hawkbit.model.inbound.MessageTypeInbound; +import eu.arrowhead.core.hbconfmgr.hawkbit.model.inbound.ThingDeletedInboundMessage; +import eu.arrowhead.core.hbconfmgr.websocket.DeviceNotConnectedException; +import eu.arrowhead.core.hbconfmgr.websocket.WebSocketSender; +import eu.arrowhead.core.hbconfmgr.websocket.model.DeviceMessage; +import lombok.extern.log4j.Log4j2; + +/** + * This is the web socket service that implements business logic specifically + * for the configuration system. + */ +@Log4j2 +@Service +public class WebSocketService { + + private final WebSocketSender wsSender; + + @Autowired + public WebSocketService(final WebSocketSender wsSender) throws IOException { + this.wsSender = wsSender; + } + + public void sendDownloadEventMessage(final DownloadRequestInboundMessage message) throws IOException, DeviceNotConnectedException { + log.debug("Message {}", message); + + final String clientId = message.getHeaders().getThingId(); + + final DeviceMessage deviceMessage = DeviceMessage.builder() + .type(MessageTypeInbound.EVENT.toString()) + .message(message) + .build(); + + this.wsSender.sendMessage(clientId, deviceMessage); + } + + public void sendThingDeletedMessage(final ThingDeletedInboundMessage message) throws IOException, DeviceNotConnectedException { + log.debug("Message {}", message); + + final String clientId = message.getHeaders().getThingId(); + + final DeviceMessage deviceMessage = DeviceMessage.builder() + .type(MessageTypeInbound.THING_DELETED.toString()) + .message(message) + .build(); + + this.wsSender.sendMessage(clientId, deviceMessage); + } + + public void sendCancelDownloadMessage(final CancelDownloadInboundMessage message) throws IOException, DeviceNotConnectedException { + log.debug("Message {}", message); + + final String clientId = message.getHeaders().getThingId(); + + final DeviceMessage deviceMessage = DeviceMessage.builder() + .type(MessageTypeInbound.EVENT.toString()) + .message(message) + .build(); + + this.wsSender.sendMessage(clientId, deviceMessage); + } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/websocket/DeviceNotConnectedException.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/websocket/DeviceNotConnectedException.java new file mode 100644 index 000000000..cc44aac99 --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/websocket/DeviceNotConnectedException.java @@ -0,0 +1,30 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.websocket; + +public class DeviceNotConnectedException extends Exception { + private static final long serialVersionUID = 7329483234L; + + public DeviceNotConnectedException() { super(); } + public DeviceNotConnectedException(final String message) { super(message); } + public DeviceNotConnectedException(final String message, final Throwable cause) { super(message, cause); } + public DeviceNotConnectedException(final Throwable cause) { super(cause); } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/websocket/WebSocketController.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/websocket/WebSocketController.java new file mode 100644 index 000000000..a91ec010e --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/websocket/WebSocketController.java @@ -0,0 +1,73 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.websocket; + +import java.io.IOException; +import java.util.Map; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import eu.arrowhead.core.hbconfmgr.service.HawkbitService; +import eu.arrowhead.core.hbconfmgr.websocket.model.ActionUpdateStatusMapper; +import eu.arrowhead.core.hbconfmgr.websocket.model.UpdateActionRequestDTO; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.handler.AbstractWebSocketHandler; + +import lombok.extern.log4j.Log4j2; + +@Log4j2 +@Controller +public class WebSocketController extends AbstractWebSocketHandler { + + private final HawkbitService hawkbitService; + private final ObjectMapper objectMapper; + private final Map webSocketSessionMap; + + @Autowired + public WebSocketController(final HawkbitService hawkbitService, final Map webSocketSessionMap) { + this.objectMapper = new ObjectMapper(); + this.hawkbitService = hawkbitService; + this.webSocketSessionMap = webSocketSessionMap; + } + + @Override + public void afterConnectionEstablished(final WebSocketSession session) throws IOException { + if (session.getPrincipal() != null) { + final String clientId = session.getPrincipal().getName(); + this.hawkbitService.createDevice(clientId); + this.webSocketSessionMap.put(clientId, session); + } else { + session.close(); + } + } + + @Override + public void afterConnectionClosed(final WebSocketSession session, final CloseStatus status) { + if (session.getPrincipal() != null) { + this.webSocketSessionMap.remove(session.getPrincipal().getName()); + } + } + + @Override + protected void handleTextMessage(final WebSocketSession session, final TextMessage message) { + try { + final UpdateActionRequestDTO request = objectMapper.readValue(message.getPayload(), UpdateActionRequestDTO.class); + this.hawkbitService.updateActionStatus(ActionUpdateStatusMapper.mapToActionUpdateStatus(request)); + } catch (final Exception e) { + log.error("Web socket text message could not be handled", e); + } + } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/websocket/WebSocketSender.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/websocket/WebSocketSender.java new file mode 100644 index 000000000..1802fa924 --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/websocket/WebSocketSender.java @@ -0,0 +1,51 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.websocket; + +import java.io.IOException; +import java.util.Map; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import eu.arrowhead.core.hbconfmgr.Constants; +import eu.arrowhead.core.hbconfmgr.websocket.model.DeviceMessage; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.DependsOn; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; + +@DependsOn(Constants.GUARD_BEAN) +@Component +public class WebSocketSender { + + private final Map webSocketSessionMap; + private final ObjectMapper objectMapper; + + @Autowired + public WebSocketSender(final Map webSocketSessionMap) { + this.webSocketSessionMap = webSocketSessionMap; + this.objectMapper = new ObjectMapper(); + } + + public void sendMessage(final String thingId, final DeviceMessage message) throws IOException, DeviceNotConnectedException { + if (this.webSocketSessionMap.containsKey(thingId)) { + final String body = this.objectMapper.writeValueAsString(message); + final TextMessage wsMessage = new TextMessage(body); + + final WebSocketSession session = this.webSocketSessionMap.get(thingId); + session.sendMessage(wsMessage); + } else { + throw new DeviceNotConnectedException("The device " + thingId + " is currently not connected."); + } + } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/websocket/model/ActionUpdateStatusMapper.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/websocket/model/ActionUpdateStatusMapper.java new file mode 100644 index 000000000..d3c586ac0 --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/websocket/model/ActionUpdateStatusMapper.java @@ -0,0 +1,30 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.websocket.model; + +import eu.arrowhead.core.hbconfmgr.hawkbit.model.ActionStatus; +import eu.arrowhead.core.hbconfmgr.model.HawkbitActionUpdateStatus; + +public class ActionUpdateStatusMapper { + + private ActionUpdateStatusMapper() { + throw new UnsupportedOperationException(); + } + + public static HawkbitActionUpdateStatus mapToActionUpdateStatus(final UpdateActionRequestDTO input) throws IllegalArgumentException { + return HawkbitActionUpdateStatus.builder() + .actionId(input.getActionId()) + .actionStatus(ActionStatus.valueOf(input.getActionStatus())) + .message(input.getMessage()) + .softwareModuleId(input.getSoftwareModuleId()) + .build(); + } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/websocket/model/DeviceMessage.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/websocket/model/DeviceMessage.java new file mode 100644 index 000000000..d5f304ce5 --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/websocket/model/DeviceMessage.java @@ -0,0 +1,23 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.websocket.model; + +import eu.arrowhead.core.hbconfmgr.hawkbit.model.inbound.InboundMessage; +import lombok.Builder; +import lombok.Data; + +@Builder +@Data +public class DeviceMessage { + + private final String type; + private InboundMessage message; +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/websocket/model/UpdateActionRequestDTO.java b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/websocket/model/UpdateActionRequestDTO.java new file mode 100644 index 000000000..ad46d1051 --- /dev/null +++ b/hawkbit-configuration-manager/src/main/java/eu/arrowhead/core/hbconfmgr/websocket/model/UpdateActionRequestDTO.java @@ -0,0 +1,30 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.websocket.model; + +import java.util.List; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +@Data +@Builder +public class UpdateActionRequestDTO { + + private Long actionId; + private Long softwareModuleId; + private String actionStatus; + private List message; +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/hawkbit-configuration-manager/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 000000000..3167764e8 --- /dev/null +++ b/hawkbit-configuration-manager/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,72 @@ +{"properties": [ + { + "name": "system.name", + "type": "java.lang.String", + "description": "A description for 'system.name'" + }, + { + "name": "system.port", + "type": "java.lang.Integer", + "description": "A description for 'system.port'" + }, + { + "name": "system.providedServiceInterface", + "type": "java.lang.String", + "description": "A description for 'system.providedServiceInterface'" + }, + { + "name": "system.providedServiceDefinition", + "type": "java.lang.String", + "description": "A description for 'system.providedServiceDefinition'" + }, + { + "name": "system.providedServiceVersion", + "type": "java.lang.Integer", + "description": "A description for 'system.providedServiceVersion'" + }, + { + "name": "system.providedServiceUri", + "type": "java.lang.String", + "description": "A description for 'system.providedServiceUri'" + }, + { + "name": "sr_address", + "type": "java.lang.String", + "description": "A description for 'sr_address'" + }, + { + "name": "sr_port", + "type": "java.lang.Integer", + "description": "A description for 'sr_port'" + }, + { + "name": "hawkbit.host", + "type": "java.lang.String", + "description": "A description for 'hawkbit.host'" + }, + { + "name": "hawkbit.port", + "type": "java.lang.Integer", + "description": "A description for 'hawkbit.port'" + }, + { + "name": "hawkbit.username", + "type": "java.lang.String", + "description": "A description for 'hawkbit.username'" + }, + { + "name": "hawkbit.password", + "type": "java.lang.String", + "description": "A description for 'hawkbit.password'" + }, + { + "name": "hawkbit.tenant", + "type": "java.lang.String", + "description": "A description for 'hawkbit.tenant'" + }, + { + "name": "system.address", + "type": "java.lang.String", + "description": "A description for 'system.address'" + } +]} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/resources/application.properties b/hawkbit-configuration-manager/src/main/resources/application.properties new file mode 100644 index 000000000..0409c7ace --- /dev/null +++ b/hawkbit-configuration-manager/src/main/resources/application.properties @@ -0,0 +1,56 @@ +############################################ +### APPLICATION PARAMETERS ### +############################################ + +# Hawkbit Configuration Manager web-server parameters +server.address=0.0.0.0 +server.port=8447 + +############################################ +### CUSTOM PARAMETERS ### +############################################ + +# These are the properties of the configuration system itself +# The properties are loaded into the class eu.arrowhead.core.hbconfmgr.properties.SystemProperties +system.name=HAWKBITCONFIGURATIONMANAGER +system.address=127.0.0.1 +system.port=8447 +# This is the protocol of the provided service from the configuration system itself, e.g. "HTTP-SECURE-JSON" +system.providedServiceInterface=HTTP-SECURE-JSON +# This is the definition of the provided service from the configuration system itself, e.g. "definition5" +system.providedServiceDefinition=definition1 +# This is the uri of the provided service from the configuration system itself, e.g "/" +system.providedServiceUri=/ +# This is the version of the provided service from the configuration system itself, e.g. 2 +system.providedServiceVersion=1 + +# Service Registry web-server parameters +sr_address=127.0.0.1 +sr_port=8443 + +# These are the connection parameters for the hawkBit DMF API +hawkbit.host=localhost +hawkbit.port=5672 +hawkbit.username=guest +hawkbit.password=guest +# This is the tenant in hawkBit itself +hawkbit.tenant=DEFAULT + +###############&############################ +### SECURE MODE ### +############################################ + +# configure secure mode + +# This system works only in secure mode. Do not modify this property. +server.ssl.enabled=true + +server.ssl.key-store-type=PKCS12 +server.ssl.key-store=classpath:certificates/hawkbitconfigurationmanager.p12 +server.ssl.key-store-password=123456 +server.ssl.key-alias=hawkbitconfigurationmanager +server.ssl.key-password=123456 +server.ssl.client-auth=need +server.ssl.trust-store-type=PKCS12 +server.ssl.trust-store=classpath:certificates/truststore.p12 +server.ssl.trust-store-password=123456 \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/main/resources/certificates/hawkbitconfigurationmanager.p12 b/hawkbit-configuration-manager/src/main/resources/certificates/hawkbitconfigurationmanager.p12 new file mode 100644 index 000000000..d7560b726 Binary files /dev/null and b/hawkbit-configuration-manager/src/main/resources/certificates/hawkbitconfigurationmanager.p12 differ diff --git a/hawkbit-configuration-manager/src/main/resources/certificates/truststore.p12 b/hawkbit-configuration-manager/src/main/resources/certificates/truststore.p12 new file mode 100644 index 000000000..567814b1e Binary files /dev/null and b/hawkbit-configuration-manager/src/main/resources/certificates/truststore.p12 differ diff --git a/hawkbit-configuration-manager/src/main/resources/log4j2.xml b/hawkbit-configuration-manager/src/main/resources/log4j2.xml new file mode 100644 index 000000000..692f06f1c --- /dev/null +++ b/hawkbit-configuration-manager/src/main/resources/log4j2.xml @@ -0,0 +1,31 @@ + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} %5p ${hostName} --- [%15.15t] %-40.40c{1.} : %m%n%ex + + INFO + . + + + + + + + + ${LOG_PATTERN} + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/arrowhead/ArrowheadAuthorizationSystemClientTest.java b/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/arrowhead/ArrowheadAuthorizationSystemClientTest.java new file mode 100644 index 000000000..5101802a5 --- /dev/null +++ b/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/arrowhead/ArrowheadAuthorizationSystemClientTest.java @@ -0,0 +1,48 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.arrowhead; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.Test; + +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; + +public class ArrowheadAuthorizationSystemClientTest { + + @Test + public void givenPublicKeyIsAvailable_whenGetPublicKey_thenPublicKeyReceived() throws Exception { + final MockWebServer mockWebServer = new MockWebServer(); + mockWebServer.enqueue(new MockResponse() + .setResponseCode(200) + .addHeader("Content-Type", "application/json") + .setBody("\"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvmaFymz4cSB+LP3pixhhgrczq8G2rWTMyyajMQNSXOuvGDBhB7S32rl/+B8VUvTrYBx7ab8AM/TxrDwWAzsQ3p3oo0+5NNibHpZ8QucWIXqbpQqFobiNWx+ogoBLV4UfJekxXYH54QrTdwkvlOFXxjkL11uwBKEsZ0TzKgGaRs+iR80sjDeicnpPlMm0RbpXjx10OzmjqfC1Wrusyl2gjsW9ySfwJ1461n6cPndn8csvC37IHdHpkKTRlcDDPG6/GN3+vfUeznA7zGFWv2IOTzsuruehXaOuDNgekvREK3Xh4BpvGTZ6oEFVmr5853U3PS6ExKlvwR8NwFiPXI9LAQIDAQAB\"")); + mockWebServer.start(); + + final String baseUrl = mockWebServer.url("").toString(); + final ArrowheadAuthorizationSystemClient client = new ArrowheadAuthorizationSystemClient(baseUrl); + + final String publicKey = client.getPublicKey(); + + final RecordedRequest recordedRequest = mockWebServer.takeRequest(10, TimeUnit.SECONDS); + assertThat(recordedRequest).isNotNull(); + assertThat(recordedRequest.getMethod()).isEqualTo("GET"); + assertThat(recordedRequest.getPath()).isEqualTo("/authorization/publickey"); + + assertThat(publicKey).isEqualTo("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvmaFymz4cSB+LP3pixhhgrczq8G2rWTMyyajMQNSXOuvGDBhB7S32rl/+B8VUvTrYBx7ab8AM/TxrDwWAzsQ3p3oo0+5NNibHpZ8QucWIXqbpQqFobiNWx+ogoBLV4UfJekxXYH54QrTdwkvlOFXxjkL11uwBKEsZ0TzKgGaRs+iR80sjDeicnpPlMm0RbpXjx10OzmjqfC1Wrusyl2gjsW9ySfwJ1461n6cPndn8csvC37IHdHpkKTRlcDDPG6/GN3+vfUeznA7zGFWv2IOTzsuruehXaOuDNgekvREK3Xh4BpvGTZ6oEFVmr5853U3PS6ExKlvwR8NwFiPXI9LAQIDAQAB"); + + mockWebServer.close(); + } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/arrowhead/ArrowheadServiceRegistryClientTest.java b/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/arrowhead/ArrowheadServiceRegistryClientTest.java new file mode 100644 index 000000000..d6c6cfc6a --- /dev/null +++ b/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/arrowhead/ArrowheadServiceRegistryClientTest.java @@ -0,0 +1,248 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.arrowhead; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.catchThrowableOfType; + +import java.util.Collections; +import java.util.Locale; +import java.util.concurrent.TimeUnit; + +import javax.validation.ConstraintViolationException; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; +import org.skyscreamer.jsonassert.JSONCompareMode; +import org.springframework.http.HttpStatus; +import org.springframework.web.reactive.function.client.WebClientResponseException; + +import eu.arrowhead.core.hbconfmgr.arrowhead.model.request.ServiceRegistryRequestDTO; +import eu.arrowhead.core.hbconfmgr.arrowhead.model.request.SystemRequestDTO; +import eu.arrowhead.core.hbconfmgr.arrowhead.model.response.ServiceDefinitionResponseDTO; +import eu.arrowhead.core.hbconfmgr.arrowhead.model.response.ServiceInterfaceResponseDTO; +import eu.arrowhead.core.hbconfmgr.arrowhead.model.response.ServiceRegistryResponseDTO; +import eu.arrowhead.core.hbconfmgr.arrowhead.model.response.SystemResponseDTO; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; + +public class ArrowheadServiceRegistryClientTest { + + private static Locale defaultLocale; + + @BeforeAll + public static void setUp() { + defaultLocale = Locale.getDefault(); + Locale.setDefault(Locale.UK); + } + + @AfterAll + public static void tearDown() { + Locale.setDefault(defaultLocale); + } + + @Test + public void givenServiceIsNotRegistered_whenRegisterService_thenServiceIsRegistered() throws Exception { + final MockWebServer mockWebServer = new MockWebServer(); + mockWebServer.enqueue(new MockResponse() + .setResponseCode(201) + .addHeader("Content-Type", "application/json") + .setBody("{\n" + + " \"id\": 22,\n" + + " \"serviceDefinition\": {\n" + + " \"id\": 7,\n" + + " \"serviceDefinition\": \"definition3\",\n" + + " \"createdAt\": \"2020-04-21T11:27:39Z\",\n" + + " \"updatedAt\": \"2020-04-21T11:27:39Z\"\n" + + " },\n" + + " \"provider\": {\n" + + " \"id\": 9,\n" + + " \"systemName\": \"conf-system\",\n" + + " \"address\": \"192.168.1.1\",\n" + + " \"port\": 1234,\n" + + " \"authenticationInfo\": \"test\",\n" + + " \"createdAt\": \"2020-05-05T06:37:19Z\",\n" + + " \"updatedAt\": \"2020-05-05T06:37:19Z\"\n" + + " },\n" + + " \"serviceUri\": \"/\",\n" + + " \"secure\": \"TOKEN\",\n" + + " \"version\": 1,\n" + + " \"interfaces\": [{\n" + + " \"id\": 3,\n" + + " \"interfaceName\": \"HTTP-SECURE-JSON\",\n" + + " \"createdAt\": \"2020-04-21T11:27:39Z\",\n" + + " \"updatedAt\": \"2020-04-21T11:27:39Z\"\n" + + " }\n" + + " ],\n" + + " \"createdAt\": \"2020-05-05T06:37:19Z\",\n" + + " \"updatedAt\": \"2020-05-05T06:37:19Z\"\n" + + "}")); + mockWebServer.start(); + + final String baseUrl = mockWebServer.url("").toString(); + final ArrowheadServiceRegistryClient client = new ArrowheadServiceRegistryClient(baseUrl); + final ServiceRegistryRequestDTO requestDTO = ServiceRegistryRequestDTO.builder() + .serviceDefinition("definition3") + .providerSystem(SystemRequestDTO.builder() + .systemName("conf-system") + .address("192.168.1.1") + .port(1234) + .authenticationInfo("test") + .build()) + .serviceUri("/") + .secure(ServiceRegistryRequestDTO.SecurityLevel.TOKEN) + .version(1) + .interfaces(Collections.singletonList("HTTP-SECURE-JSON")) + .build(); + final ServiceRegistryResponseDTO responseDTO = client.registerService(requestDTO); + + final RecordedRequest recordedRequest = mockWebServer.takeRequest(10, TimeUnit.SECONDS); + assertThat(recordedRequest).isNotNull(); + assertThat(recordedRequest.getMethod()).isEqualTo("POST"); + assertThat(recordedRequest.getPath()).isEqualTo("/serviceregistry/register"); + JSONAssert.assertEquals("{\n" + + " \"serviceDefinition\": \"definition3\",\n" + + " \"providerSystem\": {\n" + + " \"systemName\": \"conf-system\",\n" + + " \"address\": \"192.168.1.1\",\n" + + " \"port\": 1234,\n" + + " \"authenticationInfo\": \"test\",\n" + + " \"metadata\": null\n" + + " },\n" + + " \"serviceUri\": \"/\",\n" + + " \"endOfValidity\": null,\n" + + " \"secure\": \"TOKEN\",\n" + + " \"metadata\": null,\n" + + " \"version\": 1,\n" + + " \"interfaces\": [\"HTTP-SECURE-JSON\"]\n" + + "}", recordedRequest.getBody().readUtf8(), JSONCompareMode.NON_EXTENSIBLE); + + assertThat(responseDTO).isEqualTo(ServiceRegistryResponseDTO.builder() + .id(22L) + .serviceDefinition(ServiceDefinitionResponseDTO.builder() + .id(7L) + .serviceDefinition("definition3") + .createdAt("2020-04-21T11:27:39Z") + .updatedAt("2020-04-21T11:27:39Z") + .build()) + .provider(SystemResponseDTO.builder() + .id(9L) + .systemName("conf-system") + .address("192.168.1.1") + .port(1234) + .authenticationInfo("test") + .createdAt("2020-05-05T06:37:19Z") + .updatedAt("2020-05-05T06:37:19Z") + .build()) + .serviceUri("/") + .secure("TOKEN") + .version(1) + .interfaces(Collections.singletonList(ServiceInterfaceResponseDTO.builder() + .id(3L) + .interfaceName("HTTP-SECURE-JSON") + .createdAt("2020-04-21T11:27:39Z") + .updatedAt("2020-04-21T11:27:39Z") + .build())) + .createdAt("2020-05-05T06:37:19Z") + .updatedAt("2020-05-05T06:37:19Z") + .build()); + mockWebServer.close(); + } + + @Test + public void givenServiceIsAlreadyRegistered_whenRegisterService_thenWebClientResponseExceptionIsThrown() throws Exception { + final MockWebServer mockWebServer = new MockWebServer(); + mockWebServer.enqueue(new MockResponse() + .setResponseCode(400) + .addHeader("Content-Type", "application/json") + .setBody("{\n" + + " \"errorMessage\": \"Service Registry entry with provider: (conf-system, 192.168.1.1:1234) and service definition: definition3 already exists.\",\n" + + " \"errorCode\": 400,\n" + + " \"exceptionType\": \"INVALID_PARAMETER\"\n" + + "}")); + mockWebServer.start(); + + final String baseUrl = mockWebServer.url("").toString(); + final ArrowheadServiceRegistryClient client = new ArrowheadServiceRegistryClient(baseUrl); + final ServiceRegistryRequestDTO requestDTO = ServiceRegistryRequestDTO.builder() + .serviceDefinition("definition3") + .providerSystem(SystemRequestDTO.builder() + .systemName("conf-system") + .address("192.168.1.1") + .port(1234) + .authenticationInfo("test") + .build()) + .serviceUri("/") + .secure(ServiceRegistryRequestDTO.SecurityLevel.TOKEN) + .version(1) + .interfaces(Collections.singletonList("HTTP-SECURE-JSON")) + .build(); + + final WebClientResponseException exception = catchThrowableOfType(() -> + client.registerService(requestDTO), WebClientResponseException.class); + + assertThat(exception).hasMessageContaining("400 Bad Request from POST"); + assertThat(exception.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); + + mockWebServer.close(); + } + + @Test + public void givenServiceRegistryRequestDTOIsInvalid_whenRegisterService_thenConstraintViolationExceptionIsThrown() throws Exception { + final MockWebServer mockWebServer = new MockWebServer(); + mockWebServer.start(); + + final String baseUrl = mockWebServer.url("").toString(); + final ArrowheadServiceRegistryClient client = new ArrowheadServiceRegistryClient(baseUrl); + final ServiceRegistryRequestDTO requestDTO = ServiceRegistryRequestDTO.builder().build(); + + assertThatThrownBy(() -> client.registerService(requestDTO)) + .isInstanceOf(ConstraintViolationException.class) + .hasMessageContaining("providerSystem: must not be null") + .hasMessageContaining("serviceDefinition: must not be null") + .hasMessageContaining("interfaces: must not be empty") + .hasMessageContaining("serviceUri: must not be null"); + assertThat(mockWebServer.getRequestCount()).isZero(); + + mockWebServer.close(); + } + + @Test + public void givenSystemRequestDTOIsInvalid_whenRegisterService_thenConstraintViolationExceptionIsThrown() throws Exception { + final MockWebServer mockWebServer = new MockWebServer(); + mockWebServer.start(); + + final String baseUrl = mockWebServer.url("").toString(); + final ArrowheadServiceRegistryClient client = new ArrowheadServiceRegistryClient(baseUrl); + final ServiceRegistryRequestDTO requestDTO = ServiceRegistryRequestDTO.builder() + .serviceDefinition("definition3") + .providerSystem(SystemRequestDTO.builder().build()) + .serviceUri("/") + .secure(ServiceRegistryRequestDTO.SecurityLevel.TOKEN) + .version(1) + .interfaces(Collections.singletonList("HTTP-SECURE-JSON")) + .build(); + + assertThatThrownBy(() -> client.registerService(requestDTO)) + .isInstanceOf(ConstraintViolationException.class) + .hasMessageContaining("providerSystem.authenticationInfo: must not be null") + .hasMessageContaining("providerSystem.address: must not be null") + .hasMessageContaining("providerSystem.port: must not be null") + .hasMessageContaining("providerSystem.systemName: must not be null"); + assertThat(mockWebServer.getRequestCount()).isZero(); + + mockWebServer.close(); + } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/arrowhead/CertificateCheckFailureIT.java b/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/arrowhead/CertificateCheckFailureIT.java new file mode 100644 index 000000000..88a54951c --- /dev/null +++ b/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/arrowhead/CertificateCheckFailureIT.java @@ -0,0 +1,56 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.arrowhead; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.nio.channels.ClosedChannelException; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.util.ReflectionTestUtils; + +import com.fasterxml.jackson.core.JsonProcessingException; + +import eu.arrowhead.core.hbconfmgr.config.ArrowheadConfig; +import eu.arrowhead.core.hbconfmgr.config.InitArrowheadMockServers; + + +@ActiveProfiles("test") +public class CertificateCheckFailureIT { + private static InitArrowheadMockServers initMockServer; + + + @BeforeAll + public static void beforeAll() throws JsonProcessingException { + CertificateCheckFailureIT.initMockServer = new InitArrowheadMockServers(); + CertificateCheckFailureIT.initMockServer.setUp(false); + } + + @Test + public void testMissingServerCertificatesServiceRegistry() { + try { + ArrowheadConfig config = new ArrowheadConfig(); + ReflectionTestUtils.setField(config, "serviceRegistryAddress", "localhost"); + ReflectionTestUtils.setField(config, "serviceRegistryPort", 8443); + config.init(); + } catch (final Exception e) { + assertEquals(ClosedChannelException.class, e.getCause().getClass()); + } + } + + @AfterAll + public static void afterAll() { + CertificateCheckFailureIT.initMockServer.shutDown(); + } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/arrowhead/CoreSystemsFailure.java b/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/arrowhead/CoreSystemsFailure.java new file mode 100644 index 000000000..2015a0ce7 --- /dev/null +++ b/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/arrowhead/CoreSystemsFailure.java @@ -0,0 +1,61 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.arrowhead; + +import static org.junit.Assert.assertThrows; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.net.ConnectException; +import java.util.Collections; + +import org.junit.jupiter.api.Test; + +import eu.arrowhead.core.hbconfmgr.arrowhead.model.request.ServiceRegistryRequestDTO; +import eu.arrowhead.core.hbconfmgr.arrowhead.model.request.SystemRequestDTO; + + +public class CoreSystemsFailure { + @Test + public void testServiceRegistryFailure() { + final ArrowheadServiceRegistryClient srClient = new ArrowheadServiceRegistryClient("https://localhost:8443"); + + final ServiceRegistryRequestDTO requestDTO = ServiceRegistryRequestDTO.builder() + .serviceDefinition("definition3") + .providerSystem(SystemRequestDTO.builder() + .systemName("conf-system") + .address("192.168.1.1") + .port(1234) + .authenticationInfo("test") + .build()) + .serviceUri("/") + .secure(ServiceRegistryRequestDTO.SecurityLevel.TOKEN) + .version(1) + .interfaces(Collections.singletonList("HTTP-SECURE-JSON")) + .build(); + + final Exception exception = assertThrows(Exception.class, () -> { + srClient.registerService(requestDTO); + }); + + assertEquals(ConnectException.class, exception.getCause().getCause().getClass()); + } + + @Test + public void testAuthorizationSystemFailure() { + final ArrowheadAuthorizationSystemClient authClient = new ArrowheadAuthorizationSystemClient("https://localhost:8443"); + + final Exception exception = assertThrows(Exception.class, () -> { + authClient.getPublicKey(); + }); + + assertEquals(ConnectException.class, exception.getCause().getCause().getClass()); + } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/config/InitArrowheadMockServers.java b/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/config/InitArrowheadMockServers.java new file mode 100644 index 000000000..0a889fb1a --- /dev/null +++ b/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/config/InitArrowheadMockServers.java @@ -0,0 +1,119 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.config; + +import static org.mockserver.model.HttpRequest.request; +import static org.mockserver.model.HttpResponse.response; + +import java.util.List; + +import org.mockserver.configuration.ConfigurationProperties; +import org.mockserver.integration.ClientAndServer; +import org.mockserver.model.MediaType; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import eu.arrowhead.core.hbconfmgr.arrowhead.model.response.ServiceQueryResultDTO; +import eu.arrowhead.core.hbconfmgr.arrowhead.model.response.ServiceRegistryResponseDTO; +import eu.arrowhead.core.hbconfmgr.arrowhead.model.response.SystemResponseDTO; + + +public class InitArrowheadMockServers { + + private ClientAndServer serviceRegistryMockServer; + private ClientAndServer authorizationSystemMockServer; + + public void setUp() throws JsonProcessingException { + startServiceRegistryMockServer(true); + startAuthorizationSystemMockServer(true); + } + + public void setUp(final Boolean useCertificates) throws JsonProcessingException { + startServiceRegistryMockServer(useCertificates); + startAuthorizationSystemMockServer(useCertificates); + } + + private void startServiceRegistryMockServer(final Boolean useCertificates) throws JsonProcessingException { + if (useCertificates) { + final String certificateAuthorityKeyPath = PropertiesExtractor.getProperty("mockserver.certificateAuthorityPrivateKey"); + final String certificateAuthorityCertificatePath = PropertiesExtractor.getProperty("mockserver.certificateAuthorityCertificate"); + final String serviceRegistryKeyPath = PropertiesExtractor.getProperty("mockserver.sreg.privateKeyPath"); + final String serviceRegistryCertPath = PropertiesExtractor.getProperty("mockserver.sreg.x509CertificatePath"); + + ConfigurationProperties.certificateAuthorityPrivateKey(certificateAuthorityKeyPath); + ConfigurationProperties.certificateAuthorityCertificate(certificateAuthorityCertificatePath); + ConfigurationProperties.privateKeyPath(serviceRegistryKeyPath); + ConfigurationProperties.x509CertificatePath(serviceRegistryCertPath); + } else { + ConfigurationProperties.certificateAuthorityPrivateKey(""); + ConfigurationProperties.certificateAuthorityCertificate(""); + ConfigurationProperties.privateKeyPath(""); + ConfigurationProperties.x509CertificatePath(""); + } + + serviceRegistryMockServer = ClientAndServer.startClientAndServer(8443); + + serviceRegistryMockServer.when(request().withPath("/serviceregistry/register")) + .respond(response().withStatusCode(201)); + serviceRegistryMockServer.when(request().withPath("/serviceregistry/unregister")) + .respond(response().withStatusCode(200)); + final ServiceRegistryResponseDTO response = ServiceRegistryResponseDTO.builder() + .provider(SystemResponseDTO.builder() + .address("127.0.0.1") + .port(8445) + .build()) + .build(); + final ServiceQueryResultDTO result = ServiceQueryResultDTO.builder() + .unfilteredHits(1) + .serviceQueryData(List.of(response)) + .build(); + final ObjectMapper mapper = new ObjectMapper(); + serviceRegistryMockServer.when(request().withPath("/serviceregistry/query")) + .respond(response().withStatusCode(200) + .withContentType(MediaType.APPLICATION_JSON_UTF_8) + .withBody(mapper.writeValueAsString(result))); + + } + + private void startAuthorizationSystemMockServer(final Boolean useCertificates) { + if (useCertificates) { + final String certificateAuthorityKeyPath = PropertiesExtractor.getProperty("mockserver.certificateAuthorityPrivateKey"); + final String certificateAuthorityCertificatePath = PropertiesExtractor.getProperty("mockserver.certificateAuthorityCertificate"); + final String authorizationSystemKeyPath = PropertiesExtractor.getProperty("mockserver.auth.privateKeyPath"); + final String authorizationSystemCertPath = PropertiesExtractor.getProperty("mockserver.auth.x509CertificatePath"); + + ConfigurationProperties.certificateAuthorityPrivateKey(certificateAuthorityKeyPath); + ConfigurationProperties.certificateAuthorityCertificate(certificateAuthorityCertificatePath); + ConfigurationProperties.privateKeyPath(authorizationSystemKeyPath); + ConfigurationProperties.x509CertificatePath(authorizationSystemCertPath); + } + + final String authorizationSystemPubKey = PropertiesExtractor.getProperty("mockserver.auth.pubKeyResponse"); + + authorizationSystemMockServer = ClientAndServer.startClientAndServer(8445); + + authorizationSystemMockServer.when( + request() + .withPath("/authorization/publickey") + ) + .respond( + response() + .withBody("\"" + authorizationSystemPubKey + "\"") + ); + } + + public void shutDown() { + serviceRegistryMockServer.stop(); + authorizationSystemMockServer.stop(); + } + +} diff --git a/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/config/PropertiesExtractor.java b/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/config/PropertiesExtractor.java new file mode 100644 index 000000000..c72651bf9 --- /dev/null +++ b/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/config/PropertiesExtractor.java @@ -0,0 +1,33 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.config; + +import java.io.FileInputStream; +import java.io.IOException; +import java.net.URL; +import java.util.Properties; + +public class PropertiesExtractor { + private static Properties properties; + static { + properties = new Properties(); + final URL url = PropertiesExtractor.class.getClassLoader().getResource("mockserver.properties"); + try{ + properties.load(new FileInputStream(url.getPath())); + } catch (final IOException e) { + e.printStackTrace(); + } + } + + public static String getProperty(final String key){ + return properties.getProperty(key); + } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/config/TestConfig.java b/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/config/TestConfig.java new file mode 100644 index 000000000..c616e4118 --- /dev/null +++ b/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/config/TestConfig.java @@ -0,0 +1,79 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.config; + +import java.net.Socket; +import java.security.cert.X509Certificate; + +import javax.net.ssl.SSLEngine; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509ExtendedTrustManager; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; + +import lombok.extern.log4j.Log4j2; + + +@Log4j2 +@Configuration +@Profile("test") +public class TestConfig { + /** + * This Bean returns a mocked TrustManager implementation to test + * whether the correct certificate is used during TLS handshake + * @return mocked TrustManager object + */ + @Bean + public TrustManager[] getTestTrustManager() { + final TrustManager[] tm = { + new X509ExtendedTrustManager(){ + @Override + public void checkClientTrusted(final X509Certificate[] chain, final String authType, final Socket socket) { + } + + @Override + public void checkServerTrusted(final X509Certificate[] chain, final String authType, final Socket socket) { + log.info("Server authentication with the following certificate:"); + log.error(chain[0]); + } + + @Override + public void checkClientTrusted(final X509Certificate[] chain, final String authType, final SSLEngine engine) { + } + + @Override + public void checkServerTrusted(final X509Certificate[] chain, final String authType, final SSLEngine engine) { + log.info("Server authentication with the following certificate:"); + log.error(chain[0]); + } + + @Override + public java.security.cert.X509Certificate[] getAcceptedIssuers() { + return null; + } + + @Override + public void checkClientTrusted(final X509Certificate[] certs, final String authType) { + } + + @Override + public void checkServerTrusted(final X509Certificate[] certs, final String authType) { + log.info("Server authentication with the following certificate:"); + log.error(certs[0]); + } + } + }; + + return tm; + } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/config/TestHawkbitConfig.java b/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/config/TestHawkbitConfig.java new file mode 100644 index 000000000..db5487779 --- /dev/null +++ b/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/config/TestHawkbitConfig.java @@ -0,0 +1,35 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.config; + +import com.github.fridujo.rabbitmq.mock.MockConnectionFactory; +import com.rabbitmq.client.ConnectionFactory; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.context.annotation.Profile; + +/** + * This class provides the clients for interaction with hawkBit via beans managed by the Spring container. + */ +@Configuration +@Profile("test") +public class TestHawkbitConfig { + @Primary + @Bean + public ConnectionFactory hawkbitConnectionFactory() { + final MockConnectionFactory mockConnectionFactory = new MockConnectionFactory(); + mockConnectionFactory.setAutomaticRecoveryEnabled(true); + + return mockConnectionFactory; + } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/hawkbit/HawkbitDmfConsumerTest.java b/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/hawkbit/HawkbitDmfConsumerTest.java new file mode 100644 index 000000000..a2b539586 --- /dev/null +++ b/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/hawkbit/HawkbitDmfConsumerTest.java @@ -0,0 +1,150 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.hawkbit; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import java.io.IOException; +import java.util.Collections; +import java.util.Map; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.github.fridujo.rabbitmq.mock.MockConnection; +import com.github.fridujo.rabbitmq.mock.MockConnectionFactory; +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.Envelope; + +import eu.arrowhead.core.hbconfmgr.hawkbit.model.inbound.CancelDownloadInboundMessage; +import eu.arrowhead.core.hbconfmgr.hawkbit.model.inbound.DownloadRequestInboundMessage; +import eu.arrowhead.core.hbconfmgr.hawkbit.model.inbound.ThingDeletedInboundMessage; +import eu.arrowhead.core.hbconfmgr.service.WebSocketService; +import eu.arrowhead.core.hbconfmgr.websocket.DeviceNotConnectedException; + +public class HawkbitDmfConsumerTest { + + private HawkbitDmfConsumer consumer; + private MockConnection mockConnection; + private WebSocketService mock_wsService; + + @BeforeEach + public void init() { + mockConnection = new MockConnectionFactory().newConnection(); + + mock_wsService = mock(WebSocketService.class); + consumer = new HawkbitDmfConsumer(mockConnection.createChannel(), mock_wsService); + } + + @Test + public void testDownloadRequestInboundGeneration() throws IOException, DeviceNotConnectedException { + final AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder().contentType("application/json") + .headers(Map.ofEntries(Map.entry("type", "EVENT"), Map.entry("thingId", "device7"), + Map.entry("topic", "DOWNLOAD_AND_INSTALL"), Map.entry("tenant", "tenant1"))) + .build(); + + final String body = "{\n" + " \"actionId\": 8,\n" + " \"targetSecurityToken\": \"aslkhju4kjasdz9uas\",\n" + + " \"softwareModules\": [{\n" + " \"moduleId\": 1,\n" + + " \"moduleType\": \"OS\",\n" + " \"moduleVersion\": \"1.0\",\n" + + " \"artifacts\": [{\n" + " \"filename\": \"os.zip\",\n" + + " \"urls\": {\n" + + " \"COAP\": \"coap://coap.local/os.zip\",\n" + + " \"HTTP\": \"http://localhost/os.zip\",\n" + + " \"HTTPS\": \"https://localhost/os.zip\"\n" + " },\n" + + " \"hashes\": {\n" + + " \"md5\": \"8c9693121361daf0d992df4eeb714ebb\",\n" + + " \"sha1\": \"e5ee90a0f8d0570b8bbdcaa23aa9a6b737c0c826\"\n" + + " },\n" + " \"size\": 539475\n" + " }\n" + + " ],\n" + " \"metadata\": [{\n" + + " \"key\": \"Description\",\n" + + " \"value\": \"This is the new operating system\"\n" + " }\n" + + " ]\n" + " }\n" + " ]\n" + "}"; + + final DownloadRequestInboundMessage.Body assertBody = DownloadRequestInboundMessage.Body.builder().actionId(8L) + .targetSecurityToken("aslkhju4kjasdz9uas") + .softwareModules(Collections.singletonList(DownloadRequestInboundMessage.Body.SoftwareModule.builder() + .moduleId(1L).moduleType("OS").moduleVersion("1.0") + .artifacts(Collections.singletonList(DownloadRequestInboundMessage.Body.Artifact.builder() + .filename("os.zip") + .urls(DownloadRequestInboundMessage.Body.Urls.builder().COAP("coap://coap.local/os.zip") + .HTTP("http://localhost/os.zip").HTTPS("https://localhost/os.zip").build()) + .hashes(DownloadRequestInboundMessage.Body.Hashes.builder() + .md5("8c9693121361daf0d992df4eeb714ebb") + .sha1("e5ee90a0f8d0570b8bbdcaa23aa9a6b737c0c826").build()) + .size(539475L).build())) + .metadata(Collections.singletonList(DownloadRequestInboundMessage.Body.Metadata.builder() + .key("Description").value("This is the new operating system").build())) + .build())) + .build(); + + final DownloadRequestInboundMessage.Headers assertHeaders = DownloadRequestInboundMessage.Headers.builder().type("EVENT") + .thingId("device7").topic("DOWNLOAD_AND_INSTALL").tenant("tenant1").build(); + + final DownloadRequestInboundMessage assertMessage = DownloadRequestInboundMessage.builder().body(assertBody).deliveryTag(1l).headers(assertHeaders).build(); + + consumer.handleDelivery("tag", new Envelope(1L, false, "exchange", "routingKey"), properties, body.getBytes()); + + verify(mock_wsService).sendDownloadEventMessage(assertMessage); + } + + @Test + public void testCancelDownload() throws IOException, DeviceNotConnectedException { + final AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder().contentType("application/json") + .headers(Map.ofEntries(Map.entry("type", "EVENT"), Map.entry("thingId", "device7"), + Map.entry("topic", "CANCEL_DOWNLOAD"), Map.entry("tenant", "tenant1"))) + .build(); + + final String rawMessage = "{" + + "\"actionId\": 16" + + "}"; + + final CancelDownloadInboundMessage cancelDownloadInboundMessage = CancelDownloadInboundMessage.builder() + .body( + CancelDownloadInboundMessage.Body.builder() + .actionId(16L) + .build() + ) + .headers( + CancelDownloadInboundMessage.Headers.builder() + .type("EVENT") + .tenant("tenant1") + .thingId("device7") + .topic("CANCEL_DOWNLOAD") + .build() + ) + .build(); + + consumer.handleDelivery("tag", new Envelope(1L, false, "exchange", "routingKey"), properties, rawMessage.getBytes()); + + verify(mock_wsService).sendCancelDownloadMessage(cancelDownloadInboundMessage); + } + + @Test + public void testThingDeleted() throws IOException, DeviceNotConnectedException { + final AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder().contentType("application/json") + .headers(Map.ofEntries(Map.entry("type", "THING_DELETED"), Map.entry("thingId", "device7"), + Map.entry("topic", "THING_DELETED"), Map.entry("tenant", "tenant1"))) + .build(); + + final ThingDeletedInboundMessage thingDeletedInboundMessage = ThingDeletedInboundMessage.builder() + .headers( + ThingDeletedInboundMessage.Headers.builder() + .thingId("device7") + .build() + ) + .build(); + + consumer.handleDelivery("tag", new Envelope(1L, false, "exchange", "routingKey"), properties, null); + + verify(mock_wsService).sendThingDeletedMessage(thingDeletedInboundMessage); + } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/hawkbit/HawkbitDmfOutboundClientTest.java b/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/hawkbit/HawkbitDmfOutboundClientTest.java new file mode 100644 index 000000000..1adf73a06 --- /dev/null +++ b/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/hawkbit/HawkbitDmfOutboundClientTest.java @@ -0,0 +1,110 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.hawkbit; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +import java.io.IOException; +import java.util.Collections; +import java.util.Map; + +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; +import org.skyscreamer.jsonassert.JSONCompareMode; + +import com.github.fridujo.rabbitmq.mock.MockConnection; +import com.github.fridujo.rabbitmq.mock.MockConnectionFactory; +import com.rabbitmq.client.AMQP; + +import eu.arrowhead.core.hbconfmgr.hawkbit.model.ActionStatus; +import eu.arrowhead.core.hbconfmgr.hawkbit.model.outbound.ThingCreatedOutboundMessage; +import eu.arrowhead.core.hbconfmgr.hawkbit.model.outbound.UpdateActionStatusOutboundMessage; +import eu.arrowhead.core.hbconfmgr.hawkbit.util.HawkbitDmfMockServer; +import eu.arrowhead.core.hbconfmgr.hawkbit.util.Message; + +public class HawkbitDmfOutboundClientTest { + + @Test + public void givenCorrectMessage_whenCreateThing_thenThingIsCreated() throws IOException, JSONException { + final MockConnection mockConnection = new MockConnectionFactory().newConnection(); + final HawkbitDmfMockServer mockServer = new HawkbitDmfMockServer(mockConnection.createChannel()); + final HawkbitDmfOutboundClient client = new HawkbitDmfOutboundClient(mockConnection.createChannel()); + + final ThingCreatedOutboundMessage message = ThingCreatedOutboundMessage.builder() + .body(ThingCreatedOutboundMessage.Body.builder() + .name("device3") + .build()) + .headers(ThingCreatedOutboundMessage.Headers.builder() + .tenant("tenant1") + .thingId("device3") + .sender("testSender") + .build()) + .build(); + + client.createThing(message); + + await().until(() -> mockServer.getMessages().size() == 1); + final Message receivedMessage = mockServer.getMessages().get(0); + assertThat(receivedMessage).isNotNull(); + assertThat(receivedMessage.getProperties()).isEqualTo(new AMQP.BasicProperties.Builder() + .headers(Map.ofEntries( + Map.entry("type", "THING_CREATED"), + Map.entry("thingId", "device3"), + Map.entry("sender", "testSender"), + Map.entry("tenant", "tenant1"))) + .contentType("application/json") + .replyTo("configuration_system.direct.exchange") + .build()); + JSONAssert.assertEquals("{\n" + + " \"name\": \"device3\"\n" + + "}", receivedMessage.getBody(), JSONCompareMode.NON_EXTENSIBLE); + } + + @Test + public void givenCorrectMessage_whenUpdateActionStatus_thenActionIsUpdated() throws IOException, JSONException { + final MockConnection mockConnection = new MockConnectionFactory().newConnection(); + final HawkbitDmfMockServer mockServer = new HawkbitDmfMockServer(mockConnection.createChannel()); + final HawkbitDmfOutboundClient client = new HawkbitDmfOutboundClient(mockConnection.createChannel()); + + final UpdateActionStatusOutboundMessage message = UpdateActionStatusOutboundMessage.builder() + .body(UpdateActionStatusOutboundMessage.UpdateActionStatusOutboundMessageBody.builder() + .actionId(7L) + .actionStatus(ActionStatus.FINISHED) + .message(Collections.singletonList("Successfully applied update on device")) + .softwareModuleId(3L) + .build()) + .headers(UpdateActionStatusOutboundMessage.UpdateActionStatusOutboundMessageHeaders.builder() + .tenant("tenant1") + .build()) + .build(); + + client.updateActionStatus(message); + + await().until(() -> mockServer.getMessages().size() == 1); + final Message receivedMessage = mockServer.getMessages().get(0); + assertThat(receivedMessage).isNotNull(); + assertThat(receivedMessage.getProperties()).isEqualTo(new AMQP.BasicProperties.Builder() + .headers(Map.ofEntries( + Map.entry("type", "EVENT"), + Map.entry("topic", "UPDATE_ACTION_STATUS"), + Map.entry("tenant", "tenant1"))) + .contentType("application/json") + .build()); + JSONAssert.assertEquals("{\n" + + " \"actionId\": 7,\n" + + " \"softwareModuleId\": 3,\n" + + " \"actionStatus\": \"FINISHED\",\n" + + " \"message\": [\"Successfully applied update on device\"]\n" + + "}", receivedMessage.getBody(), JSONCompareMode.NON_EXTENSIBLE); + } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/hawkbit/HawkbitFailureTest.java b/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/hawkbit/HawkbitFailureTest.java new file mode 100644 index 000000000..71272dbd5 --- /dev/null +++ b/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/hawkbit/HawkbitFailureTest.java @@ -0,0 +1,61 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.hawkbit; + +import static org.junit.Assert.assertThrows; + +import java.io.IOException; + +import org.junit.jupiter.api.Test; + +import com.github.fridujo.rabbitmq.mock.MockConnection; +import com.github.fridujo.rabbitmq.mock.MockConnectionFactory; + +import eu.arrowhead.core.hbconfmgr.hawkbit.model.outbound.ThingCreatedOutboundMessage; +import eu.arrowhead.core.hbconfmgr.hawkbit.util.HawkbitDmfMockServer; + + +public class HawkbitFailureTest { + + private HawkbitDmfMockServer dmfMockServer; + + @Test + public void testMissingExchange() throws IOException { + final MockConnection mockConnection = new MockConnectionFactory().newConnection(); + + final HawkbitDmfOutboundClient hawkbitClient = new HawkbitDmfOutboundClient(mockConnection.createChannel()); + + dmfMockServer = new HawkbitDmfMockServer(mockConnection.createChannel()); + + final ThingCreatedOutboundMessage message = ThingCreatedOutboundMessage.builder() + .body( + ThingCreatedOutboundMessage.Body.builder() + .name("some.device") + .build() + ) + .headers( + ThingCreatedOutboundMessage.Headers.builder() + .sender("sender") + .tenant("tenant") + .thingId("thingId") + .build() + ) + .build(); + + hawkbitClient.createThing(message); + + dmfMockServer.stageFailure(); + + assertThrows(IllegalArgumentException.class, () -> { + hawkbitClient.createThing(message); + }); + } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/hawkbit/RabbitMQAPITest.java b/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/hawkbit/RabbitMQAPITest.java new file mode 100644 index 000000000..597c93562 --- /dev/null +++ b/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/hawkbit/RabbitMQAPITest.java @@ -0,0 +1,120 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.hawkbit; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; + +import java.io.IOException; +import java.util.Collections; +import java.util.Map; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.github.fridujo.rabbitmq.mock.MockConnectionFactory; +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.Connection; + +import eu.arrowhead.core.hbconfmgr.hawkbit.model.inbound.DownloadRequestInboundMessage; +import eu.arrowhead.core.hbconfmgr.hawkbit.util.HawkbitDmfMockServer; +import eu.arrowhead.core.hbconfmgr.service.WebSocketService; +import eu.arrowhead.core.hbconfmgr.websocket.DeviceNotConnectedException; + +public class RabbitMQAPITest { + + private HawkbitDmfConsumer consumer; + private WebSocketService mock_wsService; + private HawkbitDmfMockServer mockServer; + private Connection mockConnection; + + @BeforeEach + public void init() throws IOException { + mock_wsService = mock(WebSocketService.class); + + mockConnection = new MockConnectionFactory().newConnection(); + + mockServer = new HawkbitDmfMockServer(mockConnection.createChannel()); + consumer = new HawkbitDmfConsumer(mockConnection.createChannel(), mock_wsService); + } + + @Test + public void testAMQPInterface() throws IOException, DeviceNotConnectedException { + + consumer.subscribeToDownloadEvents(); + + final AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder() + .contentType("application/json") + .headers(Map.ofEntries( + Map.entry("type", "EVENT"), + Map.entry("thingId", "device7"), + Map.entry("topic", "DOWNLOAD_AND_INSTALL"), + Map.entry("tenant", "tenant1") + )) + .build(); + final String body = "{\n" + + " \"actionId\": 8,\n" + + " \"targetSecurityToken\": \"aslkhju4kjasdz9uas\",\n" + + " \"softwareModules\": [{\n" + + " \"moduleId\": 1,\n" + + " \"moduleType\": \"OS\",\n" + + " \"moduleVersion\": \"1.0\",\n" + + " \"artifacts\": [{\n" + + " \"filename\": \"os.zip\",\n" + + " \"urls\": {\n" + + " \"COAP\": \"coap://coap.local/os.zip\",\n" + + " \"HTTP\": \"http://localhost/os.zip\",\n" + + " \"HTTPS\": \"https://localhost/os.zip\"\n" + + " },\n" + + " \"hashes\": {\n" + + " \"md5\": \"8c9693121361daf0d992df4eeb714ebb\",\n" + + " \"sha1\": \"e5ee90a0f8d0570b8bbdcaa23aa9a6b737c0c826\"\n" + + " },\n" + + " \"size\": 539475\n" + + " }\n" + + " ],\n" + + " \"metadata\": [{\n" + + " \"key\": \"Description\",\n" + + " \"value\": \"This is the new operating system\"\n" + + " }\n" + + " ]\n" + + " }\n" + + " ]\n" + + "}"; + mockServer.publish(properties, body); + + final DownloadRequestInboundMessage.Body assertBody = DownloadRequestInboundMessage.Body.builder().actionId(8L) + .targetSecurityToken("aslkhju4kjasdz9uas") + .softwareModules(Collections.singletonList(DownloadRequestInboundMessage.Body.SoftwareModule.builder() + .moduleId(1L).moduleType("OS").moduleVersion("1.0") + .artifacts(Collections.singletonList(DownloadRequestInboundMessage.Body.Artifact.builder() + .filename("os.zip") + .urls(DownloadRequestInboundMessage.Body.Urls.builder().COAP("coap://coap.local/os.zip") + .HTTP("http://localhost/os.zip").HTTPS("https://localhost/os.zip").build()) + .hashes(DownloadRequestInboundMessage.Body.Hashes.builder() + .md5("8c9693121361daf0d992df4eeb714ebb") + .sha1("e5ee90a0f8d0570b8bbdcaa23aa9a6b737c0c826") + .build()) + .size(539475L).build())) + .metadata(Collections.singletonList(DownloadRequestInboundMessage.Body.Metadata.builder() + .key("Description").value("This is the new operating system").build())) + .build())) + .build(); + + final DownloadRequestInboundMessage.Headers assertHeaders = DownloadRequestInboundMessage.Headers.builder().type("EVENT") + .thingId("device7").topic("DOWNLOAD_AND_INSTALL").tenant("tenant1").build(); + + final DownloadRequestInboundMessage assertMessage = DownloadRequestInboundMessage.builder().body(assertBody).deliveryTag(1l).headers(assertHeaders).build(); + + verify(mock_wsService, timeout(1000)).sendDownloadEventMessage(assertMessage); + } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/hawkbit/util/HawkbitDmfMockServer.java b/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/hawkbit/util/HawkbitDmfMockServer.java new file mode 100644 index 000000000..4dafccde8 --- /dev/null +++ b/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/hawkbit/util/HawkbitDmfMockServer.java @@ -0,0 +1,69 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.hawkbit.util; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.BuiltinExchangeType; +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.DefaultConsumer; +import com.rabbitmq.client.Envelope; + +import lombok.Getter; +import lombok.extern.log4j.Log4j2; + +@Log4j2 +public class HawkbitDmfMockServer { + + @Getter + private final Channel channel; + + @Getter + private final List messages; + + public HawkbitDmfMockServer(final Channel channel) throws IOException { + this.channel = channel; + this.messages = new ArrayList<>(); + this.initializeMockServer(); + } + + public void clearMessages() { + messages.clear(); + } + + public void publish(final AMQP.BasicProperties properties, final String body) throws IOException { + final byte[] byteBody = body.getBytes(StandardCharsets.UTF_8); + this.channel.basicPublish("configuration_system.direct.exchange", "", properties, byteBody); + } + + private void initializeMockServer() throws IOException { + final String queueName = this.channel.queueDeclare().getQueue(); + this.channel.exchangeDeclare("dmf.exchange", BuiltinExchangeType.DIRECT); + this.channel.queueBind(queueName, "dmf.exchange", ""); + this.channel.basicConsume(queueName, new DefaultConsumer(this.channel) { + @Override + public void handleDelivery(final String consumerTag, final Envelope envelope, final AMQP.BasicProperties properties, final byte[] body) { + final String bodyString = new String(body, StandardCharsets.UTF_8); + log.debug("Received amqp message: consumerTag {}, envelope {}, properties {}, body {}", + consumerTag, envelope, properties, bodyString); + messages.add(new Message(envelope, properties, bodyString)); + } + }); + } + + public void stageFailure() throws IOException { + this.channel.exchangeDelete("dmf.exchange", false); + } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/hawkbit/util/Message.java b/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/hawkbit/util/Message.java new file mode 100644 index 000000000..fc743bcde --- /dev/null +++ b/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/hawkbit/util/Message.java @@ -0,0 +1,30 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.hawkbit.util; + +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.Envelope; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +@Data +@Builder +public class Message { + + private Envelope envelope; + private AMQP.BasicProperties properties; + private String body; +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/service/HawbitServiceTest.java b/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/service/HawbitServiceTest.java new file mode 100644 index 000000000..98050f781 --- /dev/null +++ b/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/service/HawbitServiceTest.java @@ -0,0 +1,74 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.service; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import java.io.IOException; +import java.util.ArrayList; + +import javax.validation.ConstraintViolationException; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import eu.arrowhead.core.hbconfmgr.hawkbit.HawkbitDmfOutboundClient; +import eu.arrowhead.core.hbconfmgr.hawkbit.model.ActionStatus; +import eu.arrowhead.core.hbconfmgr.hawkbit.model.outbound.UpdateActionStatusOutboundMessage; +import eu.arrowhead.core.hbconfmgr.model.HawkbitActionUpdateStatus; + +public class HawbitServiceTest { + + private HawkbitService hawkbitService; + private HawkbitDmfOutboundClient mock_hawkbitDmfClient; + + @BeforeEach + public void init() { + mock_hawkbitDmfClient = mock(HawkbitDmfOutboundClient.class); + + hawkbitService = new HawkbitService("testTenant", mock_hawkbitDmfClient); + } + + @Test + public void testUpdateActionStatus() throws ConstraintViolationException, IOException { + final ArrayList messageList = new ArrayList(); + messageList.add("test"); + + final HawkbitActionUpdateStatus hActionUpdateStatus = HawkbitActionUpdateStatus + .builder() + .actionId(123456789L) + .actionStatus(ActionStatus.CANCELED) + .softwareModuleId(987654321L) + .message(messageList) + .build(); + + final UpdateActionStatusOutboundMessage message = UpdateActionStatusOutboundMessage.builder() + .body( + UpdateActionStatusOutboundMessage.UpdateActionStatusOutboundMessageBody.builder() + .actionId(123456789L) + .actionStatus(ActionStatus.CANCELED) + .softwareModuleId(987654321L) + .message(messageList).build() + ) + .headers( + UpdateActionStatusOutboundMessage.UpdateActionStatusOutboundMessageHeaders.builder() + .tenant("testTenant") + .build() + ) + .build(); + + + hawkbitService.updateActionStatus(hActionUpdateStatus); + + verify(mock_hawkbitDmfClient).updateActionStatus(message); + } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/websocket/WebSocketControllerIT.java b/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/websocket/WebSocketControllerIT.java new file mode 100644 index 000000000..ac9ad05c4 --- /dev/null +++ b/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/websocket/WebSocketControllerIT.java @@ -0,0 +1,180 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.websocket; + +import static org.junit.Assert.assertThrows; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.URI; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.time.Duration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; + +import javax.net.ssl.SSLContext; +import javax.websocket.DeploymentException; + +import org.apache.http.ssl.SSLContexts; +import org.apache.http.ssl.TrustStrategy; +import org.apache.tomcat.websocket.Constants; +import org.awaitility.Awaitility; +import org.json.JSONException; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketHttpHeaders; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.client.standard.StandardWebSocketClient; +import org.springframework.web.socket.handler.TextWebSocketHandler; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.google.common.base.Throwables; +import com.rabbitmq.client.Connection; + +import eu.arrowhead.core.hbconfmgr.SSLProperties; +import eu.arrowhead.core.hbconfmgr.config.InitArrowheadMockServers; +import eu.arrowhead.core.hbconfmgr.hawkbit.util.HawkbitDmfMockServer; +import eu.arrowhead.core.hbconfmgr.hawkbit.util.Message; +import lombok.extern.log4j.Log4j2; + +@Log4j2 +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@ActiveProfiles("test") +public class WebSocketControllerIT { + + @Value("${testParameters.token}") + private String testToken; + + @Value("${testParameters.incorrectToken}") + private String incorrectTestToken; + + @Autowired + private Connection mockConnection; + +// @Autowired +// private TrustManager[] tm; + + @Autowired + private SSLProperties sslProps; + + @LocalServerPort + private Integer port; + + private HawkbitDmfMockServer hawkbitMockServer; + private StandardWebSocketClient wsClient; + + private static InitArrowheadMockServers initMockServer; + + @BeforeAll + public static void beforeAll() throws JsonProcessingException { + WebSocketControllerIT.initMockServer = new InitArrowheadMockServers(); + WebSocketControllerIT.initMockServer.setUp(); + } + + @BeforeEach + public void init() throws NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException, + KeyStoreException, UnrecoverableKeyException, KeyManagementException { + wsClient = new StandardWebSocketClient(); + final Map userProperties = new HashMap<>(); + final SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(new TrustStrategy() { + @Override + public boolean isTrusted(final X509Certificate[] chain, final String authType) throws CertificateException { + log.error(chain); + return true; + } + }).loadKeyMaterial(sslProps.getKeyStore().getURL(), sslProps.getKeyStorePassword().toCharArray(), sslProps.getKeyPassword().toCharArray()) + .build(); + + +// sslContext.init(null, tm, new java.security.SecureRandom()); + userProperties.put(Constants.SSL_CONTEXT_PROPERTY, sslContext); + + wsClient.setUserProperties(userProperties); + + // Mock the HawkBit DMF API (RabbitMQ) + hawkbitMockServer = new HawkbitDmfMockServer(mockConnection.createChannel()); + } + + @Test + public void testWebsocketWithCorrectTokenAndCorrectPayload() + throws InterruptedException, ExecutionException, IOException, JSONException { + final WebSocketHttpHeaders headers = new WebSocketHttpHeaders(); + headers.setBearerAuth(testToken); + + final WebSocketSession session = wsClient.doHandshake(new TextWebSocketHandler() { + + }, headers, URI.create("wss://localhost:" + port)).get(); + + final String message = "{" + + "\"actionId\": 11," + + "\"softwareModuleId\": 12," + + "\"actionStatus\": \"DOWNLOAD\"," + + "\"message\": [" + + "\"value\"" + + "]" + + "}"; + + session.sendMessage(new TextMessage(message)); + + // Mock server should contain two messages up to this point: + // 1. the device creation message resulting from the call of doHandshake() + // 2. the action update status message + + Awaitility.await().atMost(Duration.ofMillis(500)).until(() -> { + if (hawkbitMockServer.getMessages().size() == 2) { + return true; + } else { + return false; + } + }); + + final List receivedMessages = hawkbitMockServer.getMessages(); + final Message receivedUpdateActionStatus = receivedMessages.get(1); + final String receivedUpdateActionStatusBody = receivedUpdateActionStatus.getBody(); + + JSONAssert.assertEquals(message, receivedUpdateActionStatusBody, true); + } + + @Test + public void testWebsocketWithIncorrectToken() throws IOException, InterruptedException, ExecutionException { + final WebSocketHttpHeaders headers = new WebSocketHttpHeaders(); + headers.setBearerAuth(incorrectTestToken); + + + final Exception e = assertThrows(ExecutionException.class, () -> { + wsClient.doHandshake(new TextWebSocketHandler(), headers, URI.create("wss://localhost:" + port)).get(); + }); + + assertEquals(DeploymentException.class, Throwables.getRootCause(e).getClass()); + } + + @AfterAll + public static void afterAll() { + WebSocketControllerIT.initMockServer.shutDown(); + } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/websocket/WebSocketSenderTest.java b/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/websocket/WebSocketSenderTest.java new file mode 100644 index 000000000..effd7871f --- /dev/null +++ b/hawkbit-configuration-manager/src/test/java/eu/arrowhead/core/hbconfmgr/websocket/WebSocketSenderTest.java @@ -0,0 +1,95 @@ +/******************************************************************************** +* Copyright (c) 2021 Bosch.IO GmbH[ and others] +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +********************************************************************************/ + +package eu.arrowhead.core.hbconfmgr.websocket; + +import static org.junit.Assert.assertThrows; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import java.io.IOException; +import java.util.concurrent.ConcurrentHashMap; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; + +import eu.arrowhead.core.hbconfmgr.hawkbit.model.inbound.MessageTypeInbound; +import eu.arrowhead.core.hbconfmgr.hawkbit.model.inbound.ThingDeletedInboundMessage; +import eu.arrowhead.core.hbconfmgr.websocket.model.DeviceMessage; + +public class WebSocketSenderTest { + + private WebSocketSender wsSender; + private WebSocketSession wsSession; + + @BeforeEach + public void init() { + final ConcurrentHashMap webSocketSessionMap = new ConcurrentHashMap<>(); + + wsSession = mock(WebSocketSession.class); + webSocketSessionMap.put("testIdX", wsSession); + + wsSender = new WebSocketSender(webSocketSessionMap); + } + + @Test + public void testSendMessageWithMissingSessionWebSocketSessionMap() throws IOException { + final DeviceMessage deviceMessage = DeviceMessage.builder() + .type(MessageTypeInbound.EVENT.toString()) + .message( + ThingDeletedInboundMessage.builder() + .headers( + ThingDeletedInboundMessage.Headers.builder() + .thingId("testIdY") + .build() + ) + .build() + ) + .build(); + + assertThrows(DeviceNotConnectedException.class, () -> { + wsSender.sendMessage("testIdY", deviceMessage); + }); + } + + @Test + public void testSendMessageWithExistingSession() throws IOException, + DeviceNotConnectedException { + final DeviceMessage deviceMessage = DeviceMessage.builder() + .type(MessageTypeInbound.EVENT.toString()) + .message( + ThingDeletedInboundMessage.builder() + .headers( + ThingDeletedInboundMessage.Headers.builder() + .thingId("testIdX") + .build() + ) + .build() + ) + .build(); + + final String expectedBody = "{" + + "\"type\":\"EVENT\"," + + "\"message\":{" + + "\"headers\":{" + + "\"thingId\":\"testIdX\"" + + "}" + + "}" + + "}"; + + final TextMessage expectedWSMessage = new TextMessage(expectedBody); + + wsSender.sendMessage("testIdX", deviceMessage); + + verify(wsSession).sendMessage(expectedWSMessage); + } +} \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/test/resources/application-test.properties b/hawkbit-configuration-manager/src/test/resources/application-test.properties new file mode 100644 index 000000000..41fbd8344 --- /dev/null +++ b/hawkbit-configuration-manager/src/test/resources/application-test.properties @@ -0,0 +1,70 @@ +############################################ +### APPLICATION PARAMETERS ### +############################################ + +# Hawkbit Configuration Manager web-server parameters +server.address=0.0.0.0 +server.port=8447 + +############################################ +### CUSTOM PARAMETERS ### +############################################ + +# These are the properties of the configuration system itself +# The properties are loaded into the class eu.arrowhead.core.hbconfmgr.properties.SystemProperties +system.name=confsys +system.address=127.0.0.1 +system.port=8447 +# This is the protocol of the provided service from the configuration system itself, e.g. "HTTP-SECURE-JSON" +system.providedServiceInterface=HTTP-SECURE-JSON +# This is the definition of the provided service from the configuration system itself, e.g. "definition5" +system.providedServiceDefinition=definition1 +# This is the uri of the provided service from the configuration system itself, e.g "/" +system.providedServiceUri=/ +# This is the version of the provided service from the configuration system itself, e.g. 2 +system.providedServiceVersion=1 + +# Service Registry web-server parameters +sr_address=127.0.0.1 +sr_port=8443 + +# These are the connection parameters for the hawkBit DMF API +hawkbit.host=127.0.0.1 +hawkbit.port=5672 +hawkbit.username=hawkbit +hawkbit.password=hawkbit +# This is the tenant in hawkBit itself +hawkbit.tenant=DEFAULT + +###############&############################ +### SECURE MODE ### +############################################ + +# configure secure mode + +# This system works only in secure mode. Do not modify this property. +server.ssl.enabled=true + +server.ssl.key-store-type=PKCS12 +server.ssl.key-store=classpath:test-certificates/test-conf-system.p12 +server.ssl.key-store-password=confsys-test-pw +server.ssl.key-alias=confsys.example.corp.arrowhead.eu +server.ssl.key-password=confsys-test-pw +server.ssl.client-auth=need +server.ssl.trust-store-type=PKCS12 +server.ssl.trust-store=classpath:test-certificates/test-cloud.truststore.p12 +server.ssl.trust-store-password=test-cloud-trust-store-pw + + +testParameters.authorization.cert.path=./src/test/resources/test-certificates/test-auth-system.cert.pem +testParameters.authorization.key.path=./src/test/resources/test-certificates/test-auth-system.key.pem +testParameters.authorization.pubKey=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvmaFymz4cSB+LP3pixhhgrczq8G2rWTMyyajMQNSXOuvGDBhB7S32rl/+B8VUvTrYBx7ab8AM/TxrDwWAzsQ3p3oo0+5NNibHpZ8QucWIXqbpQqFobiNWx+ogoBLV4UfJekxXYH54QrTdwkvlOFXxjkL11uwBKEsZ0TzKgGaRs+iR80sjDeicnpPlMm0RbpXjx10OzmjqfC1Wrusyl2gjsW9ySfwJ1461n6cPndn8csvC37IHdHpkKTRlcDDPG6/GN3+vfUeznA7zGFWv2IOTzsuruehXaOuDNgekvREK3Xh4BpvGTZ6oEFVmr5853U3PS6ExKlvwR8NwFiPXI9LAQIDAQAB + +testParameters.serviceRegistry.cert.path=./src/test/resources/test-certificates/test-sreg.cert.pem +testParameters.serviceRegistry.key.path=./src/test/resources/test-certificates/test-sreg.key.pem + +testParameters.cloud.cert.path=./src/test/resources/test-certificates/test-cloud.cert.pem +testParameters.cloud.key.path=./src/test/resources/test-certificates/test-cloud.key.pem + +testParameters.token=eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIiwiY3R5IjoiSldUIn0.LgLfAMnAvIKBhSThP6hhY9q5sVH9krMhXlb6UvVC5Bmvm-ph_ThOKlqd4iIoXws6kDaKJx--sWKKZhjbnRabLAZUHMwkmQHTbHl9FfUinCXBf95rwVxlL6hUS7tPvzp4awh5QqVMi_Focb6FboXx0ScXNUlaqsP3AreSFPiS_w2wPyBvsfKHF2xFkCmsSRb2Cxoh2P7_MFxtXK-eY4IiDlCOaBv9VijeDVV2hJ251R10k0w2yjG-b8--cdgDLsjQ3vjpDoIDqRHR8_7VzfnQZKVsU_4YNsAJMPx0OdwGBXKsUhx5yEqHtaglq3ym4P7qm3zXcYWgG9u8wD3KHvnPiw.4d8ObjIomnHzKdilPBGOQg.yhdv2DSpw4N_sQtalwVBnHhIXPRkm8G82KF11tHAJZ-iw3NKGjMI57_xOGPkL04yF3-HPsr52lxNLn883nynIJRcj2g8rri1D08mQ6hil4B2HpL3O4OvyM-CLgLNQ8ZODFMbTQ8rZgw4B1-gT61H0RYq4XMKyubf3_QrLKDkz4CLzuZR_F4EsXYZZ682zX1_Ym3sM6rDA2YI7wVZRSIAf0FOnV2IOu5WfABNcRYJtuSE60Tx20rz86nLdvC7LVo6xzFRAzsW3YQLSCQrZeanxzVU0lSN3p6AxifuD38CZzNMDqFIEuNJA3PpEU6-hwrKVYksrngbUSdJ1cjaSrGuXR5zI4LwsJOXMoCUDvGhO1Wz4qzWAdLJNZ2qOz_FC_6rN2hrU7YgWJ4-vDNLTJVyqCYURUiv1b3UDjEBUgSBPBp0gpX7-xBwv3EW0DB8c-r5_93lS8c0DIJ_bwr5p2q9o2nFSVbjWOPBzz0i8heGq_ZgkzE-x2672NhxoCQRGEfC9EhZH5pUtjhqIKedZn90XtbeXgytDtpRJ8sgCdMejjnzZpFIdyQru549e14t9bjoyE7mNs4INd6gNB8qQyFaTV07pLDsTdWrfvVRcp6BZx_0wWkXQOpUF5jmrSIGdpHOhFmNlrKJCapivyTwVs_IPmqzFrIdexTKA3Gx7w3fT1-4ckrTtsVR115xPbh_b0ZPPRH9mI9OLlf_TR1r9ZAI3KOAA4yCwDYZWVi0x87BPmnt0O1RWtQRfmDrXapLCHxdEz1KuCv8_KPWjKSm1lnyzg._lX83lJYfL2lf8Mq5t8N3r9-Qa9045Odx3bg9yL6deY +testParameters.incorrectToken=eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIiwiY3R5IjoiSldUIn0.U5PblBD4SRwVIYjZbG-OwA83pZeU63mj9VlOWxSgcRdskU24KDRA1utE3Ubl6zLtSgUuTtVtGgWNJWi-AHHQBRY1XLSfr4f7-5fUp3bUes1QtF3Sj8cUknA04_H4nyIzoHwxb_b80upS5wpps6gcASmBuWtVlwyCxiv-mR5mNvHro3ueZo1Lrxk0y4MSIXQA6FQlnTkamSdGGA9WDI1Q6pm4YVkPF4Z7OOobvwhqQJR3r4HUQyglf8jhdIBfxxzD6RRGqXTk7PVQgq2D018UAAgJfNmpD37_A9EPFsWDFNMj_7Prf_to48QnwVX5qHrVcHTclHPjgZb4TD8sEzKjTg.qxBxIfX41UfQbSOhLhRWoQ.bFqcOHyl8A6ujQR2oXToQIfbxvpKLchZwuz7KJ-y5jvOqEaOo0hzLX0G-ueeLZVbQFsuLFJSaJKPmEe1odPaRCjH4H1Seo2eUWMDIMGj7ub6v5dvn2Y7p5jpYKG2mHsFC0bA7nvMx6RyT4wMwPsX8GTDHDPwFFMRVoWzT0ENRuV358IjNp5wEWuKEHTB2_VKuUYKFGmP7rSsXlgSWYz6anJC41bVkzfv4OnJyYTV7DJ5yXtESkE9b1UZNDXpBezbeCAshpFgjKrACi_m46zRJR5kJP9Fkn_lJPPNvKd11Q3ij6OLPjLnoHm1vENnsP7BhWvLJhoTiGjMROe5hLL1AzajNYegSYHwKONPk2w_Zf9UZP8A2hq_s5olkDmopKPEiWJ_Ml8PN61Q9dfMtio7UvLIu9MP-12pU0hvCK1BMs_F8jqqm-SpxqSDa0sM-DJaeYjbxrCVzCiiJg04bq1y_2BrGP1sGi3pl6W-ezpmksyn0m_UjwCFl9nYi8caYHr4fNvBt4mRRY1ZoH-ihGWM0befFPyG-QjHqMTfdcwU17NyBHEqoq5Yxq4maOeULg7MM0MPP0I5g_s9P8ByrwJ0arTkmZIh4crjhhjXycsOMFNKJRd8CogmHHQPJ3VrT78bmLjVQ-2EyK7X3R-qs-JiUwY6HnQR9EpNcw_LeFRSPuqCWZBeyEDIS8T_Fc8fcHUj4nz0pdSN1GS5uSbdy8OwO87zF8y6g1pEo__yL_D1GwrLr1r6tfJbAAhIJC6vu4MwoJ1lhdGN3HODA2Kt87-1ww.onky0el9l6HXWQOIs2ZrWDHNl1jI06dxmQeJUJ_UwcA diff --git a/hawkbit-configuration-manager/src/test/resources/banner.txt b/hawkbit-configuration-manager/src/test/resources/banner.txt new file mode 100644 index 000000000..e69de29bb diff --git a/hawkbit-configuration-manager/src/test/resources/log4j2.xml b/hawkbit-configuration-manager/src/test/resources/log4j2.xml new file mode 100644 index 000000000..26c26f8dd --- /dev/null +++ b/hawkbit-configuration-manager/src/test/resources/log4j2.xml @@ -0,0 +1,31 @@ + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} %5p ${hostName} --- [%15.15t] %-40.40c{1.} : %m%n%ex + + OFF + . + + + + + + + + ${LOG_PATTERN} + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/test/resources/mockserver.properties b/hawkbit-configuration-manager/src/test/resources/mockserver.properties new file mode 100644 index 000000000..2249ee4e4 --- /dev/null +++ b/hawkbit-configuration-manager/src/test/resources/mockserver.properties @@ -0,0 +1,7 @@ +mockserver.certificateAuthorityPrivateKey=./src/test/resources/test-certificates/test-cloud.key.pem +mockserver.certificateAuthorityCertificate=./src/test/resources/test-certificates/test-cloud.cert.pem +mockserver.sreg.privateKeyPath=./src/test/resources/test-certificates/test-sreg.key.pem +mockserver.sreg.x509CertificatePath=./src/test/resources/test-certificates/test-sreg.cert.pem +mockserver.auth.privateKeyPath=./src/test/resources/test-certificates/test-auth-system.key.pem +mockserver.auth.x509CertificatePath=./src/test/resources/test-certificates/test-auth-system.cert.pem +mockserver.auth.pubKeyResponse=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqIZpQa/yCE8JBYtLz+XkoXpHYUsTFHuntQIPV6pPgcX07OagpFWW+LIRWD4jXPc+ndXOVD7XmimnkAOXGYD+1GF+glBSCKVcjOM55S5LLoJbV9J77H1B/NhKQX6kZDxP94jRPql7YmMlo8ge3NHjc/isNh2mX8yS4LnjjXXdhv2Ggn0fcItgAAYd2CLpds/obrsue6hWD4G1T/TUTjgoYR3JiPh+XTOOQJXWTDMA5ay81/TGTLJ+PAc8Fp2GP+AED2QVmPO+PPuk4RjTCxS7L63aH0HW2o1ibf4kndKbsg5XwOEP56+IwEutGqctzQw4y3luLHXOBSxsfWcnyoMeDwIDAQAB \ No newline at end of file diff --git a/hawkbit-configuration-manager/src/test/resources/test-certificates/master.p12 b/hawkbit-configuration-manager/src/test/resources/test-certificates/master.p12 new file mode 100644 index 000000000..c6bc2c6ea Binary files /dev/null and b/hawkbit-configuration-manager/src/test/resources/test-certificates/master.p12 differ diff --git a/hawkbit-configuration-manager/src/test/resources/test-certificates/test-auth-system.cert.pem b/hawkbit-configuration-manager/src/test/resources/test-certificates/test-auth-system.cert.pem new file mode 100644 index 000000000..446256a20 --- /dev/null +++ b/hawkbit-configuration-manager/src/test/resources/test-certificates/test-auth-system.cert.pem @@ -0,0 +1,80 @@ +Bag Attributes + friendlyName: authorization.example.corp.arrowhead.eu + localKeyID: 54 69 6D 65 20 31 36 30 34 36 36 32 30 32 37 30 39 34 +subject=CN = authorization.example.corp.arrowhead.eu + +issuer=C = DE, ST = Berlin, L = Berlin, O = AHT, OU = arrowhead.eu, CN = example.corp.arrowhead.eu + +-----BEGIN CERTIFICATE----- +MIIDqjCCApKgAwIBAgIEX6UyfTANBgkqhkiG9w0BAQsFADB4MQswCQYDVQQGEwJE +RTEPMA0GA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4xDDAKBgNVBAoMA0FI +VDEVMBMGA1UECwwMYXJyb3doZWFkLmV1MSIwIAYDVQQDDBlleGFtcGxlLmNvcnAu +YXJyb3doZWFkLmV1MB4XDTIwMTEwNjExMjQ0NVoXDTMwMTEwNjExMjQ0NVowMjEw +MC4GA1UEAwwnYXV0aG9yaXphdGlvbi5leGFtcGxlLmNvcnAuYXJyb3doZWFkLmV1 +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqIZpQa/yCE8JBYtLz+Xk +oXpHYUsTFHuntQIPV6pPgcX07OagpFWW+LIRWD4jXPc+ndXOVD7XmimnkAOXGYD+ +1GF+glBSCKVcjOM55S5LLoJbV9J77H1B/NhKQX6kZDxP94jRPql7YmMlo8ge3NHj +c/isNh2mX8yS4LnjjXXdhv2Ggn0fcItgAAYd2CLpds/obrsue6hWD4G1T/TUTjgo +YR3JiPh+XTOOQJXWTDMA5ay81/TGTLJ+PAc8Fp2GP+AED2QVmPO+PPuk4RjTCxS7 +L63aH0HW2o1ibf4kndKbsg5XwOEP56+IwEutGqctzQw4y3luLHXOBSxsfWcnyoMe +DwIDAQABo4GBMH8wGgYDVR0RBBMwEYIJbG9jYWxob3N0hwR/AAABMEIGA1UdIwQ7 +MDmAFBOMBYikgCvo8FXUYauQwlW8VZvqoRukGTAXMRUwEwYDVQQDDAxhcnJvd2hl +YWQuZXWCBF+kKMowHQYDVR0OBBYEFKAIzWSlATK8//WtDrPAzVbefzZUMA0GCSqG +SIb3DQEBCwUAA4IBAQCKlv6loenAlJPgAzNkm9HVKU72Qk+Atn7J/lM3ub8SxTO2 +0tD8B9Jm+0kpn5Tl14KOPtCnjSu2hFRzp+q6x4N2P63SXezaBCuxIOfS3CSdEWPA +sF9oTbhBaaz528lx2xjkI4yagDRdmNcnSvmoOJd4AfJpD8C1aWOm2EgDsC/MgB3w +rMEBs67hFpzS0rxxNM1J6myc2Bhq2lzgwKE5tQxquSUBSjJtRyh0p3lZWLWp72jN +YfwHg8T5l7Xvf/4l2kDiAOLZRRo/88SQpxxgIb3v54JfvRJmazapikgISMyv1U9s +ukfQtyNSPeCX4+//LYda1D7EvuRIq5ljK0UdzEUn +-----END CERTIFICATE----- +Bag Attributes + friendlyName: CN=example.corp.arrowhead.eu,OU=arrowhead.eu,O=AHT,L=Berlin,ST=Berlin,C=DE +subject=C = DE, ST = Berlin, L = Berlin, O = AHT, OU = arrowhead.eu, CN = example.corp.arrowhead.eu + +issuer=CN = arrowhead.eu + +-----BEGIN CERTIFICATE----- +MIIDgzCCAmugAwIBAgIEX6QoyjANBgkqhkiG9w0BAQsFADAXMRUwEwYDVQQDDAxh +cnJvd2hlYWQuZXUwHhcNMjAxMTA1MTYzMTA2WhcNMzAxMTA1MTYzMTA2WjB4MQsw +CQYDVQQGEwJERTEPMA0GA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4xDDAK +BgNVBAoMA0FIVDEVMBMGA1UECwwMYXJyb3doZWFkLmV1MSIwIAYDVQQDDBlleGFt +cGxlLmNvcnAuYXJyb3doZWFkLmV1MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAmrcvpwqTKaGgpQLuqXMi6TrH5KxkpFH8lH5vq2aozu7QnZexM0Qyf1TY +kvpiy3sDTu6mzGysT96Qv8K8CI8g24oIVRKnJUEs23NgS+KUK6Nz2eIjRaaMVRh4 +cFBcRkr3oFbk+ujGySn+BR8v4eRjq5+qzFX8fswcoeBmlkU+M92kg/Bcy65Pg2v5 +1ORuGhbnmqsEuoyOsSjucL43zTOQtwAva5rvdERVPh7978I+UGFUXaSQnYpP8oyw +CRRIYrPTBHHBA0Q/7sDb2lCA1yLPSwD/H5QvFOxmN3cYAnzvgQZO8+spCiy1T7Kx +FBAnutVn8Sj6BeFNGZvszmlDCs54AwIDAQABo3YwdDAPBgNVHRMECDAGAQH/AgEC +MEIGA1UdIwQ7MDmAFJqKmR4xTB6y5i22mu3HHjHK3Tv+oRukGTAXMRUwEwYDVQQD +DAxhcnJvd2hlYWQuZXWCBFzVPq0wHQYDVR0OBBYEFBOMBYikgCvo8FXUYauQwlW8 +VZvqMA0GCSqGSIb3DQEBCwUAA4IBAQAEhHykqvmRnl3S0aVv4tSoA+tGxwBMaRoY +OvxEDC321CFY/QkWaQfUpVg/eGgsLLFweUp9OFXxkgK4H/rUvQ/twt7oUIDUdJd+ +sGn04mIaym3M9IWhCTxqYf+X3/SHWmI5GnT3dQu9ayJKitD28/6FMLMqEdPiON/9 +VMzkoD2ndN6VSJWOWSg3xmOJ93u+xGbTkweIFildnhf79QwgColJGBytIWBhggvV +DVccNfLqmq9NNaxvPx/y2Oe1ngNhLcqqTUSiwKH42pdupKWZD/EcNlMXttRrkiOt +S3sXv18pDV/094abPB38ulgeeY7Kt2lz12qK7irxjZsVPahyW1Z0 +-----END CERTIFICATE----- +Bag Attributes + friendlyName: CN=arrowhead.eu +subject=CN = arrowhead.eu + +issuer=CN = arrowhead.eu + +-----BEGIN CERTIFICATE----- +MIIC3jCCAcagAwIBAgIEXNU+rTANBgkqhkiG9w0BAQsFADAXMRUwEwYDVQQDDAxh +cnJvd2hlYWQuZXUwHhcNMTkwNTEwMDkwNDQ1WhcNMjkwNTEwMDkwNDQ1WjAXMRUw +EwYDVQQDDAxhcnJvd2hlYWQuZXUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQCuB4z+wchXDKdfy9YFZha2U0khBAWuHYerBlBLM0Oqvr4c/YYcZpNwTWY2 +tk6UXPUTQ8gI9V6Ob7DRXoAfLDhCRGKySN0BlGnjUJkItP25Sj0RfiTL3b8fFEIT +Z8pg+6pAfeFQgV0yz+ziyL+0uu69VZPv+RAEf1GKgshGGLJw3sOlcIdKuZaEAaA2 +b0nDUn229VpKXb9cg47Ae1Yb0sJcTkyIBuhkQKln3uhG2xct9nDfVal05+229AJQ +Ly1f0UfEofvD/OLjFG3umF857T1Vr5azj8zFOvNi503gV458lH3wKC+9UHUf46sg +Hc8Tyrz1q8VAIamJe7BLUeRFwGvBAgMBAAGjMjAwMA8GA1UdEwQIMAYBAf8CAQMw +HQYDVR0OBBYEFJqKmR4xTB6y5i22mu3HHjHK3Tv+MA0GCSqGSIb3DQEBCwUAA4IB +AQCkFsqyeAjztDBkTQrPxAB0Vvx6KPINApHGIHkJj/9crKXZEQcNJcJr35hfLcgv +hSsmLMdeRFCeaG5QLmUKI6GFYIbX+6nawMLGzIPUTOGetNeuMauDXkq09Hu/UmjN +AOgoD5vWdtyTbItv21enJnUelClAJ7VXti2QpyRM2puPHpZMNi4FWgLGPo6hq5ka +d7KomzW8JLh2Vd67v/6mXGpST4EzyRe+Yb2FJZUmhxVWt68/MFaflPQ2toPIsIpW +5m4OS+rT7t+uxPKWU/ogCK7BOUfE/qf3Al/osWkNKnFQDtO/7x7InEmRoP9EUv07 +JY4vK/+k5fJUZHpJzpuKxbBo +-----END CERTIFICATE----- diff --git a/hawkbit-configuration-manager/src/test/resources/test-certificates/test-auth-system.key.pem b/hawkbit-configuration-manager/src/test/resources/test-certificates/test-auth-system.key.pem new file mode 100644 index 000000000..cde3c39ae --- /dev/null +++ b/hawkbit-configuration-manager/src/test/resources/test-certificates/test-auth-system.key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCohmlBr/IITwkF +i0vP5eShekdhSxMUe6e1Ag9Xqk+BxfTs5qCkVZb4shFYPiNc9z6d1c5UPteaKaeQ +A5cZgP7UYX6CUFIIpVyM4znlLksugltX0nvsfUH82EpBfqRkPE/3iNE+qXtiYyWj +yB7c0eNz+Kw2HaZfzJLgueONdd2G/YaCfR9wi2AABh3YIul2z+huuy57qFYPgbVP +9NROOChhHcmI+H5dM45AldZMMwDlrLzX9MZMsn48BzwWnYY/4AQPZBWY8748+6Th +GNMLFLsvrdofQdbajWJt/iSd0puyDlfA4Q/nr4jAS60apy3NDDjLeW4sdc4FLGx9 +ZyfKgx4PAgMBAAECggEAB5o8sFpsHETYftgIXQIdfM1j3/c/jpp/d9J2d7l2qb+Q +64kB8TftPh3mW2k+NijvObaV/sXw+wLrDLukuuFPXaxiRSWzdbFkCfUMPRMIEDqQ +PVeBAKur6Oim/OpmJyFzaBLTgbSqnMclQQy7m7GaRbaPqkdPJJ45eArawZxyGG9P +nB1Iwnv1J7KilklJuWkzFSrlk2VE+UUzAiUiRN2TPtGQ0HOnTz+piuovSEIDco3v +EUCpr6sppUTHKRCyN6h6UZlMeFAfyjiTDWXhx8N5pN00m8QXdL7g2EILBnB8/m1r +sUUiVlAznSQ+C5oVEuc17NpuABA94yfCSRA8XUvVXQKBgQDqVjjfZnXMJsi7N4gO +DTuuWrpPBV03RVOBHtcrW0zGcyxGkz4B/E9pm0A7q2PSfVR0YaunetLFjMpm/GOC +AD7Bys19NtejKnrgOFwcDxYnj5spvu5huMpkIdmr0sSMaQ8CoaCm3R16GOUEUax+ +6s7HfBoJedWSmrOT/V9n+DyfjQKBgQC4GrK/mteRXgq/2NrukxrfhIcFcwQ3Hige +V3ZgsMdwROUeKz5AYX/vQdL3t1cQkmTfBiIS+MPxRrtCIaR0AQ10IvA1ch7eIIrJ +8YVTZQEurTRJrzDuwpJSlHQyGEqBKCTx6wKesAnb+D6CWmTpl5569Yya/VTGLvkL +sISo9YsPCwKBgQCUaRsH0XpVW1LRzy+pxpO/rtJD7Z/Fu3BRXFjlqqMLJDIsWGBz +HXql5Z9eqwNDcgCk3J68KTuHxc0CQGI9GS6lyW0vGiajvemoS8l6M8vSLk8Ut0l+ +Nmxn0lBrU8cLLhZFkluOkodypoWUoK1gjBF9oYlT6wdqG/QTi+p6jGrImQKBgQCq +7HaZtzt+Bc57EO0p5D9l11e38nGubnwWt2L7IAT5saz6FnHEOgEZ84XXYEsf4ppu +CPanoHfiUKXYHv3ciYGhGjLjm+EWnyXbmTUMGNFBMaNC6bjHYrOQoa1DORQq10d9 +po+XHp74r3/xa9UWniKPy9tPpfUHzmJ/vNFdQCJrwwKBgAyVj1mgcmvT4ISfgwtm +nBLJ7a0fDvRRphCC6LUyYMntAHRzAJspYMmgdBb32PClF4WmJ01APtToYFWroUYL +JG6lQAZE92qyhxaR/Juth30zsTVVczsL0zQ16cAEMneNf9LCjZG2f78driNMsX6h +ystjmZOkSuYTKgNc7nfDnNZE +-----END PRIVATE KEY----- diff --git a/hawkbit-configuration-manager/src/test/resources/test-certificates/test-auth-system.p12 b/hawkbit-configuration-manager/src/test/resources/test-certificates/test-auth-system.p12 new file mode 100644 index 000000000..5f54d7f3e Binary files /dev/null and b/hawkbit-configuration-manager/src/test/resources/test-certificates/test-auth-system.p12 differ diff --git a/hawkbit-configuration-manager/src/test/resources/test-certificates/test-cloud.cert.pem b/hawkbit-configuration-manager/src/test/resources/test-certificates/test-cloud.cert.pem new file mode 100644 index 000000000..128567567 --- /dev/null +++ b/hawkbit-configuration-manager/src/test/resources/test-certificates/test-cloud.cert.pem @@ -0,0 +1,52 @@ +Bag Attributes + friendlyName: example.corp.arrowhead.eu + localKeyID: 54 69 6D 65 20 31 36 30 34 35 39 34 31 32 36 38 31 36 +subject=C = DE, ST = Berlin, L = Berlin, O = AHT, OU = arrowhead.eu, CN = example.corp.arrowhead.eu + +issuer=CN = arrowhead.eu + +-----BEGIN CERTIFICATE----- +MIIDgzCCAmugAwIBAgIEX6QoyjANBgkqhkiG9w0BAQsFADAXMRUwEwYDVQQDDAxh +cnJvd2hlYWQuZXUwHhcNMjAxMTA1MTYzMTA2WhcNMzAxMTA1MTYzMTA2WjB4MQsw +CQYDVQQGEwJERTEPMA0GA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4xDDAK +BgNVBAoMA0FIVDEVMBMGA1UECwwMYXJyb3doZWFkLmV1MSIwIAYDVQQDDBlleGFt +cGxlLmNvcnAuYXJyb3doZWFkLmV1MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAmrcvpwqTKaGgpQLuqXMi6TrH5KxkpFH8lH5vq2aozu7QnZexM0Qyf1TY +kvpiy3sDTu6mzGysT96Qv8K8CI8g24oIVRKnJUEs23NgS+KUK6Nz2eIjRaaMVRh4 +cFBcRkr3oFbk+ujGySn+BR8v4eRjq5+qzFX8fswcoeBmlkU+M92kg/Bcy65Pg2v5 +1ORuGhbnmqsEuoyOsSjucL43zTOQtwAva5rvdERVPh7978I+UGFUXaSQnYpP8oyw +CRRIYrPTBHHBA0Q/7sDb2lCA1yLPSwD/H5QvFOxmN3cYAnzvgQZO8+spCiy1T7Kx +FBAnutVn8Sj6BeFNGZvszmlDCs54AwIDAQABo3YwdDAPBgNVHRMECDAGAQH/AgEC +MEIGA1UdIwQ7MDmAFJqKmR4xTB6y5i22mu3HHjHK3Tv+oRukGTAXMRUwEwYDVQQD +DAxhcnJvd2hlYWQuZXWCBFzVPq0wHQYDVR0OBBYEFBOMBYikgCvo8FXUYauQwlW8 +VZvqMA0GCSqGSIb3DQEBCwUAA4IBAQAEhHykqvmRnl3S0aVv4tSoA+tGxwBMaRoY +OvxEDC321CFY/QkWaQfUpVg/eGgsLLFweUp9OFXxkgK4H/rUvQ/twt7oUIDUdJd+ +sGn04mIaym3M9IWhCTxqYf+X3/SHWmI5GnT3dQu9ayJKitD28/6FMLMqEdPiON/9 +VMzkoD2ndN6VSJWOWSg3xmOJ93u+xGbTkweIFildnhf79QwgColJGBytIWBhggvV +DVccNfLqmq9NNaxvPx/y2Oe1ngNhLcqqTUSiwKH42pdupKWZD/EcNlMXttRrkiOt +S3sXv18pDV/094abPB38ulgeeY7Kt2lz12qK7irxjZsVPahyW1Z0 +-----END CERTIFICATE----- +Bag Attributes + friendlyName: CN=arrowhead.eu +subject=CN = arrowhead.eu + +issuer=CN = arrowhead.eu + +-----BEGIN CERTIFICATE----- +MIIC3jCCAcagAwIBAgIEXNU+rTANBgkqhkiG9w0BAQsFADAXMRUwEwYDVQQDDAxh +cnJvd2hlYWQuZXUwHhcNMTkwNTEwMDkwNDQ1WhcNMjkwNTEwMDkwNDQ1WjAXMRUw +EwYDVQQDDAxhcnJvd2hlYWQuZXUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQCuB4z+wchXDKdfy9YFZha2U0khBAWuHYerBlBLM0Oqvr4c/YYcZpNwTWY2 +tk6UXPUTQ8gI9V6Ob7DRXoAfLDhCRGKySN0BlGnjUJkItP25Sj0RfiTL3b8fFEIT +Z8pg+6pAfeFQgV0yz+ziyL+0uu69VZPv+RAEf1GKgshGGLJw3sOlcIdKuZaEAaA2 +b0nDUn229VpKXb9cg47Ae1Yb0sJcTkyIBuhkQKln3uhG2xct9nDfVal05+229AJQ +Ly1f0UfEofvD/OLjFG3umF857T1Vr5azj8zFOvNi503gV458lH3wKC+9UHUf46sg +Hc8Tyrz1q8VAIamJe7BLUeRFwGvBAgMBAAGjMjAwMA8GA1UdEwQIMAYBAf8CAQMw +HQYDVR0OBBYEFJqKmR4xTB6y5i22mu3HHjHK3Tv+MA0GCSqGSIb3DQEBCwUAA4IB +AQCkFsqyeAjztDBkTQrPxAB0Vvx6KPINApHGIHkJj/9crKXZEQcNJcJr35hfLcgv +hSsmLMdeRFCeaG5QLmUKI6GFYIbX+6nawMLGzIPUTOGetNeuMauDXkq09Hu/UmjN +AOgoD5vWdtyTbItv21enJnUelClAJ7VXti2QpyRM2puPHpZMNi4FWgLGPo6hq5ka +d7KomzW8JLh2Vd67v/6mXGpST4EzyRe+Yb2FJZUmhxVWt68/MFaflPQ2toPIsIpW +5m4OS+rT7t+uxPKWU/ogCK7BOUfE/qf3Al/osWkNKnFQDtO/7x7InEmRoP9EUv07 +JY4vK/+k5fJUZHpJzpuKxbBo +-----END CERTIFICATE----- diff --git a/hawkbit-configuration-manager/src/test/resources/test-certificates/test-cloud.key.pem b/hawkbit-configuration-manager/src/test/resources/test-certificates/test-cloud.key.pem new file mode 100644 index 000000000..5c201123b --- /dev/null +++ b/hawkbit-configuration-manager/src/test/resources/test-certificates/test-cloud.key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCaty+nCpMpoaCl +Au6pcyLpOsfkrGSkUfyUfm+rZqjO7tCdl7EzRDJ/VNiS+mLLewNO7qbMbKxP3pC/ +wrwIjyDbighVEqclQSzbc2BL4pQro3PZ4iNFpoxVGHhwUFxGSvegVuT66MbJKf4F +Hy/h5GOrn6rMVfx+zByh4GaWRT4z3aSD8FzLrk+Da/nU5G4aFueaqwS6jI6xKO5w +vjfNM5C3AC9rmu90RFU+Hv3vwj5QYVRdpJCdik/yjLAJFEhis9MEccEDRD/uwNva +UIDXIs9LAP8flC8U7GY3dxgCfO+BBk7z6ykKLLVPsrEUECe61WfxKPoF4U0Zm+zO +aUMKzngDAgMBAAECggEADZysXp/5jqIfxiTZ5qKwoaCAUcgxGpv0AwSUwpnHpF0y +kYr1FjOb1cQbQeMxqgFqqxkoJ7gv+N72EUkKzh35oBcm4IVBm29+iNg/0q1ep/A7 +kyHoRIqAskPy5L7NP4n8+2hpwQGV68D8DmtOBSvY8RKdLlkqW+WNZ1ic7f+tl1Z3 +vi3o0SfID6FvVTUXhWMopR2eLDOn5oX9MQE1uXqZyBDk8/6DxcMqjleDnVMmJWMz +HPlhPK2TFcW8UPxYuCe2z1s1OMR+sdOKZ0g87wD/GEm95SR8gTzbN6OJkvWVxYIl +RPTk4ukK5gyhJDKAh+/jQXEkzdmwAySBR/x6to2dwQKBgQDSW9XloxjpcvkpkaPe +rXsR+FY/TCBLSfFrtXnKxSmqLxSZBBWHNlyhMO7zdJRa6m9EnPZizIv0Kl/RkHzH +GMc4GYiJEJ9p0kiyB2iDXuxurw2BrCyowNWJOvQ1i8EMULv39DIYGHtTNxHXRPqx +mkPtAed2mi+BWieCtoRJto2JUQKBgQC8SLUEB/f/Wll2ujhyi01WSbNe9+MoOTmn +ibN6Uf3bZtul0MS6uKs4Bljjo9BovyBNXqCVcG+VngyhyoV+Ed/dSjBRnG9nol0o +uMWHDIFn7BjdfT5tekzUy40K2BeU6RRRLZ+Wrh87mVC0H18hTqKQoXPZmQwyXBGQ +sq1bOccXEwKBgQCvRCz3Y+jBuTW4WMw8IDbGRi82FetiT32CzHVpaNTKIuf6hdia +C8Up2Gd/GMby6RlEBbOTpfGFwjiLluMfz5lNOJj1+o+Xz8kZ6+o8ar57igaq4BVl +lSVVbXVDl+mEpU+3zBJg2SUHtH586dAmYe3ubwO3Ycfq8n6w/flCoYNTwQKBgQCb +9eXrVuZ4IFDm5c4II5eGGDp6Of9xvCUjwA5pDi2nZYYfzdSHjpxVJfzPY0wo19hb +/jwqTR5A4tA33FfDW+8BkIiBsYEeaQGdz5/fA97VRF48aZgieyHVSl6kUucFtCPe +Mlp1J/o7Ff4hlbLpFgfWocYiwoG13Um3gCnU3QEq/QKBgQCpXFRfz3CKM2Qs1ThV +Rs7qxcmpReQTRy0uMQ5C/VTMSI+S9LAreVn/juZV4DY+ukV6gXFENpXRzU+K71cN +nducNnGJ0LoiNdtiPuCiCVLNkuYU87HlIPIc07g3MSUJ6ZVs38mI8yFnGhXsMpfB +9WU9YlS49UkHvw9ma7U8+MldoQ== +-----END PRIVATE KEY----- diff --git a/hawkbit-configuration-manager/src/test/resources/test-certificates/test-cloud.p12 b/hawkbit-configuration-manager/src/test/resources/test-certificates/test-cloud.p12 new file mode 100644 index 000000000..ac088c1cb Binary files /dev/null and b/hawkbit-configuration-manager/src/test/resources/test-certificates/test-cloud.p12 differ diff --git a/hawkbit-configuration-manager/src/test/resources/test-certificates/test-cloud.truststore.p12 b/hawkbit-configuration-manager/src/test/resources/test-certificates/test-cloud.truststore.p12 new file mode 100644 index 000000000..434905c9a Binary files /dev/null and b/hawkbit-configuration-manager/src/test/resources/test-certificates/test-cloud.truststore.p12 differ diff --git a/hawkbit-configuration-manager/src/test/resources/test-certificates/test-conf-system.p12 b/hawkbit-configuration-manager/src/test/resources/test-certificates/test-conf-system.p12 new file mode 100644 index 000000000..0c7e3633e Binary files /dev/null and b/hawkbit-configuration-manager/src/test/resources/test-certificates/test-conf-system.p12 differ diff --git a/hawkbit-configuration-manager/src/test/resources/test-certificates/test-hdevice.p12 b/hawkbit-configuration-manager/src/test/resources/test-certificates/test-hdevice.p12 new file mode 100644 index 000000000..c7eb364e2 Binary files /dev/null and b/hawkbit-configuration-manager/src/test/resources/test-certificates/test-hdevice.p12 differ diff --git a/hawkbit-configuration-manager/src/test/resources/test-certificates/test-orch.p12 b/hawkbit-configuration-manager/src/test/resources/test-certificates/test-orch.p12 new file mode 100644 index 000000000..119baf27a Binary files /dev/null and b/hawkbit-configuration-manager/src/test/resources/test-certificates/test-orch.p12 differ diff --git a/hawkbit-configuration-manager/src/test/resources/test-certificates/test-sreg.cert.pem b/hawkbit-configuration-manager/src/test/resources/test-certificates/test-sreg.cert.pem new file mode 100644 index 000000000..72e8d632a --- /dev/null +++ b/hawkbit-configuration-manager/src/test/resources/test-certificates/test-sreg.cert.pem @@ -0,0 +1,80 @@ +Bag Attributes + friendlyName: service_registry.example.corp.arrowhead.eu + localKeyID: 54 69 6D 65 20 31 36 30 34 36 36 32 37 30 37 31 37 34 +subject=CN = service_registry.example.corp.arrowhead.eu + +issuer=C = DE, ST = Berlin, L = Berlin, O = AHT, OU = arrowhead.eu, CN = example.corp.arrowhead.eu + +-----BEGIN CERTIFICATE----- +MIIDrTCCApWgAwIBAgIEX6UzaTANBgkqhkiG9w0BAQsFADB4MQswCQYDVQQGEwJE +RTEPMA0GA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4xDDAKBgNVBAoMA0FI +VDEVMBMGA1UECwwMYXJyb3doZWFkLmV1MSIwIAYDVQQDDBlleGFtcGxlLmNvcnAu +YXJyb3doZWFkLmV1MB4XDTIwMTEwNjExMjg0MVoXDTMwMTEwNjExMjg0MVowNTEz +MDEGA1UEAwwqc2VydmljZV9yZWdpc3RyeS5leGFtcGxlLmNvcnAuYXJyb3doZWFk +LmV1MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxRAbGzhXa/9qmsVs +tavu72oQaPxaqkinE4NOxlqv309nKrtV24ETW0xilD9L6UUkdHOMXDuCi9vSivHD +9LT+lSIgy+O1+7mpH4ykN9Fv6KSXoDCt37+nCV8ysp7syy1cVh9sMGbGZcbqenOn +S7aNZ0YlLMsvgbB4rG2CdRRFtOUaVWre3lKd3U6DW69jTk+ObtbLymfrlDqTugmj +FWbBLbEq5MCtnDR1sZ5aupHW8KTxnqMOz4Q0Xg7NYUAad1hGkzkM304Y8BO99RNp +Of96B54R8zB30nvsCBR7vp4/pzgqtf4b2qY25eU1Sn530LHpp2TrEumUpyyTbh8P +xGssiwIDAQABo4GBMH8wGgYDVR0RBBMwEYIJbG9jYWxob3N0hwR/AAABMEIGA1Ud +IwQ7MDmAFBOMBYikgCvo8FXUYauQwlW8VZvqoRukGTAXMRUwEwYDVQQDDAxhcnJv +d2hlYWQuZXWCBF+kKMowHQYDVR0OBBYEFMRvboBK5sZoOK6kt5PUmkNnWrTtMA0G +CSqGSIb3DQEBCwUAA4IBAQA2yW1neYa+FCfiBQApV17RJDA8CbdU0jgcJNwwg1OG +AHhtDZ5zy/v/mfI1fYF3aiB/UgIfaNzqRtTLNLdpSgV3TNCsP5O7EYgyElQkNcIH +LJAFbqUnObFgrhuU6Sdv7qtHy3s0sKePDvpxa4G6jD6RdJqxCd5ziIjh57tFE3bt +0KMFdMKL9YahBElPrwqqA6wEKWHQBJSg841Up4noYg1HI32hJQP2pzeXl5zU0grR +Cp5y37gEa0KPosQp7u9e6D1O1/WyYuEZ5p81fCgdzNsJc77Pi147AT5UQLxgv3FZ +mchn0yB3mDBE/qmPSeoq5L8lwkpjHuDHbTZA8LSg8CbO +-----END CERTIFICATE----- +Bag Attributes + friendlyName: CN=example.corp.arrowhead.eu,OU=arrowhead.eu,O=AHT,L=Berlin,ST=Berlin,C=DE +subject=C = DE, ST = Berlin, L = Berlin, O = AHT, OU = arrowhead.eu, CN = example.corp.arrowhead.eu + +issuer=CN = arrowhead.eu + +-----BEGIN CERTIFICATE----- +MIIDgzCCAmugAwIBAgIEX6QoyjANBgkqhkiG9w0BAQsFADAXMRUwEwYDVQQDDAxh +cnJvd2hlYWQuZXUwHhcNMjAxMTA1MTYzMTA2WhcNMzAxMTA1MTYzMTA2WjB4MQsw +CQYDVQQGEwJERTEPMA0GA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4xDDAK +BgNVBAoMA0FIVDEVMBMGA1UECwwMYXJyb3doZWFkLmV1MSIwIAYDVQQDDBlleGFt +cGxlLmNvcnAuYXJyb3doZWFkLmV1MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAmrcvpwqTKaGgpQLuqXMi6TrH5KxkpFH8lH5vq2aozu7QnZexM0Qyf1TY +kvpiy3sDTu6mzGysT96Qv8K8CI8g24oIVRKnJUEs23NgS+KUK6Nz2eIjRaaMVRh4 +cFBcRkr3oFbk+ujGySn+BR8v4eRjq5+qzFX8fswcoeBmlkU+M92kg/Bcy65Pg2v5 +1ORuGhbnmqsEuoyOsSjucL43zTOQtwAva5rvdERVPh7978I+UGFUXaSQnYpP8oyw +CRRIYrPTBHHBA0Q/7sDb2lCA1yLPSwD/H5QvFOxmN3cYAnzvgQZO8+spCiy1T7Kx +FBAnutVn8Sj6BeFNGZvszmlDCs54AwIDAQABo3YwdDAPBgNVHRMECDAGAQH/AgEC +MEIGA1UdIwQ7MDmAFJqKmR4xTB6y5i22mu3HHjHK3Tv+oRukGTAXMRUwEwYDVQQD +DAxhcnJvd2hlYWQuZXWCBFzVPq0wHQYDVR0OBBYEFBOMBYikgCvo8FXUYauQwlW8 +VZvqMA0GCSqGSIb3DQEBCwUAA4IBAQAEhHykqvmRnl3S0aVv4tSoA+tGxwBMaRoY +OvxEDC321CFY/QkWaQfUpVg/eGgsLLFweUp9OFXxkgK4H/rUvQ/twt7oUIDUdJd+ +sGn04mIaym3M9IWhCTxqYf+X3/SHWmI5GnT3dQu9ayJKitD28/6FMLMqEdPiON/9 +VMzkoD2ndN6VSJWOWSg3xmOJ93u+xGbTkweIFildnhf79QwgColJGBytIWBhggvV +DVccNfLqmq9NNaxvPx/y2Oe1ngNhLcqqTUSiwKH42pdupKWZD/EcNlMXttRrkiOt +S3sXv18pDV/094abPB38ulgeeY7Kt2lz12qK7irxjZsVPahyW1Z0 +-----END CERTIFICATE----- +Bag Attributes + friendlyName: CN=arrowhead.eu +subject=CN = arrowhead.eu + +issuer=CN = arrowhead.eu + +-----BEGIN CERTIFICATE----- +MIIC3jCCAcagAwIBAgIEXNU+rTANBgkqhkiG9w0BAQsFADAXMRUwEwYDVQQDDAxh +cnJvd2hlYWQuZXUwHhcNMTkwNTEwMDkwNDQ1WhcNMjkwNTEwMDkwNDQ1WjAXMRUw +EwYDVQQDDAxhcnJvd2hlYWQuZXUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQCuB4z+wchXDKdfy9YFZha2U0khBAWuHYerBlBLM0Oqvr4c/YYcZpNwTWY2 +tk6UXPUTQ8gI9V6Ob7DRXoAfLDhCRGKySN0BlGnjUJkItP25Sj0RfiTL3b8fFEIT +Z8pg+6pAfeFQgV0yz+ziyL+0uu69VZPv+RAEf1GKgshGGLJw3sOlcIdKuZaEAaA2 +b0nDUn229VpKXb9cg47Ae1Yb0sJcTkyIBuhkQKln3uhG2xct9nDfVal05+229AJQ +Ly1f0UfEofvD/OLjFG3umF857T1Vr5azj8zFOvNi503gV458lH3wKC+9UHUf46sg +Hc8Tyrz1q8VAIamJe7BLUeRFwGvBAgMBAAGjMjAwMA8GA1UdEwQIMAYBAf8CAQMw +HQYDVR0OBBYEFJqKmR4xTB6y5i22mu3HHjHK3Tv+MA0GCSqGSIb3DQEBCwUAA4IB +AQCkFsqyeAjztDBkTQrPxAB0Vvx6KPINApHGIHkJj/9crKXZEQcNJcJr35hfLcgv +hSsmLMdeRFCeaG5QLmUKI6GFYIbX+6nawMLGzIPUTOGetNeuMauDXkq09Hu/UmjN +AOgoD5vWdtyTbItv21enJnUelClAJ7VXti2QpyRM2puPHpZMNi4FWgLGPo6hq5ka +d7KomzW8JLh2Vd67v/6mXGpST4EzyRe+Yb2FJZUmhxVWt68/MFaflPQ2toPIsIpW +5m4OS+rT7t+uxPKWU/ogCK7BOUfE/qf3Al/osWkNKnFQDtO/7x7InEmRoP9EUv07 +JY4vK/+k5fJUZHpJzpuKxbBo +-----END CERTIFICATE----- diff --git a/hawkbit-configuration-manager/src/test/resources/test-certificates/test-sreg.key.pem b/hawkbit-configuration-manager/src/test/resources/test-certificates/test-sreg.key.pem new file mode 100644 index 000000000..0d24c2281 --- /dev/null +++ b/hawkbit-configuration-manager/src/test/resources/test-certificates/test-sreg.key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDFEBsbOFdr/2qa +xWy1q+7vahBo/FqqSKcTg07GWq/fT2cqu1XbgRNbTGKUP0vpRSR0c4xcO4KL29KK +8cP0tP6VIiDL47X7uakfjKQ30W/opJegMK3fv6cJXzKynuzLLVxWH2wwZsZlxup6 +c6dLto1nRiUsyy+BsHisbYJ1FEW05RpVat7eUp3dToNbr2NOT45u1svKZ+uUOpO6 +CaMVZsEtsSrkwK2cNHWxnlq6kdbwpPGeow7PhDReDs1hQBp3WEaTOQzfThjwE731 +E2k5/3oHnhHzMHfSe+wIFHu+nj+nOCq1/hvapjbl5TVKfnfQsemnZOsS6ZSnLJNu +Hw/EayyLAgMBAAECggEAEK4k/TrZpi8/1iCb67dv9B1QN51qDJE1wzW8Iey53Pca ++Rz+Ts7a1UGJSUR0/SZ4ctBNCImTqpmiyqwINYW/KYnH/jokxKKZ1C0oFqlzBYgu +ZXAkEj+7YymxtY405kXNU7o5LXz653vc4RyPrp2h089g2i0gtPjmXgIEUZCzuHSo +privjmUQtX/8MSj68gDQ5555/EUtWhMjEBrl0nOBWcsg3vNhPtHTWJCnKMMlA/hr +hqItoygjjYMMXujxX/laPke1yjPiXA7NP/YM1gAZUicB7TPaf9ltJ3bMyhnuUWab +k5iprSGMlxYaJIjoHDYtzRYe6DAZ998YMfjJD3MosQKBgQDpXp8vBi819Ucw4viQ +N5Fh1GOgq7QaUlMdobZDe6AUKC2ngdlvt3linmbGsLjivxwcgmBBqT+5vdAN4ND4 +JURvobAiLNE+1pUEdNBUJ62RrorIEqHH/Egrgb9azW5GT+WRk3TPWL78WIC/PBfI +laJetx32KlVc+vVYcJH9yk8IzQKBgQDYLCx2Ysz3H+XFa5Wvar5owrhBXBZoWclR +8yHIYyrjaqYQIo+cy8Lhneh+KQ97DITTTuJHAv1gvMxtwnU2ZiJCNYdgEl4+vH+t +9aJTjdTI5NvYb1mxDOA9JUT3DzZK1s2VnkOmA43eIlPCog7ZkvHKt/rX0iKX2NaX +wsY/GWNqtwKBgQCaBNYAXg+IiBHtJM8xt0rfCyKZptjdylmKo/C7xvqWcxH2jI9p +2Ohm+u4P0hCjrceq1S0cCMzDFJAcqLSiIU5ycn7hfzy2QT7mSwY3lFxMWqrDcvCN +IWasOByHnC9cflyf4HbmZcbemraV/94ehws7gZVnovblv8dvEBR6MCxLPQKBgBH1 +0Zfga5EYqFl3r88MOlev3ekoQoBW/V/+qE7i1lxgrv1mMbJgR4fBO+DHfo7Fon9/ +7VLjD7Qq230/C8gCQlRn6CB2RjhGJwGIB/2TKhuq0A0yayxHmpXZ89nm/KbJI/mL +VamEYBQVnAnutvciu04RbRjjT5Z4IJQpTZlOfbEPAoGAO6RBn7kHsSh4F6QEVS4U +nL5X8uG0I6xSdLDT9xNbyjwHs5r0EBkjo+QXG5vC/ofgVp52DEPoicSCue5l/C3e +h2kq/H55iHkcJC/EVEwOAiGZPOznFMzwfWCT8sW9uIloe+vLAJiqezltKK+oDA2P +/rpiixiWCPNlHvre5ugcPw0= +-----END PRIVATE KEY----- diff --git a/hawkbit-configuration-manager/src/test/resources/test-certificates/test-sreg.p12 b/hawkbit-configuration-manager/src/test/resources/test-certificates/test-sreg.p12 new file mode 100644 index 000000000..caf350bff Binary files /dev/null and b/hawkbit-configuration-manager/src/test/resources/test-certificates/test-sreg.p12 differ diff --git a/hawkbit-configuration-manager/src/test/resources/test-certificates/test-sysop.p12 b/hawkbit-configuration-manager/src/test/resources/test-certificates/test-sysop.p12 new file mode 100644 index 000000000..7668c04a2 Binary files /dev/null and b/hawkbit-configuration-manager/src/test/resources/test-certificates/test-sysop.p12 differ diff --git a/kubernetes/README.md b/kubernetes/README.md new file mode 100644 index 000000000..4584cd26c --- /dev/null +++ b/kubernetes/README.md @@ -0,0 +1,50 @@ +# Arrowhead Core Systems Helm chart + +## Introduction + +This Helm chart enables you to quickly setup the Arrowhead core systems on your Kubernetes cluster. +Currently, the three mandatory core systems (service registry, authorization system and orchestrator) are supported. + +Supported core systems: + +| core system | included | +| ----------------- | :--------: | +| Service Registry | ☑ | +| Authorization System | ☑ | +| Orchestrator | ☑ | +| event handler | | +| Gatekeeper | | +| Gateway | | +| Gateway | | +| Certificate Authority | | +| QoS Monitor | | +| Onboarding Controller | | +| Device Registry | | +| System Registry | | +| Choreographer | | + +## Ingress +This deployment uses the [Ambassador Emissary Ingress implementation](https://www.getambassador.io/products/api-gateway/) to route traffic to each core system. +You will need to provision a static and public IP address beforehand. +The required steps for that strongly depend on the cloud provider or (in case you are hosting the cluster by yourself) Kubernetes distribution you are using. + +To let Ambassador know, on what IP address it should listen on, modify the `emissary-ingress.service.loadBalancerIP` property in the `values.yaml`. +In case you do not have a domain name yet, you need to set the `domain` property for each service to the same public IP address. +Also, set the ports your core systems are listening on. +By default the port range starts at `3000` for the service registry and continues with `3001` for the authorization system, etc. +These ports also need to map to the exposed Ambassador ports. + +## Setup +To configure the core system properties, modify the `values.yaml` accordingly. +Remember to set the datasource credentials which is used to setup a connection with the database. +Furthermore, you need to generate your cloud and core system certificates as keystores (PKCS12) and place them under `static/certificates`. +Now, modify the properties `keystore`, `keystorePassword`, `keyAlias`, `keyPassword`, `truststore` and `truststorePassword` in the `values.yaml` for each core system. + +## Deployment +Load the necessary dependencies: `helm dep up arrowhead/` + +Apply the Ambassador CRDs: `kubectl apply -f https://app.getambassador.io/yaml/emissary/2.1.0/emissary-crds.yaml` + +Now, we have to create a new namespace in your cluster: `kubectl create namespace arrowhead` + +Finally, you can start the actual deployment by running `helm install arrowhead arrowhead/ -n arrowhead` \ No newline at end of file diff --git a/kubernetes/arrowhead-helm/.gitignore b/kubernetes/arrowhead-helm/.gitignore new file mode 100644 index 000000000..ee27b40cf --- /dev/null +++ b/kubernetes/arrowhead-helm/.gitignore @@ -0,0 +1,2 @@ +*.tgz +.DS_Store \ No newline at end of file diff --git a/kubernetes/arrowhead-helm/.helmignore b/kubernetes/arrowhead-helm/.helmignore new file mode 100644 index 000000000..0e8a0eb36 --- /dev/null +++ b/kubernetes/arrowhead-helm/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/kubernetes/arrowhead-helm/Chart.lock b/kubernetes/arrowhead-helm/Chart.lock new file mode 100644 index 000000000..05f55d90e --- /dev/null +++ b/kubernetes/arrowhead-helm/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: emissary-ingress + repository: https://app.getambassador.io + version: 7.2.0 +digest: sha256:69f5374b042286ae53e60729065a58181b47d8bafa8b1cfdf8a9809c93834451 +generated: "2022-01-04T18:44:09.734379+01:00" diff --git a/kubernetes/arrowhead-helm/Chart.yaml b/kubernetes/arrowhead-helm/Chart.yaml new file mode 100644 index 000000000..e34f603d5 --- /dev/null +++ b/kubernetes/arrowhead-helm/Chart.yaml @@ -0,0 +1,14 @@ +apiVersion: v2 +name: arrowhead +description: A Helm chart for the Arrowhead core systems + +type: application + +version: 1.0.0 + +appVersion: 4.4.0 + +dependencies: + - name: emissary-ingress + version: 7.2.0 + repository: https://app.getambassador.io \ No newline at end of file diff --git a/kubernetes/arrowhead-helm/charts/.gitignore b/kubernetes/arrowhead-helm/charts/.gitignore new file mode 100644 index 000000000..9e30eb9b7 --- /dev/null +++ b/kubernetes/arrowhead-helm/charts/.gitignore @@ -0,0 +1 @@ +*.tgz \ No newline at end of file diff --git a/kubernetes/arrowhead-helm/static/certificates/test-auth-system.p12 b/kubernetes/arrowhead-helm/static/certificates/test-auth-system.p12 new file mode 100644 index 000000000..5f3c83526 Binary files /dev/null and b/kubernetes/arrowhead-helm/static/certificates/test-auth-system.p12 differ diff --git a/kubernetes/arrowhead-helm/static/certificates/test-cloud.truststore.p12 b/kubernetes/arrowhead-helm/static/certificates/test-cloud.truststore.p12 new file mode 100644 index 000000000..2d45b8ba5 Binary files /dev/null and b/kubernetes/arrowhead-helm/static/certificates/test-cloud.truststore.p12 differ diff --git a/kubernetes/arrowhead-helm/static/certificates/test-orch.p12 b/kubernetes/arrowhead-helm/static/certificates/test-orch.p12 new file mode 100644 index 000000000..ef0072d66 Binary files /dev/null and b/kubernetes/arrowhead-helm/static/certificates/test-orch.p12 differ diff --git a/kubernetes/arrowhead-helm/static/certificates/test-sreg.p12 b/kubernetes/arrowhead-helm/static/certificates/test-sreg.p12 new file mode 100644 index 000000000..ef80d8ea1 Binary files /dev/null and b/kubernetes/arrowhead-helm/static/certificates/test-sreg.p12 differ diff --git a/kubernetes/arrowhead-helm/static/certificates/testcloud2.aitia.arrowhead.eu.p12 b/kubernetes/arrowhead-helm/static/certificates/testcloud2.aitia.arrowhead.eu.p12 new file mode 100644 index 000000000..071c456b1 Binary files /dev/null and b/kubernetes/arrowhead-helm/static/certificates/testcloud2.aitia.arrowhead.eu.p12 differ diff --git a/kubernetes/arrowhead-helm/static/db-init/authorization_privileges.sql b/kubernetes/arrowhead-helm/static/db-init/authorization_privileges.sql new file mode 100644 index 000000000..0484c30a3 --- /dev/null +++ b/kubernetes/arrowhead-helm/static/db-init/authorization_privileges.sql @@ -0,0 +1,33 @@ +USE `arrowhead`; + +REVOKE ALL, GRANT OPTION FROM 'authorization'@'localhost'; + +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_intra_cloud` TO 'authorization'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_intra_cloud_interface_connection` TO 'authorization'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_inter_cloud` TO 'authorization'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_inter_cloud_interface_connection` TO 'authorization'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`system_` TO 'authorization'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`cloud` TO 'authorization'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`relay` TO 'authorization'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`cloud_gatekeeper_relay` TO 'authorization'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`cloud_gateway_relay` TO 'authorization'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`service_definition` TO 'authorization'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`service_interface` TO 'authorization'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`logs` TO 'authorization'@'localhost'; + +REVOKE ALL, GRANT OPTION FROM 'authorization'@'%'; + +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_intra_cloud` TO 'authorization'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_intra_cloud_interface_connection` TO 'authorization'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_inter_cloud` TO 'authorization'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_inter_cloud_interface_connection` TO 'authorization'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`system_` TO 'authorization'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`cloud` TO 'authorization'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`relay` TO 'authorization'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`cloud_gatekeeper_relay` TO 'authorization'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`cloud_gateway_relay` TO 'authorization'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`service_definition` TO 'authorization'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`service_interface` TO 'authorization'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`logs` TO 'authorization'@'%'; + +FLUSH PRIVILEGES; \ No newline at end of file diff --git a/kubernetes/arrowhead-helm/static/db-init/certificate_authority_privileges.sql b/kubernetes/arrowhead-helm/static/db-init/certificate_authority_privileges.sql new file mode 100644 index 000000000..413aa5090 --- /dev/null +++ b/kubernetes/arrowhead-helm/static/db-init/certificate_authority_privileges.sql @@ -0,0 +1,13 @@ +USE `arrowhead`; + +REVOKE ALL, GRANT OPTION FROM 'certificate_authority'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`logs` TO 'certificate_authority'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`ca_certificate` TO 'certificate_authority'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`ca_trusted_key` TO 'certificate_authority'@'localhost'; + +REVOKE ALL, GRANT OPTION FROM 'certificate_authority'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`logs` TO 'certificate_authority'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`ca_certificate` TO 'certificate_authority'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`ca_trusted_key` TO 'certificate_authority'@'%'; + +FLUSH PRIVILEGES; diff --git a/kubernetes/arrowhead-helm/static/db-init/choreographer_privileges.sql b/kubernetes/arrowhead-helm/static/db-init/choreographer_privileges.sql new file mode 100644 index 000000000..b022267bc --- /dev/null +++ b/kubernetes/arrowhead-helm/static/db-init/choreographer_privileges.sql @@ -0,0 +1,27 @@ +USE `arrowhead`; + +REVOKE ALL, GRANT OPTION FROM 'choreographer'@'localhost'; + +GRANT ALL PRIVILEGES ON `arrowhead`.`choreographer_plan` TO 'choreographer'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`choreographer_action` TO 'choreographer'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`choreographer_step` TO 'choreographer'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`choreographer_action_step_service_definition_connection` TO 'choreographer'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`choreographer_step_next_step_connection` TO 'choreographer'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`choreographer_session` TO 'choreographer'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`choreographer_running_step` TO 'choreographer'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`choreographer_worklog` TO 'choreographer'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`logs` TO 'choreographer'@'localhost'; + +REVOKE ALL, GRANT OPTION FROM 'choreographer'@'%'; + +GRANT ALL PRIVILEGES ON `arrowhead`.`choreographer_plan` TO 'choreographer'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`choreographer_action` TO 'choreographer'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`choreographer_step` TO 'choreographer'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`choreographer_step_service_definition_connection` TO 'choreographer'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`choreographer_step_next_step_connection` TO 'choreographer'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`choreographer_session` TO 'choreographer'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`choreographer_running_step` TO 'choreographer'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`choreographer_worklog` TO 'choreographer'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`logs` TO 'choreographer'@'%'; + +FLUSH PRIVILEGES; \ No newline at end of file diff --git a/kubernetes/arrowhead-helm/static/db-init/configuration_privileges.sql b/kubernetes/arrowhead-helm/static/db-init/configuration_privileges.sql new file mode 100644 index 000000000..fd9ad9956 --- /dev/null +++ b/kubernetes/arrowhead-helm/static/db-init/configuration_privileges.sql @@ -0,0 +1,11 @@ +USE `arrowhead`; + +REVOKE ALL, GRANT OPTION FROM 'configuration'@'localhost'; + +GRANT ALL PRIVILEGES ON `arrowhead`.`configuration_data` TO 'configuration'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`logs` TO 'configuration'@'localhost'; + +GRANT ALL PRIVILEGES ON `arrowhead`.`configuration_data` TO 'configuration'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`logs` TO 'configuration'@'%'; + +FLUSH PRIVILEGES; diff --git a/kubernetes/arrowhead-helm/static/db-init/create_arrowhead_tables.sql b/kubernetes/arrowhead-helm/static/db-init/create_arrowhead_tables.sql new file mode 100644 index 000000000..dfac3666f --- /dev/null +++ b/kubernetes/arrowhead-helm/static/db-init/create_arrowhead_tables.sql @@ -0,0 +1,731 @@ +CREATE DATABASE IF NOT EXISTS `arrowhead`; +USE `arrowhead`; + +-- Common + +CREATE TABLE IF NOT EXISTS `cloud` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `operator` varchar(255) NOT NULL, + `name` varchar(255) NOT NULL, + `secure` int(1) NOT NULL DEFAULT 0 COMMENT 'Is secure?', + `neighbor` int(1) NOT NULL DEFAULT 0 COMMENT 'Is neighbor cloud?', + `own_cloud` int(1) NOT NULL DEFAULT 0 COMMENT 'Is own cloud?', + `authentication_info` varchar(2047) DEFAULT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `cloud` (`operator`,`name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `relay` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `address` varchar(255) NOT NULL, + `port` int(11) NOT NULL, + `secure` int(1) NOT NULL DEFAULT 0, + `exclusive` int(1) NOT NULL DEFAULT 0, + `type` varchar(255) NOT NULL DEFAULT 'GENERAL_RELAY', + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `pair` (`address`, `port`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `cloud_gatekeeper_relay` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `cloud_id` bigint(20) NOT NULL, + `relay_id` bigint(20) NOT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `pair` (`cloud_id`,`relay_id`), + CONSTRAINT `gk_cloud_constr` FOREIGN KEY (`cloud_id`) REFERENCES `cloud` (`id`) ON DELETE CASCADE, + CONSTRAINT `gk_relay_constr` FOREIGN KEY (`relay_id`) REFERENCES `relay` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `cloud_gateway_relay` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `cloud_id` bigint(20) NOT NULL, + `relay_id` bigint(20) NOT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `pair` (`cloud_id`,`relay_id`), + CONSTRAINT `gw_cloud_constr` FOREIGN KEY (`cloud_id`) REFERENCES `cloud` (`id`) ON DELETE CASCADE, + CONSTRAINT `gw_relay_constr` FOREIGN KEY (`relay_id`) REFERENCES `relay` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `system_` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `system_name` varchar(255) NOT NULL, + `address` varchar(255) NOT NULL, + `port` int(11) NOT NULL, + `authentication_info` varchar(2047) DEFAULT NULL, + `metadata` mediumtext NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `triple` (`system_name`,`address`,`port`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `device` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `device_name` varchar(255) NOT NULL, + `address` varchar(255) NOT NULL, + `mac_address` varchar(255) NOT NULL, + `authentication_info` varchar(2047) DEFAULT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `double` (`device_name`,`mac_address`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `service_definition` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `service_definition` varchar(255) NOT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `service_definition` (`service_definition`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `service_interface` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `interface_name` varchar(255) DEFAULT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `interface` (`interface_name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +INSERT IGNORE INTO `service_interface` (interface_name) VALUES ('HTTP-SECURE-JSON'); +INSERT IGNORE INTO `service_interface` (interface_name) VALUES ('HTTP-INSECURE-JSON'); + +-- Device Registry + +CREATE TABLE IF NOT EXISTS `device_registry` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `device_id` bigint(20) NOT NULL, + `end_of_validity` timestamp NULL DEFAULT NULL, + `metadata` text, + `version` int(11) DEFAULT 1, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `device_registry_device` (`device_id`), + CONSTRAINT `device_registry_device` FOREIGN KEY (`device_id`) REFERENCES `device` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- System Registry + +CREATE TABLE IF NOT EXISTS `system_registry` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `system_id` bigint(20) NOT NULL, + `device_id` bigint(20) NOT NULL, + `end_of_validity` timestamp NULL DEFAULT NULL, + `metadata` text, + `version` int(11) DEFAULT 1, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `system_registry_pair` (`system_id`,`device_id`), + KEY `system_registry_device` (`device_id`), + CONSTRAINT `system_registry_system` FOREIGN KEY (`system_id`) REFERENCES `system_` (`id`) ON DELETE CASCADE, + CONSTRAINT `system_registry_device` FOREIGN KEY (`device_id`) REFERENCES `device` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- Service Registry + +CREATE TABLE IF NOT EXISTS `service_registry` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `service_id` bigint(20) NOT NULL, + `system_id` bigint(20) NOT NULL, + `service_uri` varchar(255) NOT NULL DEFAULT '', + `end_of_validity` timestamp NULL DEFAULT NULL, + `secure` varchar(255) NOT NULL DEFAULT 'NOT_SECURE', + `metadata` mediumtext, + `version` int(11) DEFAULT 1, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `service_registry_triplet` (`service_id`,`system_id`, `service_uri`), + KEY `service_registry_system` (`system_id`), + CONSTRAINT `service_registry_service` FOREIGN KEY (`service_id`) REFERENCES `service_definition` (`id`) ON DELETE CASCADE, + CONSTRAINT `service_registry_system` FOREIGN KEY (`system_id`) REFERENCES `system_` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `service_registry_interface_connection` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `service_registry_id` bigint(20) NOT NULL, + `interface_id` bigint(20) NOT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `pair` (`service_registry_id`,`interface_id`), + KEY `interface_sr` (`interface_id`), + CONSTRAINT `interface_sr` FOREIGN KEY (`interface_id`) REFERENCES `service_interface` (`id`) ON DELETE CASCADE, + CONSTRAINT `service_registry` FOREIGN KEY (`service_registry_id`) REFERENCES `service_registry` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- Authorization + +CREATE TABLE IF NOT EXISTS `authorization_intra_cloud` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `consumer_system_id` bigint(20) NOT NULL, + `provider_system_id` bigint(20) NOT NULL, + `service_id` bigint(20) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `rule` (`consumer_system_id`,`provider_system_id`,`service_id`), + KEY `provider` (`provider_system_id`), + KEY `service_intra_auth` (`service_id`), + CONSTRAINT `service_intra_auth` FOREIGN KEY (`service_id`) REFERENCES `service_definition` (`id`) ON DELETE CASCADE, + CONSTRAINT `provider` FOREIGN KEY (`provider_system_id`) REFERENCES `system_` (`id`) ON DELETE CASCADE, + CONSTRAINT `consumer` FOREIGN KEY (`consumer_system_id`) REFERENCES `system_` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `authorization_inter_cloud` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `consumer_cloud_id` bigint(20) NOT NULL, + `provider_system_id` bigint(20) NOT NULL, + `service_id` bigint(20) NOT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `rule` (`consumer_cloud_id`, `provider_system_id`, `service_id`), + KEY `service_inter_auth` (`service_id`), + CONSTRAINT `cloud` FOREIGN KEY (`consumer_cloud_id`) REFERENCES `cloud` (`id`) ON DELETE CASCADE, + CONSTRAINT `service_inter_auth` FOREIGN KEY (`service_id`) REFERENCES `service_definition` (`id`) ON DELETE CASCADE, + CONSTRAINT `provider_inter_auth` FOREIGN KEY (`provider_system_id`) REFERENCES `system_` (id) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `authorization_inter_cloud_interface_connection` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `authorization_inter_cloud_id` bigint(20) NOT NULL, + `interface_id` bigint(20) NOT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `pair` (`authorization_inter_cloud_id`,`interface_id`), + KEY `interface_inter` (`interface_id`), + CONSTRAINT `auth_inter_interface` FOREIGN KEY (`interface_id`) REFERENCES `service_interface` (`id`) ON DELETE CASCADE, + CONSTRAINT `auth_inter_cloud` FOREIGN KEY (`authorization_inter_cloud_id`) REFERENCES `authorization_inter_cloud` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `authorization_intra_cloud_interface_connection` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `authorization_intra_cloud_id` bigint(20) NOT NULL, + `interface_id` bigint(20) NOT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `pair` (`authorization_intra_cloud_id`,`interface_id`), + KEY `interface_intra` (`interface_id`), + CONSTRAINT `auth_intra_interface` FOREIGN KEY (`interface_id`) REFERENCES `service_interface` (`id`) ON DELETE CASCADE, + CONSTRAINT `auth_intra_cloud` FOREIGN KEY (`authorization_intra_cloud_id`) REFERENCES `authorization_intra_cloud` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- Orchestrator + +CREATE TABLE IF NOT EXISTS `orchestrator_store` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `consumer_system_id` bigint(20) NOT NULL, + `provider_system_id` bigint(20) NOT NULL, + `foreign_` int(1) NOT NULL DEFAULT 0, + `service_id` bigint(20) NOT NULL, + `service_interface_id` bigint(20) NOT NULL, + `priority` int(11) NOT NULL, + `attribute` text, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `priority_rule` (`service_id`, `service_interface_id`, `consumer_system_id`,`priority`), + UNIQUE KEY `duplication_rule` (`service_id`, `service_interface_id`, `consumer_system_id`,`provider_system_id`, `foreign_`), + CONSTRAINT `consumer_orch` FOREIGN KEY (`consumer_system_id`) REFERENCES `system_` (`id`) ON DELETE CASCADE, + CONSTRAINT `service_orch` FOREIGN KEY (`service_id`) REFERENCES `service_definition` (`id`) ON DELETE CASCADE, + CONSTRAINT `service_intf_orch` FOREIGN KEY (`service_interface_id`) REFERENCES `service_interface` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `foreign_system` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `provider_cloud_id` bigint(20) NOT NULL, + `system_name` varchar(255) NOT NULL, + `address` varchar(255) NOT NULL, + `port` int(11) NOT NULL, + `authentication_info` varchar(2047) DEFAULT NULL, + `metadata` mediumtext, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `triple` (`system_name`,`address`,`port`), + CONSTRAINT `foreign_cloud` FOREIGN KEY (`provider_cloud_id`) REFERENCES `cloud` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `orchestrator_store_flexible` ( +`id` bigint(20) NOT NULL AUTO_INCREMENT, +`consumer_system_name` varchar(255), +`provider_system_name` varchar(255), +`consumer_system_metadata` mediumtext, +`provider_system_metadata` mediumtext, +`service_metadata` mediumtext, +`service_interface_name` varchar(255), +`service_definition_name` varchar(255) NOT NULL, +`priority` int(11) NOT NULL DEFAULT 2147483647, +`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, +`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, +PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- Logs + +CREATE TABLE IF NOT EXISTS `logs` ( + `log_id` varchar(100) NOT NULL, + `entry_date` timestamp NULL DEFAULT NULL, + `logger` varchar(100) DEFAULT NULL, + `log_level` varchar(100) DEFAULT NULL, + `message` text, + `exception` text, + PRIMARY KEY (`log_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- Event Handler + +CREATE TABLE IF NOT EXISTS `event_type` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `event_type_name` varchar(255) NOT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `eventtype` (`event_type_name`) +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `subscription` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `system_id` bigint(20) NOT NULL, + `event_type_id` bigint(20) NOT NULL, + `filter_meta_data` mediumtext, + `match_meta_data` int(1) NOT NULL DEFAULT 0, + `only_predefined_publishers` int(1) NOT NULL DEFAULT 0, + `notify_uri` text NOT NULL, + `start_date` timestamp NULL DEFAULT NULL, + `end_date` timestamp NULL DEFAULT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `pair` (`event_type_id`,`system_id`), + CONSTRAINT `subscriber_system` FOREIGN KEY (`system_id`) REFERENCES `system_` (`id`) ON DELETE CASCADE, + CONSTRAINT `event_type` FOREIGN KEY (`event_type_id`) REFERENCES `event_type` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `subscription_publisher_connection` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `subscription_id` bigint(20) NOT NULL, + `system_id` bigint(20) NOT NULL, + `authorized` int(1) NOT NULL DEFAULT 0, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `pair` (`subscription_id`,`system_id`), + CONSTRAINT `subscription_constraint` FOREIGN KEY (`subscription_id`) REFERENCES `subscription` (`id`) ON DELETE CASCADE, + CONSTRAINT `system_constraint` FOREIGN KEY (`system_id`) REFERENCES `system_` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- DataManager + +CREATE TABLE IF NOT EXISTS `dmhist_services` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `system_name` varchar(255) NOT NULL, + `service_name` varchar(255) NOT NULL, + `service_type` varchar(255), + last_update timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `dmhist_messages` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `sid` bigint(20) NOT NULL, + `bt` double NOT NULL, + `mint` double NOT NULL, + `maxt` double NOT NULL, + `msg` BLOB NOT NULL, + `datastored` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + CONSTRAINT `service_id_constr` FOREIGN KEY (`sid`) REFERENCES `dmhist_services` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `dmhist_entries` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `sid` bigint(20) NOT NULL, + `mid` bigint(20) NOT NULL, + `n` varchar(128) NOT NULL, + `t` double NOT NULL, + `u` varchar(64), + `v` double, + `vs` BLOB, + `vb` BOOLEAN, + PRIMARY KEY (`id`), + CONSTRAINT `service_id_fk` FOREIGN KEY(`sid`) REFERENCES `dmhist_services`(`id`) ON DELETE CASCADE, + CONSTRAINT `message_id_fk` FOREIGN KEY(`mid`) REFERENCES `dmhist_messages`(`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- TimeManager + +-- Choreographer + +CREATE TABLE IF NOT EXISTS `choreographer_plan` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `name` varchar(255) NOT NULL, + `first_action_id` bigint(20), + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `choreographer_action` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `name` varchar(255) NOT NULL, + `plan_id` bigint(20) NOT NULL, + `next_action_id` bigint(20) DEFAULT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `name_plan_id_unique_key` (`name`,`plan_id`), + CONSTRAINT `next_action` FOREIGN KEY (`next_action_id`) REFERENCES `choreographer_action` (`id`) ON DELETE CASCADE, + CONSTRAINT `plan` FOREIGN KEY (`plan_id`) REFERENCES `choreographer_plan` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; + +ALTER TABLE `choreographer_plan` ADD FOREIGN KEY (`first_action_id`) references `choreographer_action`(`id`); + +CREATE TABLE IF NOT EXISTS `choreographer_step` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `name` varchar(255) NOT NULL, + `action_first_step_id` bigint(20), + `action_id` bigint(20) NOT NULL, + `service_name` varchar(255) NOT NULL, + `metadata` text, + `parameters` text, + `quantity` int(20) NOT NULL DEFAULT 1, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `name_action_id_unique_key` (`name`, `action_id`), + CONSTRAINT `action_first_step` FOREIGN KEY (`action_first_step_id`) REFERENCES `choreographer_action` (`id`) ON DELETE CASCADE, + CONSTRAINT `action` FOREIGN KEY (`action_id`) REFERENCES `choreographer_action` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `choreographer_step_next_step_connection` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `step_id` bigint(20) NOT NULL, + `next_step_id` bigint(20) NOT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + CONSTRAINT `current_step` FOREIGN KEY (`step_id`) REFERENCES choreographer_step (`id`) ON DELETE CASCADE, + CONSTRAINT `next_step` FOREIGN KEY (`step_id`) REFERENCES choreographer_step (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `choreographer_session` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `plan_id` bigint(20) NOT NULL, + `started_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `status` varchar(255) NOT NULL, + PRIMARY KEY (`id`), + CONSTRAINT `session_plan` FOREIGN KEY (`plan_id`) REFERENCES `choreographer_plan` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `choreographer_running_step` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `step_id` bigint(20) NOT NULL, + `session_id` bigint(20) NOT NULL, + `status` varchar(255) NOT NULL, + `message` text, + `started_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + CONSTRAINT `running_step` FOREIGN KEY (`step_id`) REFERENCES `choreographer_step` (`id`), + CONSTRAINT `running_step_session` FOREIGN KEY (`session_id`) REFERENCES `choreographer_session`(`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `choreographer_worklog` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `entry_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `message` text, + `exception` text, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- Configuration +CREATE TABLE IF NOT EXISTS `configuration_data` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `systemName` varchar(255) NOT NULL UNIQUE, + `fileName` varchar(255) NOT NULL, + `contentType` varchar(255) NOT NULL, + `data` blob NOT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; + +-- QoS Monitor +-- Intra + +CREATE TABLE IF NOT EXISTS `qos_intra_measurement` ( + `id` bigint(20) PRIMARY KEY AUTO_INCREMENT, + `system_id` bigint(20) NOT NULL, + `measurement_type` varchar(255) NOT NULL, + `last_measurement_at` timestamp NOT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + CONSTRAINT `fk_system` FOREIGN KEY (`system_id`) REFERENCES `system_` (`id`) ON DELETE CASCADE, + UNIQUE KEY `unique_system_id_measurement_type` (`system_id`, `measurement_type`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `qos_intra_ping_measurement` ( + `id` bigint(20) PRIMARY KEY AUTO_INCREMENT, + `measurement_id` bigint(20) NOT NULL, + `available` int(1) NOT NULL DEFAULT 0, + `last_access_at` timestamp NULL DEFAULT NULL, + `min_response_time` int(11) DEFAULT NULL, + `max_response_time` int(11) DEFAULT NULL, + `mean_response_time_with_timeout` int(11) NULL DEFAULT NULL, + `mean_response_time_without_timeout` int(11) NULL DEFAULT NULL, + `jitter_with_timeout` int(11) NULL DEFAULT NULL, + `jitter_without_timeout` int(11) NULL DEFAULT NULL, + `lost_per_measurement_percent` int(3) NOT NULL DEFAULT 0, + `sent` bigint(20) NOT NULL DEFAULT 0, + `received` bigint(20) NOT NULL DEFAULT 0, + `count_started_at` timestamp NULL, + `sent_all` bigint(20) NOT NULL DEFAULT 0, + `received_all` bigint(20) NOT NULL DEFAULT 0, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + CONSTRAINT `fk_intra_measurement` FOREIGN KEY (`measurement_id`) REFERENCES `qos_intra_measurement` (`id`) ON DELETE CASCADE, + UNIQUE KEY `unique_intra_measurement` (`measurement_id`) + +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `qos_intra_ping_measurement_log` ( + `id` bigint(20) PRIMARY KEY AUTO_INCREMENT, + `measured_system_address` varchar(255) NOT NULL, + `available` int(1) NOT NULL DEFAULT 0, + `min_response_time` int(11) DEFAULT NULL, + `max_response_time` int(11) DEFAULT NULL, + `mean_response_time_with_timeout` int(11) NULL DEFAULT NULL, + `mean_response_time_without_timeout` int(11) NULL DEFAULT NULL, + `jitter_with_timeout` int(11) NULL DEFAULT NULL, + `jitter_without_timeout` int(11) NULL DEFAULT NULL, + `lost_per_measurement_percent` int(3) NOT NULL DEFAULT 0, + `sent` bigint(20) NOT NULL DEFAULT 0, + `received` bigint(20) NOT NULL DEFAULT 0, + `measured_at` timestamp NOT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP + +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `qos_intra_ping_measurement_log_details` ( + `id` bigint(20) PRIMARY KEY AUTO_INCREMENT, + `measurement_log_id` bigint(20) NOT NULL, + `measurement_sequenece_number` int(3) NOT NULL, + `success_flag` int(1) NOT NULL DEFAULT 0, + `timeout_flag` int(1) NOT NULL DEFAULT 0, + `error_message` varchar(255) NULL DEFAULT NULL, + `throwable` varchar(255) NULL DEFAULT NULL, + `size_` int(11) NULL DEFAULT NULL, + `rtt` int(11) NULL DEFAULT NULL, + `ttl` int(3) NULL DEFAULT NULL, + `duration` int(5) NULL DEFAULT NULL, + `measured_at` timestamp NOT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + CONSTRAINT `fk_intra_measurement_log` FOREIGN KEY (`measurement_log_id`) REFERENCES `qos_intra_ping_measurement_log` (`id`) ON DELETE CASCADE + +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- QoS Monitor +-- Inter + +CREATE TABLE IF NOT EXISTS `qos_inter_direct_measurement` ( + `id` bigint(20) PRIMARY KEY AUTO_INCREMENT, + `cloud_id` bigint(20) NOT NULL, + `address` varchar(255) NOT NULL, + `measurement_type` varchar(255) NOT NULL, + `last_measurement_at` timestamp NOT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + CONSTRAINT `fk_cloud_inter_direct` FOREIGN KEY (`cloud_id`) REFERENCES `cloud` (`id`) ON DELETE CASCADE, + UNIQUE KEY `unique_cloud_id_address_measurement_type` (`cloud_id`, `address`, `measurement_type`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `qos_inter_direct_ping_measurement` ( + `id` bigint(20) PRIMARY KEY AUTO_INCREMENT, + `measurement_id` bigint(20) NOT NULL, + `available` int(1) NOT NULL DEFAULT 0, + `last_access_at` timestamp NULL DEFAULT NULL, + `min_response_time` int(11) DEFAULT NULL, + `max_response_time` int(11) DEFAULT NULL, + `mean_response_time_with_timeout` int(11) NULL DEFAULT NULL, + `mean_response_time_without_timeout` int(11) NULL DEFAULT NULL, + `jitter_with_timeout` int(11) NULL DEFAULT NULL, + `jitter_without_timeout` int(11) NULL DEFAULT NULL, + `lost_per_measurement_percent` int(3) NOT NULL DEFAULT 0, + `sent` bigint(20) NOT NULL DEFAULT 0, + `received` bigint(20) NOT NULL DEFAULT 0, + `count_started_at` timestamp NULL, + `sent_all` bigint(20) NOT NULL DEFAULT 0, + `received_all` bigint(20) NOT NULL DEFAULT 0, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + CONSTRAINT `fk_inter_direct_measurement` FOREIGN KEY (`measurement_id`) REFERENCES `qos_inter_direct_measurement` (`id`) ON DELETE CASCADE, + UNIQUE KEY `unique_measurement` (`measurement_id`) + +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `qos_inter_direct_ping_measurement_log` ( + `id` bigint(20) PRIMARY KEY AUTO_INCREMENT, + `measured_system_address` varchar(255) NOT NULL, + `available` int(1) NOT NULL DEFAULT 0, + `min_response_time` int(11) DEFAULT NULL, + `max_response_time` int(11) DEFAULT NULL, + `mean_response_time_with_timeout` int(11) NULL DEFAULT NULL, + `mean_response_time_without_timeout` int(11) NULL DEFAULT NULL, + `jitter_with_timeout` int(11) NULL DEFAULT NULL, + `jitter_without_timeout` int(11) NULL DEFAULT NULL, + `lost_per_measurement_percent` int(3) NOT NULL DEFAULT 0, + `sent` bigint(20) NOT NULL DEFAULT 0, + `received` bigint(20) NOT NULL DEFAULT 0, + `measured_at` timestamp NOT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP + +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `qos_inter_direct_ping_measurement_log_details` ( + `id` bigint(20) PRIMARY KEY AUTO_INCREMENT, + `measurement_log_id` bigint(20) NOT NULL, + `measurement_sequenece_number` int(3) NOT NULL, + `success_flag` int(1) NOT NULL DEFAULT 0, + `timeout_flag` int(1) NOT NULL DEFAULT 0, + `error_message` varchar(255) NULL DEFAULT NULL, + `throwable` varchar(255) NULL DEFAULT NULL, + `size_` int(11) NULL DEFAULT NULL, + `rtt` int(11) NULL DEFAULT NULL, + `ttl` int(3) NULL DEFAULT NULL, + `duration` int(5) NULL DEFAULT NULL, + `measured_at` timestamp NOT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + CONSTRAINT `fk_inter_direct_ping_measurement_log` FOREIGN KEY (`measurement_log_id`) REFERENCES `qos_inter_direct_ping_measurement_log` (`id`) ON DELETE CASCADE + +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `qos_inter_relay_measurement` ( + `id` bigint(20) PRIMARY KEY AUTO_INCREMENT, + `cloud_id` bigint(20) NOT NULL, + `relay_id` bigint(20) NOT NULL, + `measurement_type` varchar(255) NOT NULL, + `status` varchar(255) NOT NULL, + `last_measurement_at` timestamp NOT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + CONSTRAINT `fk_cloud_inter_relay` FOREIGN KEY (`cloud_id`) REFERENCES `cloud` (`id`) ON DELETE CASCADE, + CONSTRAINT `fk_relay_inter_relay` FOREIGN KEY (`relay_id`) REFERENCES `relay` (`id`) ON DELETE CASCADE, + UNIQUE KEY `unique_cloud_relay_measurement_type` (`cloud_id`, `relay_id`, `measurement_type`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `qos_inter_relay_echo_measurement` ( + `id` bigint(20) PRIMARY KEY AUTO_INCREMENT, + `measurement_id` bigint(20) NOT NULL, + `last_access_at` timestamp NULL DEFAULT NULL, + `min_response_time` int(11) DEFAULT NULL, + `max_response_time` int(11) DEFAULT NULL, + `mean_response_time_with_timeout` int(11) NULL DEFAULT NULL, + `mean_response_time_without_timeout` int(11) NULL DEFAULT NULL, + `jitter_with_timeout` int(11) NULL DEFAULT NULL, + `jitter_without_timeout` int(11) NULL DEFAULT NULL, + `lost_per_measurement_percent` int(3) NOT NULL DEFAULT 0, + `sent` bigint(20) NOT NULL DEFAULT 0, + `received` bigint(20) NOT NULL DEFAULT 0, + `count_started_at` timestamp NULL, + `sent_all` bigint(20) NOT NULL DEFAULT 0, + `received_all` bigint(20) NOT NULL DEFAULT 0, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + CONSTRAINT `fk_inter_relay_measurement` FOREIGN KEY (`measurement_id`) REFERENCES `qos_inter_relay_measurement` (`id`) ON DELETE CASCADE, + UNIQUE KEY `unique_inter_relay_echo_measurement` (`measurement_id`) + +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `qos_inter_relay_echo_measurement_log` ( + `id` bigint(20) PRIMARY KEY AUTO_INCREMENT, + `measurement_id` bigint(20) NOT NULL, + `measurement_sequenece_number` int(3) NOT NULL, + `timeout_flag` int(1) NOT NULL DEFAULT 0, + `error_message` varchar(255) NULL DEFAULT NULL, + `throwable` varchar(255) NULL DEFAULT NULL, + `size_` int(11) NULL DEFAULT NULL, + `duration` int(5) NULL DEFAULT NULL, + `measured_at` timestamp NOT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + CONSTRAINT `fk_inter_relay_echo_measurement_log` FOREIGN KEY (`measurement_id`) REFERENCES `qos_inter_relay_measurement` (`id`) ON DELETE CASCADE + +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- QoS Manager + +CREATE TABLE IF NOT EXISTS `qos_reservation` ( + `id` bigint(20) PRIMARY KEY AUTO_INCREMENT, + `reserved_provider_id` bigint(20) NOT NULL, + `reserved_service_id` bigint(20) NOT NULL, + `consumer_system_name` varchar(255) NOT NULL, + `consumer_address` varchar(255) NOT NULL, + `consumer_port` int(11) NOT NULL, + `reserved_to` timestamp NOT NULL, + `temporary_lock` int(1) NOT NULL DEFAULT 0, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + CONSTRAINT `fk_reserved_provider` FOREIGN KEY (`reserved_provider_id`) REFERENCES `system_` (`id`) ON DELETE CASCADE, + CONSTRAINT `fk_reserved_service` FOREIGN KEY (`reserved_service_id`) REFERENCES `service_definition` (`id`) ON DELETE CASCADE, + UNIQUE KEY `unique_reserved_provider_and_service` (`reserved_provider_id`, `reserved_service_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- Certificate Authority + +CREATE TABLE IF NOT EXISTS `ca_certificate` ( + `id` bigint(20) PRIMARY KEY AUTO_INCREMENT, + `common_name` varchar(255) NOT NULL, + `serial` bigint(20) NOT NULL, + `created_by` varchar(255) NOT NULL, + `valid_after` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `valid_before` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `revoked_at` timestamp NULL, + UNIQUE KEY `unique_certificate_serial` (`serial`) +) ENGINE = InnoDB DEFAULT CHARSET = utf8; + +CREATE TABLE IF NOT EXISTS `ca_trusted_key` ( + `id` bigint(20) PRIMARY KEY AUTO_INCREMENT, + `public_key` text NOT NULL, + `hash` varchar(255) NOT NULL, + `description` varchar(255) NOT NULL, + `valid_after` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `valid_before` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + UNIQUE KEY `unique_hash` (`hash`) +) ENGINE = InnoDB DEFAULT CHARSET = utf8; + +-- Plant Description Engine + +CREATE TABLE IF NOT EXISTS `pde_rule` ( + `id` bigint(20) PRIMARY KEY, + `plant_description_id` bigint(20) NOT NULL +) ENGINE = InnoDB DEFAULT CHARSET = utf8; + +CREATE TABLE IF NOT EXISTS `plant_description` ( + `id` bigint(20) PRIMARY KEY, + `plant_description` mediumtext NOT NULL +) ENGINE = InnoDB DEFAULT CHARSET = utf8; diff --git a/kubernetes/arrowhead-helm/static/db-init/create_empty_arrowhead_db.sql b/kubernetes/arrowhead-helm/static/db-init/create_empty_arrowhead_db.sql new file mode 100644 index 000000000..fae500eba --- /dev/null +++ b/kubernetes/arrowhead-helm/static/db-init/create_empty_arrowhead_db.sql @@ -0,0 +1,93 @@ +DROP DATABASE IF EXISTS `arrowhead`; +CREATE DATABASE `arrowhead`; +USE `arrowhead`; + +-- create tables +source docker-entrypoint-initdb.d/privileges/create_arrowhead_tables.sql + +-- Set up privileges + +-- Service Registry +CREATE USER IF NOT EXISTS 'service_registry'@'localhost' IDENTIFIED BY 'ZzNNpxrbZGVvfJ8'; +CREATE USER IF NOT EXISTS 'service_registry'@'%' IDENTIFIED BY 'ZzNNpxrbZGVvfJ8'; +source docker-entrypoint-initdb.d/privileges/service_registry_privileges.sql + +-- System Registry +CREATE USER IF NOT EXISTS 'system_registry'@'localhost' IDENTIFIED BY 'Kh12Hhgaxzo7haf'; +CREATE USER IF NOT EXISTS 'system_registry'@'%' IDENTIFIED BY 'Kh12Hhgaxzo7haf'; +source docker-entrypoint-initdb.d/privileges/system_registry_privileges.sql + +-- Device Registry +CREATE USER IF NOT EXISTS 'device_registry'@'localhost' IDENTIFIED BY 'iooHU87hNGUalht'; +CREATE USER IF NOT EXISTS 'device_registry'@'%' IDENTIFIED BY 'iooHU87hNGUalht'; +source docker-entrypoint-initdb.d/privileges/device_registry_privileges.sql + +-- Onboarding controller +CREATE USER IF NOT EXISTS 'onboarding_controller'@'localhost' IDENTIFIED BY 'JKgh1as5f6oi7aV'; +CREATE USER IF NOT EXISTS 'onboarding_controller'@'%' IDENTIFIED BY 'JKgh1as5f6oi7aV'; +source docker-entrypoint-initdb.d/privileges/onboarding_controller_privileges.sql + +-- Authorization +CREATE USER IF NOT EXISTS 'authorization'@'localhost' IDENTIFIED BY 'hqZFUkuHxhekio3'; +CREATE USER IF NOT EXISTS 'authorization'@'%' IDENTIFIED BY 'hqZFUkuHxhekio3'; +source docker-entrypoint-initdb.d/privileges/authorization_privileges.sql + +-- Orchestrator +CREATE USER IF NOT EXISTS 'orchestrator'@'localhost' IDENTIFIED BY 'KbgD2mTr8DQ4vtc'; +CREATE USER IF NOT EXISTS 'orchestrator'@'%' IDENTIFIED BY 'KbgD2mTr8DQ4vtc'; +source docker-entrypoint-initdb.d/privileges/orchestrator_privileges.sql + +-- Event Handler +CREATE USER IF NOT EXISTS 'event_handler'@'localhost' IDENTIFIED BY 'gRLjXbqu9YwYhfK'; +CREATE USER IF NOT EXISTS 'event_handler'@'%' IDENTIFIED BY 'gRLjXbqu9YwYhfK'; +source docker-entrypoint-initdb.d/privileges/event_handler_privileges.sql + +-- DataManager +CREATE USER IF NOT EXISTS 'datamanager'@'localhost' IDENTIFIED BY 'gRLjXbqu0YwYhfK'; +CREATE USER IF NOT EXISTS 'datamanager'@'%' IDENTIFIED BY 'gRLjXbqu0YwYhfK'; +source docker-entrypoint-initdb.d/privileges/datamanager_privileges.sql + +-- TimeManager +CREATE USER IF NOT EXISTS 'timemanager'@'localhost' IDENTIFIED BY 'xyp2XEAu5Tbc41g'; +CREATE USER IF NOT EXISTS 'timemanager'@'%' IDENTIFIED BY 'xyp2XEAu5Tbc41g'; +source docker-entrypoint-initdb.d/privileges/timemanager_privileges.sql + +-- Choreographer +CREATE USER IF NOT EXISTS 'choreographer'@'localhost' IDENTIFIED BY 'Qa5yx4oBp4Y9RLX'; +CREATE USER IF NOT EXISTS 'choreographer'@'%' IDENTIFIED BY 'Qa5yx4oBp4Y9RLX'; +source docker-entrypoint-initdb.d/privileges/choreographer_privileges.sql + +-- Configuration +CREATE USER IF NOT EXISTS 'configuration'@'localhost' IDENTIFIED BY 'yRLjX2qA0YwYhzU'; +CREATE USER IF NOT EXISTS 'configuration'@'%' IDENTIFIED BY 'yRLjX2qA0YwYhzU'; +source docker-entrypoint-initdb.d/privileges/configuration_privileges.sql + +-- Gatekeeper +CREATE USER IF NOT EXISTS 'gatekeeper'@'localhost' IDENTIFIED BY 'fbJKYzKhU5t8QtT'; +CREATE USER IF NOT EXISTS 'gatekeeper'@'%' IDENTIFIED BY 'fbJKYzKhU5t8QtT'; +source docker-entrypoint-initdb.d/privileges/gatekeeper_privileges.sql + +-- Gateway +CREATE USER IF NOT EXISTS 'gateway'@'localhost' IDENTIFIED BY 'LfiSM9DpGfDEP5g'; +CREATE USER IF NOT EXISTS 'gateway'@'%' IDENTIFIED BY 'LfiSM9DpGfDEP5g'; +source docker-entrypoint-initdb.d/privileges/gateway_privileges.sql + +-- Certificate Authority +CREATE USER IF NOT EXISTS 'certificate_authority'@'localhost' IDENTIFIED BY 'FsdG6Kgf9QpPfv2'; +CREATE USER IF NOT EXISTS 'certificate_authority'@'%' IDENTIFIED BY 'FsdG6Kgf9QpPfv2'; +source docker-entrypoint-initdb.d/privileges/certificate_authority_privileges.sql + +-- QoS Monitor +CREATE USER IF NOT EXISTS 'qos_monitor'@'localhost' IDENTIFIED BY 'RLY3UEx6nx4kSXy'; +CREATE USER IF NOT EXISTS 'qos_monitor'@'%' IDENTIFIED BY 'RLY3UEx6nx4kSXy'; +source docker-entrypoint-initdb.d/privileges/qos_monitor_privileges.sql + +-- Translator +CREATE USER IF NOT EXISTS 'translator'@'localhost' IDENTIFIED BY 'wozYpV58G0HUkbL'; +CREATE USER IF NOT EXISTS 'translator'@'%' IDENTIFIED BY 'wozYpV58G0HUkbL'; +source docker-entrypoint-initdb.d/privileges/translator_privileges.sql + +-- Plant Description Engine +CREATE USER IF NOT EXISTS 'plant_description_engine'@'localhost' IDENTIFIED BY 'ivJ2y9qWCpTmzr0'; +CREATE USER IF NOT EXISTS 'plant_description_engine'@'%' IDENTIFIED BY 'ivJ2y9qWCpTmzr0'; +source docker-entrypoint-initdb.d/privileges/plant_description_engine_privileges.sql \ No newline at end of file diff --git a/kubernetes/arrowhead-helm/static/db-init/datamanager_privileges.sql b/kubernetes/arrowhead-helm/static/db-init/datamanager_privileges.sql new file mode 100644 index 000000000..e35de51f8 --- /dev/null +++ b/kubernetes/arrowhead-helm/static/db-init/datamanager_privileges.sql @@ -0,0 +1,17 @@ +USE `arrowhead`; + +REVOKE ALL, GRANT OPTION FROM 'datamanager'@'localhost'; + +GRANT ALL PRIVILEGES ON `arrowhead`.`dmhist_services` TO 'datamanager'@'localhost'; +#GRANT ALL PRIVILEGES ON `arrowhead`.`dmhist_files` TO 'datamanager'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`dmhist_messages` TO 'datamanager'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`dmhist_entries` TO 'datamanager'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`logs` TO 'datamanager'@'localhost'; + +GRANT ALL PRIVILEGES ON `arrowhead`.`dmhist_services` TO 'datamanager'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`dmhist_files` TO 'datamanager'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`dmhist_messages` TO 'datamanager'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`dmhist_entries` TO 'datamanager'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`logs` TO 'datamanager'@'%'; + +FLUSH PRIVILEGES; diff --git a/kubernetes/arrowhead-helm/static/db-init/device_registry_privileges.sql b/kubernetes/arrowhead-helm/static/db-init/device_registry_privileges.sql new file mode 100644 index 000000000..eaa1e60ce --- /dev/null +++ b/kubernetes/arrowhead-helm/static/db-init/device_registry_privileges.sql @@ -0,0 +1,33 @@ +USE `arrowhead`; + +REVOKE ALL, GRANT OPTION FROM 'device_registry'@'localhost'; + +GRANT ALL PRIVILEGES ON `arrowhead`.`device` TO 'device_registry'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`device_registry` TO 'device_registry'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`system_` TO 'device_registry'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`service_definition` TO 'device_registry'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`service_interface` TO 'device_registry'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`service_registry` TO 'device_registry'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`service_registry_interface_connection` TO 'device_registry'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_intra_cloud` TO 'device_registry'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_intra_cloud_interface_connection` TO 'device_registry'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_inter_cloud` TO 'device_registry'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_inter_cloud_interface_connection` TO 'device_registry'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`logs` TO 'device_registry'@'localhost'; + +REVOKE ALL, GRANT OPTION FROM 'device_registry'@'%'; + +GRANT ALL PRIVILEGES ON `arrowhead`.`device` TO 'device_registry'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`device_registry` TO 'device_registry'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`system_` TO 'device_registry'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`service_definition` TO 'device_registry'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`service_interface` TO 'device_registry'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`service_registry` TO 'device_registry'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`service_registry_interface_connection` TO 'device_registry'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_intra_cloud` TO 'device_registry'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_intra_cloud_interface_connection` TO 'device_registry'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_inter_cloud` TO 'device_registry'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_inter_cloud_interface_connection` TO 'device_registry'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`logs` TO 'device_registry'@'%'; + +FLUSH PRIVILEGES; \ No newline at end of file diff --git a/kubernetes/arrowhead-helm/static/db-init/event_handler_privileges.sql b/kubernetes/arrowhead-helm/static/db-init/event_handler_privileges.sql new file mode 100644 index 000000000..59183f487 --- /dev/null +++ b/kubernetes/arrowhead-helm/static/db-init/event_handler_privileges.sql @@ -0,0 +1,19 @@ +USE `arrowhead`; + +REVOKE ALL, GRANT OPTION FROM 'event_handler'@'localhost'; + +GRANT ALL PRIVILEGES ON `arrowhead`.`event_type` TO 'event_handler'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`subscription` TO 'event_handler'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`subscription_publisher_connection` TO 'event_handler'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`system_` TO 'event_handler'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`logs` TO 'event_handler'@'localhost'; + +REVOKE ALL, GRANT OPTION FROM 'event_handler'@'%'; + +GRANT ALL PRIVILEGES ON `arrowhead`.`event_type` TO 'event_handler'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`subscription` TO 'event_handler'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`subscription_publisher_connection` TO 'event_handler'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`system_` TO 'event_handler'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`logs` TO 'event_handler'@'%'; + +FLUSH PRIVILEGES; \ No newline at end of file diff --git a/kubernetes/arrowhead-helm/static/db-init/gatekeeper_privileges.sql b/kubernetes/arrowhead-helm/static/db-init/gatekeeper_privileges.sql new file mode 100644 index 000000000..944fe7cb1 --- /dev/null +++ b/kubernetes/arrowhead-helm/static/db-init/gatekeeper_privileges.sql @@ -0,0 +1,29 @@ +USE `arrowhead`; + +REVOKE ALL, GRANT OPTION FROM 'gatekeeper'@'localhost'; + +GRANT ALL PRIVILEGES ON `arrowhead`.`cloud` TO 'gatekeeper'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`relay` TO 'gatekeeper'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`system_` TO 'gatekeeper'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`service_definition` TO 'gatekeeper'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`cloud_gatekeeper_relay` TO 'gatekeeper'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`cloud_gateway_relay` TO 'gatekeeper'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_inter_cloud` TO 'gatekeeper'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_inter_cloud_interface_connection` TO 'gatekeeper'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`foreign_system` TO 'gatekeeper'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`logs` TO 'gatekeeper'@'localhost'; + +REVOKE ALL, GRANT OPTION FROM 'gatekeeper'@'%'; + +GRANT ALL PRIVILEGES ON `arrowhead`.`cloud` TO 'gatekeeper'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`relay` TO 'gatekeeper'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`system_` TO 'gatekeeper'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`service_definition` TO 'gatekeeper'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`cloud_gatekeeper_relay` TO 'gatekeeper'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`cloud_gateway_relay` TO 'gatekeeper'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_inter_cloud` TO 'gatekeeper'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_inter_cloud_interface_connection` TO 'gatekeeper'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`foreign_system` TO 'gatekeeper'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`logs` TO 'gatekeeper'@'%'; + +FLUSH PRIVILEGES; \ No newline at end of file diff --git a/kubernetes/arrowhead-helm/static/db-init/gateway_privileges.sql b/kubernetes/arrowhead-helm/static/db-init/gateway_privileges.sql new file mode 100644 index 000000000..0158ed405 --- /dev/null +++ b/kubernetes/arrowhead-helm/static/db-init/gateway_privileges.sql @@ -0,0 +1,9 @@ +USE `arrowhead`; + +REVOKE ALL, GRANT OPTION FROM 'gateway'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`logs` TO 'gateway'@'localhost'; + +REVOKE ALL, GRANT OPTION FROM 'gateway'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`logs` TO 'gateway'@'%'; + +FLUSH PRIVILEGES; diff --git a/kubernetes/arrowhead-helm/static/db-init/onboarding_controller_privileges.sql b/kubernetes/arrowhead-helm/static/db-init/onboarding_controller_privileges.sql new file mode 100644 index 000000000..b619e2097 --- /dev/null +++ b/kubernetes/arrowhead-helm/static/db-init/onboarding_controller_privileges.sql @@ -0,0 +1,11 @@ +USE `arrowhead`; + +REVOKE ALL, GRANT OPTION FROM 'onboarding_controller'@'localhost'; + +GRANT ALL PRIVILEGES ON `arrowhead`.`logs` TO 'onboarding_controller'@'localhost'; + +REVOKE ALL, GRANT OPTION FROM 'onboarding_controller'@'%'; + +GRANT ALL PRIVILEGES ON `arrowhead`.`logs` TO 'onboarding_controller'@'%'; + +FLUSH PRIVILEGES; \ No newline at end of file diff --git a/kubernetes/arrowhead-helm/static/db-init/orchestrator_privileges.sql b/kubernetes/arrowhead-helm/static/db-init/orchestrator_privileges.sql new file mode 100644 index 000000000..6f8563117 --- /dev/null +++ b/kubernetes/arrowhead-helm/static/db-init/orchestrator_privileges.sql @@ -0,0 +1,33 @@ +USE `arrowhead`; + +REVOKE ALL, GRANT OPTION FROM 'orchestrator'@'localhost'; + +GRANT ALL PRIVILEGES ON `arrowhead`.`orchestrator_store` TO 'orchestrator'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`foreign_system` TO 'orchestrator'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`orchestrator_store_flexible` TO 'orchestrator'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`system_` TO 'orchestrator'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`cloud` TO 'orchestrator'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`relay` TO 'orchestrator'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`cloud_gatekeeper_relay` TO 'orchestrator'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`cloud_gateway_relay` TO 'orchestrator'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`service_definition` TO 'orchestrator'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`service_interface` TO 'orchestrator'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`qos_reservation` TO 'orchestrator'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`logs` TO 'orchestrator'@'localhost'; + +REVOKE ALL, GRANT OPTION FROM 'orchestrator'@'%'; + +GRANT ALL PRIVILEGES ON `arrowhead`.`orchestrator_store` TO 'orchestrator'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`foreign_system` TO 'orchestrator'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`orchestrator_store_flexible` TO 'orchestrator'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`system_` TO 'orchestrator'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`cloud` TO 'orchestrator'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`relay` TO 'orchestrator'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`cloud_gatekeeper_relay` TO 'orchestrator'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`cloud_gateway_relay` TO 'orchestrator'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`service_definition` TO 'orchestrator'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`service_interface` TO 'orchestrator'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`qos_reservation` TO 'orchestrator'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`logs` TO 'orchestrator'@'%'; + +FLUSH PRIVILEGES; \ No newline at end of file diff --git a/kubernetes/arrowhead-helm/static/db-init/plant_description_engine_privileges.sql b/kubernetes/arrowhead-helm/static/db-init/plant_description_engine_privileges.sql new file mode 100644 index 000000000..bbf87d38c --- /dev/null +++ b/kubernetes/arrowhead-helm/static/db-init/plant_description_engine_privileges.sql @@ -0,0 +1,15 @@ +USE `arrowhead`; + +REVOKE ALL, GRANT OPTION FROM 'plant_description_engine'@'localhost'; + +GRANT ALL PRIVILEGES ON `arrowhead`.`pde_rule` TO 'plant_description_engine'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`plant_description` TO 'plant_description_engine'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`logs` TO 'plant_description_engine'@'localhost'; + +REVOKE ALL, GRANT OPTION FROM 'plant_description_engine'@'%'; + +GRANT ALL PRIVILEGES ON `arrowhead`.`pde_rule` TO 'plant_description_engine'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`plant_description` TO 'plant_description_engine'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`logs` TO 'plant_description_engine'@'%'; + +FLUSH PRIVILEGES; diff --git a/kubernetes/arrowhead-helm/static/db-init/qos_monitor_privileges.sql b/kubernetes/arrowhead-helm/static/db-init/qos_monitor_privileges.sql new file mode 100644 index 000000000..c8a9d1d5f --- /dev/null +++ b/kubernetes/arrowhead-helm/static/db-init/qos_monitor_privileges.sql @@ -0,0 +1,47 @@ +USE `arrowhead`; + +REVOKE ALL, GRANT OPTION FROM 'qos_monitor'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`qos_intra_measurement` TO 'qos_monitor'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`qos_intra_ping_measurement` TO 'qos_monitor'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`qos_intra_ping_measurement_log` TO 'qos_monitor'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`qos_intra_ping_measurement_log_details` TO 'qos_monitor'@'localhost'; + +GRANT ALL PRIVILEGES ON `arrowhead`.`qos_inter_direct_measurement` TO 'qos_monitor'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`qos_inter_direct_ping_measurement` TO 'qos_monitor'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`qos_inter_direct_ping_measurement_log` TO 'qos_monitor'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`qos_inter_direct_ping_measurement_log_details` TO 'qos_monitor'@'localhost'; + +GRANT ALL PRIVILEGES ON `arrowhead`.`qos_inter_relay_measurement` TO 'qos_monitor'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`qos_inter_relay_echo_measurement` TO 'qos_monitor'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`qos_inter_relay_echo_measurement_log` TO 'qos_monitor'@'localhost'; + +GRANT SELECT ON `arrowhead`.`system_` TO 'qos_monitor'@'localhost'; +GRANT SELECT ON `arrowhead`.`cloud` TO 'qos_monitor'@'localhost'; +GRANT SELECT ON `arrowhead`.`relay` TO 'qos_monitor'@'localhost'; +GRANT SELECT ON `arrowhead`.`cloud_gatekeeper_relay` TO 'qos_monitor'@'localhost'; +GRANT SELECT ON `arrowhead`.`cloud_gateway_relay` TO 'qos_monitor'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`logs` TO 'qos_monitor'@'localhost'; + +REVOKE ALL, GRANT OPTION FROM 'qos_monitor'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`qos_intra_measurement` TO 'qos_monitor'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`qos_intra_ping_measurement` TO 'qos_monitor'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`qos_intra_ping_measurement_log` TO 'qos_monitor'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`qos_intra_ping_measurement_log_details` TO 'qos_monitor'@'%'; + +GRANT ALL PRIVILEGES ON `arrowhead`.`qos_inter_direct_measurement` TO 'qos_monitor'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`qos_inter_direct_ping_measurement` TO 'qos_monitor'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`qos_inter_direct_ping_measurement_log` TO 'qos_monitor'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`qos_inter_direct_ping_measurement_log_details` TO 'qos_monitor'@'%'; + +GRANT ALL PRIVILEGES ON `arrowhead`.`qos_inter_relay_measurement` TO 'qos_monitor'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`qos_inter_relay_echo_measurement` TO 'qos_monitor'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`qos_inter_relay_echo_measurement_log` TO 'qos_monitor'@'%'; + +GRANT SELECT ON `arrowhead`.`system_` TO 'qos_monitor'@'%'; +GRANT SELECT ON `arrowhead`.`cloud` TO 'qos_monitor'@'%'; +GRANT SELECT ON `arrowhead`.`relay` TO 'qos_monitor'@'%'; +GRANT SELECT ON `arrowhead`.`cloud_gatekeeper_relay` TO 'qos_monitor'@'%'; +GRANT SELECT ON `arrowhead`.`cloud_gateway_relay` TO 'qos_monitor'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`logs` TO 'qos_monitor'@'%'; + +FLUSH PRIVILEGES; \ No newline at end of file diff --git a/kubernetes/arrowhead-helm/static/db-init/service_registry_privileges.sql b/kubernetes/arrowhead-helm/static/db-init/service_registry_privileges.sql new file mode 100644 index 000000000..072ae3572 --- /dev/null +++ b/kubernetes/arrowhead-helm/static/db-init/service_registry_privileges.sql @@ -0,0 +1,57 @@ +USE `arrowhead`; + +REVOKE ALL, GRANT OPTION FROM 'service_registry'@'localhost'; + +GRANT ALL PRIVILEGES ON `arrowhead`.`system_` TO 'service_registry'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`service_definition` TO 'service_registry'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`service_interface` TO 'service_registry'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`service_registry` TO 'service_registry'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`service_registry_interface_connection` TO 'service_registry'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`cloud` TO 'service_registry'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`relay` TO 'service_registry'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`cloud_gatekeeper_relay` TO 'service_registry'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`cloud_gateway_relay` TO 'service_registry'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_intra_cloud` TO 'service_registry'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_intra_cloud_interface_connection` TO 'service_registry'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_inter_cloud` TO 'service_registry'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_inter_cloud_interface_connection` TO 'service_registry'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`orchestrator_store` TO 'service_registry'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`subscription` TO 'service_registry'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`subscription_publisher_connection` TO 'service_registry'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`choreographer_action_plan` TO 'service_registry'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`choreographer_action` TO 'service_registry'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`choreographer_action_step` TO 'service_registry'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`choreographer_action_plan_action_connection` TO 'service_registry'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`choreographer_action_action_step_connection` TO 'service_registry'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`choreographer_action_step_service_definition_connection` TO 'service_registry'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`choreographer_next_action_step` TO 'service_registry'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`logs` TO 'service_registry'@'localhost'; + +REVOKE ALL, GRANT OPTION FROM 'service_registry'@'%'; + +GRANT ALL PRIVILEGES ON `arrowhead`.`system_` TO 'service_registry'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`service_definition` TO 'service_registry'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`service_interface` TO 'service_registry'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`service_registry` TO 'service_registry'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`service_registry_interface_connection` TO 'service_registry'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`cloud` TO 'service_registry'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`relay` TO 'service_registry'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`cloud_gatekeeper_relay` TO 'service_registry'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`cloud_gateway_relay` TO 'service_registry'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_intra_cloud` TO 'service_registry'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_intra_cloud_interface_connection` TO 'service_registry'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_inter_cloud` TO 'service_registry'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_inter_cloud_interface_connection` TO 'service_registry'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`orchestrator_store` TO 'service_registry'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`subscription` TO 'service_registry'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`subscription_publisher_connection` TO 'service_registry'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`choreographer_action_plan` TO 'service_registry'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`choreographer_action` TO 'service_registry'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`choreographer_action_step` TO 'service_registry'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`choreographer_action_plan_action_connection` TO 'service_registry'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`choreographer_action_action_step_connection` TO 'service_registry'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`choreographer_action_step_service_definition_connection` TO 'service_registry'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`choreographer_next_action_step` TO 'service_registry'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`logs` TO 'service_registry'@'%'; + +FLUSH PRIVILEGES; \ No newline at end of file diff --git a/kubernetes/arrowhead-helm/static/db-init/system_registry_privileges.sql b/kubernetes/arrowhead-helm/static/db-init/system_registry_privileges.sql new file mode 100644 index 000000000..37ea8a6e0 --- /dev/null +++ b/kubernetes/arrowhead-helm/static/db-init/system_registry_privileges.sql @@ -0,0 +1,33 @@ +USE `arrowhead`; + +REVOKE ALL, GRANT OPTION FROM 'system_registry'@'localhost'; + +GRANT ALL PRIVILEGES ON `arrowhead`.`device` TO 'system_registry'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`system_` TO 'system_registry'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`system_registry` TO 'system_registry'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`service_definition` TO 'system_registry'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`service_interface` TO 'system_registry'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`service_registry` TO 'system_registry'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`service_registry_interface_connection` TO 'system_registry'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_intra_cloud` TO 'system_registry'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_intra_cloud_interface_connection` TO 'system_registry'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_inter_cloud` TO 'system_registry'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_inter_cloud_interface_connection` TO 'system_registry'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`logs` TO 'system_registry'@'localhost'; + +REVOKE ALL, GRANT OPTION FROM 'system_registry'@'%'; + +GRANT ALL PRIVILEGES ON `arrowhead`.`device` TO 'system_registry'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`system_` TO 'system_registry'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`system_registry` TO 'system_registry'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`service_definition` TO 'system_registry'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`service_interface` TO 'system_registry'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`service_registry` TO 'system_registry'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`service_registry_interface_connection` TO 'system_registry'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_intra_cloud` TO 'system_registry'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_intra_cloud_interface_connection` TO 'system_registry'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_inter_cloud` TO 'system_registry'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_inter_cloud_interface_connection` TO 'system_registry'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`logs` TO 'system_registry'@'%'; + +FLUSH PRIVILEGES; \ No newline at end of file diff --git a/kubernetes/arrowhead-helm/static/db-init/timemanager_privileges.sql b/kubernetes/arrowhead-helm/static/db-init/timemanager_privileges.sql new file mode 100644 index 000000000..aadf90563 --- /dev/null +++ b/kubernetes/arrowhead-helm/static/db-init/timemanager_privileges.sql @@ -0,0 +1,9 @@ +USE `arrowhead`; + +REVOKE ALL, GRANT OPTION FROM 'timemanager'@'localhost'; + +GRANT ALL PRIVILEGES ON `arrowhead`.`logs` TO 'timemanager'@'localhost'; + +GRANT ALL PRIVILEGES ON `arrowhead`.`logs` TO 'timemanager'@'%'; + +FLUSH PRIVILEGES; diff --git a/kubernetes/arrowhead-helm/static/db-init/translator_privileges.sql b/kubernetes/arrowhead-helm/static/db-init/translator_privileges.sql new file mode 100644 index 000000000..1245851d1 --- /dev/null +++ b/kubernetes/arrowhead-helm/static/db-init/translator_privileges.sql @@ -0,0 +1,9 @@ +USE `arrowhead`; + +REVOKE ALL, GRANT OPTION FROM 'translator'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`logs` TO 'translator'@'localhost'; + +REVOKE ALL, GRANT OPTION FROM 'translator'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`logs` TO 'translator'@'%'; + +FLUSH PRIVILEGES; \ No newline at end of file diff --git a/kubernetes/arrowhead-helm/templates/_helpers.tpl b/kubernetes/arrowhead-helm/templates/_helpers.tpl new file mode 100644 index 000000000..dfd8f5c6a --- /dev/null +++ b/kubernetes/arrowhead-helm/templates/_helpers.tpl @@ -0,0 +1,47 @@ +{{- define "loadDBConnectionDetails" -}} +spring: + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + password: {{ .component.datasourcePassword }} + username: {{ .component.datasourceUsername }} + url: {{ printf "jdbc:mysql://%s:%v/arrowhead?serverTimezone=Europe/Budapest" .dot.Values.mysql.address .dot.Values.mysql.port | indent 2 }} +{{- end -}} + +{{- define "loadCertificateDetails" -}} +server: + ssl: + key-store-password: {{ .component.keystorePassword }} + trust-store-type: {{ .component.truststoreType }} + key-store: {{ printf "file:%s/keystore/keystore.p12" .mountpath }} + key-password: {{ .component.keyPassword }} + trust-store: {{ printf "file:%s/truststore/truststore.p12" .mountpath }} + key-store-type: {{ .component.keystoreType }} + key-alias: {{ .component.keyAlias }} + client-auth: need + enabled: {{ .component.sslEnabled }} + trust-store-password: {{ .component.truststorePassword }} +{{- end -}} + +{{- define "loadSRDetails" -}} +sr_address: {{ .Values.serviceRegistry.address }} +sr_port: {{ .Values.serviceRegistry.port }} +{{- end -}} + +{{- define "flattenYaml" -}} +{{- $map := first . -}} +{{- $concatenatedKeys := last . -}} +{{- range $key, $value := $map -}} + {{ $addKey := "" }} + {{- if not (eq $concatenatedKeys "") -}} + {{ $addKey = printf "%s.%s" $concatenatedKeys $key}} + {{- else -}} + {{- $addKey = $key -}} + {{- end -}} + {{- if kindOf $value | eq "map" -}} + {{- include "flattenYaml" (list $value $addKey) -}} + {{- else -}} +{{ printf "%s=%v" $addKey $value }} +{{ printf ""}} + {{- end -}} +{{- end -}} +{{- end -}} \ No newline at end of file diff --git a/kubernetes/arrowhead-helm/templates/ambassador/authorization-mapping.yaml b/kubernetes/arrowhead-helm/templates/ambassador/authorization-mapping.yaml new file mode 100644 index 000000000..0a0967f36 --- /dev/null +++ b/kubernetes/arrowhead-helm/templates/ambassador/authorization-mapping.yaml @@ -0,0 +1,7 @@ +apiVersion: getambassador.io/v3alpha1 +kind: TCPMapping +metadata: + name: authorization-mapping +spec: + port: 3001 + service: {{ printf "%s:%v" .Values.authorization.address .Values.authorization.port }} \ No newline at end of file diff --git a/kubernetes/arrowhead-helm/templates/ambassador/orchestrator-mapping.yaml b/kubernetes/arrowhead-helm/templates/ambassador/orchestrator-mapping.yaml new file mode 100644 index 000000000..aa39a19f2 --- /dev/null +++ b/kubernetes/arrowhead-helm/templates/ambassador/orchestrator-mapping.yaml @@ -0,0 +1,7 @@ +apiVersion: getambassador.io/v3alpha1 +kind: TCPMapping +metadata: + name: orchestrator-mapping +spec: + port: 3002 + service: {{ printf "%s:%v" .Values.orchestrator.address .Values.orchestrator.port }} \ No newline at end of file diff --git a/kubernetes/arrowhead-helm/templates/ambassador/service-registry-mapping.yaml b/kubernetes/arrowhead-helm/templates/ambassador/service-registry-mapping.yaml new file mode 100644 index 000000000..30fcce6b1 --- /dev/null +++ b/kubernetes/arrowhead-helm/templates/ambassador/service-registry-mapping.yaml @@ -0,0 +1,7 @@ +apiVersion: getambassador.io/v3alpha1 +kind: TCPMapping +metadata: + name: service-registry-mapping +spec: + port: 3000 + service: {{ printf "%s:%v" .Values.serviceRegistry.address .Values.serviceRegistry.port }} \ No newline at end of file diff --git a/kubernetes/arrowhead-helm/templates/authorization/authorization-deployment.yaml b/kubernetes/arrowhead-helm/templates/authorization/authorization-deployment.yaml new file mode 100644 index 000000000..553a5da6a --- /dev/null +++ b/kubernetes/arrowhead-helm/templates/authorization/authorization-deployment.yaml @@ -0,0 +1,55 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: authorization + name: {{ .Release.Name }}-authorization-deployment +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + app: authorization + strategy: + type: Recreate + template: + metadata: + labels: + app: authorization + spec: + containers: + - image: {{ .Values.authorization.image }} + name: authorization + ports: + - containerPort: {{ .Values.authorization.port }} + resources: {} + volumeMounts: + - name: {{ .Values.authorization.address }}-properties-volume + mountPath: /authorization/application.properties + subPath: application.properties + - name: {{ .Values.authorization.address }}-keystore-volume + mountPath: /authorization/keystore + - name: {{ .Values.authorization.address }}-truststore-volume + mountPath: /authorization/truststore + initContainers: + - image: busybox:1.28 + name: wait-for-db + volumeMounts: + - name: wait-for-db + mountPath: /init + command: + - /bin/sh + - /init/wait-for-db.sh + volumes: + - name: {{ .Values.authorization.address }}-properties-volume + secret: + secretName: {{ .Release.Name }}-authorization-properties + - name: wait-for-db + configMap: + name: {{ .Release.Name }}-wait-for-db + - name: {{ .Values.authorization.address }}-keystore-volume + configMap: + name: {{ .Release.Name }}-authorization-keystore + - name: {{ .Values.authorization.address }}-truststore-volume + configMap: + name: {{ .Release.Name }}-authorization-truststore + restartPolicy: Always \ No newline at end of file diff --git a/kubernetes/arrowhead-helm/templates/authorization/authorization-keystore.yaml b/kubernetes/arrowhead-helm/templates/authorization/authorization-keystore.yaml new file mode 100644 index 000000000..3f15fd391 --- /dev/null +++ b/kubernetes/arrowhead-helm/templates/authorization/authorization-keystore.yaml @@ -0,0 +1,6 @@ +kind: ConfigMap +apiVersion: v1 +metadata: + name: {{ .Release.Name }}-authorization-keystore +binaryData: + keystore.p12: {{ printf "static/certificates/%s" .Values.authorization.keystore | .Files.Get | b64enc }} \ No newline at end of file diff --git a/kubernetes/arrowhead-helm/templates/authorization/authorization-properties.yaml b/kubernetes/arrowhead-helm/templates/authorization/authorization-properties.yaml new file mode 100644 index 000000000..8effddd78 --- /dev/null +++ b/kubernetes/arrowhead-helm/templates/authorization/authorization-properties.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Release.Name }}-authorization-properties +stringData: + application.properties: |- +{{ list (merge .Values.authorization.properties (fromYaml (include "loadDBConnectionDetails" (dict "dot" . "component" .Values.serviceRegistry))) (fromYaml (include "loadCertificateDetails" (dict "component" .Values.authorization "mountpath" "/authorization"))) (fromYaml (include "loadSRDetails" .))) "" | include "flattenYaml" | indent 4 }} \ No newline at end of file diff --git a/kubernetes/arrowhead-helm/templates/authorization/authorization-service.yaml b/kubernetes/arrowhead-helm/templates/authorization/authorization-service.yaml new file mode 100644 index 000000000..fdb45a890 --- /dev/null +++ b/kubernetes/arrowhead-helm/templates/authorization/authorization-service.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.authorization.address }} +spec: + selector: + app: authorization + ports: + - port: {{ .Values.authorization.port }} + targetPort: {{ .Values.authorization.port }} + type: ClusterIP \ No newline at end of file diff --git a/kubernetes/arrowhead-helm/templates/authorization/authorization-truststore.yaml b/kubernetes/arrowhead-helm/templates/authorization/authorization-truststore.yaml new file mode 100644 index 000000000..34e25d86f --- /dev/null +++ b/kubernetes/arrowhead-helm/templates/authorization/authorization-truststore.yaml @@ -0,0 +1,6 @@ +kind: ConfigMap +apiVersion: v1 +metadata: + name: {{ .Release.Name }}-authorization-truststore +binaryData: + truststore.p12: {{ printf "static/certificates/%s" .Values.authorization.truststore | .Files.Get | b64enc }} \ No newline at end of file diff --git a/kubernetes/arrowhead-helm/templates/mysql/mysql-conf.yaml b/kubernetes/arrowhead-helm/templates/mysql/mysql-conf.yaml new file mode 100644 index 000000000..60110b257 --- /dev/null +++ b/kubernetes/arrowhead-helm/templates/mysql/mysql-conf.yaml @@ -0,0 +1,22 @@ +kind: ConfigMap +apiVersion: v1 +metadata: + name: mysql-conf +data: +{{- range $path, $bytes := .Files.Glob "static/db-init/*" }} + {{ base $path }}: |- +{{ $.Files.Get $path | indent 4 }} +{{- end }} + updateUsers.sql: |- +{{ printf "RENAME USER 'service_registry'@'localhost' TO '%s'@'localhost';" .Values.serviceRegistry.datasourceUsername | indent 4 }} +{{ printf "RENAME USER 'service_registry'@'%%' TO '%s'@'%%';" .Values.serviceRegistry.datasourceUsername | indent 4 }} +{{ printf "ALTER USER '%s'@'localhost' IDENTIFIED BY '%s';" .Values.serviceRegistry.datasourceUsername .Values.serviceRegistry.datasourcePassword | indent 4 }} +{{ printf "ALTER USER '%s'@'%%' IDENTIFIED BY '%s';" .Values.serviceRegistry.datasourceUsername .Values.serviceRegistry.datasourcePassword | indent 4 }} +{{ printf "RENAME USER 'authorization'@'localhost' TO '%s'@'localhost';" .Values.authorization.datasourceUsername | indent 4 }} +{{ printf "RENAME USER 'authorization'@'%%' TO '%s'@'%%';" .Values.authorization.datasourceUsername | indent 4 }} +{{ printf "ALTER USER '%s'@'localhost' IDENTIFIED BY '%s';" .Values.authorization.datasourceUsername .Values.authorization.datasourcePassword | indent 4 }} +{{ printf "ALTER USER '%s'@'%%' IDENTIFIED BY '%s';" .Values.authorization.datasourceUsername .Values.authorization.datasourcePassword | indent 4 }} +{{ printf "RENAME USER 'orchestrator'@'localhost' TO '%s'@'localhost';" .Values.orchestrator.datasourceUsername | indent 4 }} +{{ printf "RENAME USER 'orchestrator'@'%%' TO '%s'@'%%';" .Values.orchestrator.datasourceUsername | indent 4 }} +{{ printf "ALTER USER '%s'@'localhost' IDENTIFIED BY '%s';" .Values.orchestrator.datasourceUsername .Values.orchestrator.datasourcePassword | indent 4 }} +{{ printf "ALTER USER '%s'@'%%' IDENTIFIED BY '%s';" .Values.orchestrator.datasourceUsername .Values.orchestrator.datasourcePassword | indent 4 }} \ No newline at end of file diff --git a/kubernetes/arrowhead-helm/templates/mysql/mysql-deployment.yaml b/kubernetes/arrowhead-helm/templates/mysql/mysql-deployment.yaml new file mode 100644 index 000000000..6839ee2f7 --- /dev/null +++ b/kubernetes/arrowhead-helm/templates/mysql/mysql-deployment.yaml @@ -0,0 +1,43 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mysql +spec: + selector: + matchLabels: + app: mysql + strategy: + type: Recreate + template: + metadata: + labels: + app: mysql + spec: + containers: + - image: {{ .Values.mysql.image }} + name: mysql + env: + - name: MYSQL_ROOT_PASSWORD + value: {{ .Values.mysql.rootPassword }} + ports: + - containerPort: 3306 + name: mysql + volumeMounts: + - name: mysql-persistent-storage + mountPath: /var/lib/mysql/ + subPath: data + - name: mysql-conf + mountPath: /docker-entrypoint-initdb.d/privileges + - name: mysql-conf + mountPath: /docker-entrypoint-initdb.d/updateUsers.sql + subPath: updateUsers.sql + - name: mysql-conf + mountPath: /docker-entrypoint-initdb.d/create_empty_arrowhead_db.sql + subPath: create_empty_arrowhead_db.sql + volumes: + - name: mysql-persistent-storage + persistentVolumeClaim: + claimName: mysql-pv-claim + - name: mysql-conf + configMap: + name: mysql-conf \ No newline at end of file diff --git a/kubernetes/arrowhead-helm/templates/mysql/mysql-service.yaml b/kubernetes/arrowhead-helm/templates/mysql/mysql-service.yaml new file mode 100644 index 000000000..c355e3649 --- /dev/null +++ b/kubernetes/arrowhead-helm/templates/mysql/mysql-service.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.mysql.address }} +spec: + ports: + - port: 3306 + selector: + app: mysql \ No newline at end of file diff --git a/kubernetes/arrowhead-helm/templates/mysql/mysql-volume-claim.yaml b/kubernetes/arrowhead-helm/templates/mysql/mysql-volume-claim.yaml new file mode 100644 index 000000000..8d3627463 --- /dev/null +++ b/kubernetes/arrowhead-helm/templates/mysql/mysql-volume-claim.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: mysql-pv-claim +spec: + storageClassName: managed-premium + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi \ No newline at end of file diff --git a/kubernetes/arrowhead-helm/templates/orchestrator/orchestrator-deployment.yaml b/kubernetes/arrowhead-helm/templates/orchestrator/orchestrator-deployment.yaml new file mode 100644 index 000000000..196e56410 --- /dev/null +++ b/kubernetes/arrowhead-helm/templates/orchestrator/orchestrator-deployment.yaml @@ -0,0 +1,55 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: orchestrator + name: {{ .Release.Name }}-orchestrator-deployment +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + app: orchestrator + strategy: + type: Recreate + template: + metadata: + labels: + app: orchestrator + spec: + containers: + - image: {{ .Values.orchestrator.image }} + name: orchestrator + ports: + - containerPort: {{ .Values.orchestrator.port }} + resources: {} + volumeMounts: + - name: {{ .Values.orchestrator.address }}-properties-volume + mountPath: /orchestrator/application.properties + subPath: application.properties + - name: {{ .Values.orchestrator.address }}-keystore-volume + mountPath: /orchestrator/keystore + - name: {{ .Values.orchestrator.address }}-truststore-volume + mountPath: /orchestrator/truststore + initContainers: + - image: busybox:1.28 + name: wait-for-db + volumeMounts: + - name: wait-for-db + mountPath: /init + command: + - /bin/sh + - /init/wait-for-db.sh + volumes: + - name: {{ .Values.orchestrator.address }}-properties-volume + secret: + secretName: {{ .Release.Name }}-orchestrator-properties + - name: wait-for-db + configMap: + name: {{ .Release.Name }}-wait-for-db + - name: {{ .Values.orchestrator.address }}-keystore-volume + configMap: + name: {{ .Release.Name }}-orchestrator-keystore + - name: {{ .Values.orchestrator.address }}-truststore-volume + configMap: + name: {{ .Release.Name }}-orchestrator-truststore + restartPolicy: Always \ No newline at end of file diff --git a/kubernetes/arrowhead-helm/templates/orchestrator/orchestrator-keystore.yaml b/kubernetes/arrowhead-helm/templates/orchestrator/orchestrator-keystore.yaml new file mode 100644 index 000000000..a0ff4a26d --- /dev/null +++ b/kubernetes/arrowhead-helm/templates/orchestrator/orchestrator-keystore.yaml @@ -0,0 +1,6 @@ +kind: ConfigMap +apiVersion: v1 +metadata: + name: {{ .Release.Name }}-orchestrator-keystore +binaryData: + keystore.p12: {{ printf "static/certificates/%s" .Values.orchestrator.keystore | .Files.Get | b64enc }} \ No newline at end of file diff --git a/kubernetes/arrowhead-helm/templates/orchestrator/orchestrator-properties.yaml b/kubernetes/arrowhead-helm/templates/orchestrator/orchestrator-properties.yaml new file mode 100644 index 000000000..ef89152b6 --- /dev/null +++ b/kubernetes/arrowhead-helm/templates/orchestrator/orchestrator-properties.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Release.Name }}-orchestrator-properties +stringData: + application.properties: |- +{{ list (merge .Values.orchestrator.properties (fromYaml (include "loadDBConnectionDetails" (dict "dot" . "component" .Values.serviceRegistry))) (fromYaml (include "loadCertificateDetails" (dict "component" .Values.orchestrator "mountpath" "/orchestrator"))) (fromYaml (include "loadSRDetails" .))) "" | include "flattenYaml" | indent 4 }} \ No newline at end of file diff --git a/kubernetes/arrowhead-helm/templates/orchestrator/orchestrator-service.yaml b/kubernetes/arrowhead-helm/templates/orchestrator/orchestrator-service.yaml new file mode 100644 index 000000000..172c83e03 --- /dev/null +++ b/kubernetes/arrowhead-helm/templates/orchestrator/orchestrator-service.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.orchestrator.address }} +spec: + selector: + app: orchestrator + ports: + - port: {{ .Values.orchestrator.port }} + targetPort: {{ .Values.orchestrator.port }} + type: ClusterIP \ No newline at end of file diff --git a/kubernetes/arrowhead-helm/templates/orchestrator/orchestrator-truststore.yaml b/kubernetes/arrowhead-helm/templates/orchestrator/orchestrator-truststore.yaml new file mode 100644 index 000000000..39d3fc804 --- /dev/null +++ b/kubernetes/arrowhead-helm/templates/orchestrator/orchestrator-truststore.yaml @@ -0,0 +1,6 @@ +kind: ConfigMap +apiVersion: v1 +metadata: + name: {{ .Release.Name }}-orchestrator-truststore +binaryData: + truststore.p12: {{ printf "static/certificates/%s" .Values.orchestrator.truststore | .Files.Get | b64enc }} \ No newline at end of file diff --git a/kubernetes/arrowhead-helm/templates/service-registry/service-registry-deployment.yaml b/kubernetes/arrowhead-helm/templates/service-registry/service-registry-deployment.yaml new file mode 100644 index 000000000..8d8c6e51f --- /dev/null +++ b/kubernetes/arrowhead-helm/templates/service-registry/service-registry-deployment.yaml @@ -0,0 +1,55 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: service-registry + name: {{ .Release.Name }}-service-registry-deployment +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + app: service-registry + strategy: + type: Recreate + template: + metadata: + labels: + app: service-registry + spec: + containers: + - image: {{ .Values.serviceRegistry.image }} + name: serviceregistry + ports: + - containerPort: {{ .Values.serviceRegistry.port }} + resources: {} + volumeMounts: + - name: {{ .Values.serviceRegistry.address }}-properties-volume + mountPath: /serviceregistry/application.properties + subPath: application.properties + - name: {{ .Values.serviceRegistry.address }}-keystore-volume + mountPath: /serviceregistry/keystore + - name: {{ .Values.serviceRegistry.address }}-truststore-volume + mountPath: /serviceregistry/truststore + initContainers: + - image: busybox:1.28 + name: wait-for-db + volumeMounts: + - name: wait-for-db + mountPath: /init + command: + - /bin/sh + - /init/wait-for-db.sh + volumes: + - name: {{ .Values.serviceRegistry.address }}-properties-volume + secret: + secretName: {{ .Release.Name }}-service-registry-properties + - name: wait-for-db + configMap: + name: {{ .Release.Name }}-wait-for-db + - name: {{ .Values.serviceRegistry.address }}-keystore-volume + configMap: + name: {{ .Release.Name }}-service-registry-keystore + - name: {{ .Values.serviceRegistry.address }}-truststore-volume + configMap: + name: {{ .Release.Name }}-service-registry-truststore + restartPolicy: Always \ No newline at end of file diff --git a/kubernetes/arrowhead-helm/templates/service-registry/service-registry-keystore.yaml b/kubernetes/arrowhead-helm/templates/service-registry/service-registry-keystore.yaml new file mode 100644 index 000000000..08b72ca1b --- /dev/null +++ b/kubernetes/arrowhead-helm/templates/service-registry/service-registry-keystore.yaml @@ -0,0 +1,6 @@ +kind: ConfigMap +apiVersion: v1 +metadata: + name: {{ .Release.Name }}-service-registry-keystore +binaryData: + keystore.p12: {{ printf "static/certificates/%s" .Values.serviceRegistry.keystore | .Files.Get | b64enc }} \ No newline at end of file diff --git a/kubernetes/arrowhead-helm/templates/service-registry/service-registry-properties.yaml b/kubernetes/arrowhead-helm/templates/service-registry/service-registry-properties.yaml new file mode 100644 index 000000000..96ed4c2fb --- /dev/null +++ b/kubernetes/arrowhead-helm/templates/service-registry/service-registry-properties.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Release.Name }}-service-registry-properties +stringData: + application.properties: |- +{{ list (merge .Values.serviceRegistry.properties (fromYaml (include "loadDBConnectionDetails" (dict "dot" . "component" .Values.serviceRegistry))) (fromYaml (include "loadCertificateDetails" (dict "component" .Values.serviceRegistry "mountpath" "/serviceregistry")))) "" | include "flattenYaml" | indent 4 }} \ No newline at end of file diff --git a/kubernetes/arrowhead-helm/templates/service-registry/service-registry-service.yaml b/kubernetes/arrowhead-helm/templates/service-registry/service-registry-service.yaml new file mode 100644 index 000000000..95ccd5273 --- /dev/null +++ b/kubernetes/arrowhead-helm/templates/service-registry/service-registry-service.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.serviceRegistry.address }} +spec: + selector: + app: service-registry + ports: + - port: {{ .Values.serviceRegistry.port }} + targetPort: {{ .Values.serviceRegistry.port }} + type: ClusterIP \ No newline at end of file diff --git a/kubernetes/arrowhead-helm/templates/service-registry/service-registry-truststore.yaml b/kubernetes/arrowhead-helm/templates/service-registry/service-registry-truststore.yaml new file mode 100644 index 000000000..543ad60b3 --- /dev/null +++ b/kubernetes/arrowhead-helm/templates/service-registry/service-registry-truststore.yaml @@ -0,0 +1,6 @@ +kind: ConfigMap +apiVersion: v1 +metadata: + name: {{ .Release.Name }}-service-registry-truststore +binaryData: + truststore.p12: {{ printf "static/certificates/%s" .Values.serviceRegistry.truststore | .Files.Get | b64enc }} \ No newline at end of file diff --git a/kubernetes/arrowhead-helm/templates/wait-for-db-configmap.yaml b/kubernetes/arrowhead-helm/templates/wait-for-db-configmap.yaml new file mode 100644 index 000000000..b12d85938 --- /dev/null +++ b/kubernetes/arrowhead-helm/templates/wait-for-db-configmap.yaml @@ -0,0 +1,12 @@ +kind: ConfigMap +apiVersion: v1 +metadata: + name: {{ .Release.Name }}-wait-for-db +data: + wait-for-db.sh: | + #!/bin/sh + {{ printf "until nc -z -v -w30 %s %v" .Values.mysql.address .Values.mysql.port }} + do + echo "Waiting for database connection..." + sleep 5 + done \ No newline at end of file diff --git a/kubernetes/arrowhead-helm/values.yaml b/kubernetes/arrowhead-helm/values.yaml new file mode 100644 index 000000000..0e356b3dc --- /dev/null +++ b/kubernetes/arrowhead-helm/values.yaml @@ -0,0 +1,153 @@ +replicaCount: 1 + +namespace: arrowhead + +restartPolicy: Always + +emissary-ingress: + replicaCount: 1 + service: + ports: + - name: sreg + port: 3000 + targetPort: 3000 + - name: auth + port: 3001 + targetPort: 3001 + - name: orch + port: 3002 + targetPort: 3002 + loadBalancerIP: + +mysql: + image: mysql:8.0 + port: 3306 + address: mysql + rootPassword: tvYiBMhznmcVOeng + +serviceRegistry: + image: arrowheadcr.azurecr.io/serviceregistry:4.4.0 + port: 80 + address: arrowhead-serviceregistry + sslEnabled: true + datasourceUsername: s_reg + datasourcePassword: 3RgoBkD5mqER68js + keystore: test-sreg.p12 + keystorePassword: SgK0QHHX7ge1vtGs + keystoreType: PKCS12 + keyAlias: service_registry.testcloud2.aitia.arrowhead.eu + keyPassword: SgK0QHHX7ge1vtGs + truststore: test-cloud.truststore.p12 + truststorePassword: "123456" + truststoreType: PKCS12 + properties: + log_all_request_and_response: false + server: + address: 0.0.0.0 + port: 80 + use_strict_service_definition_verifier: true + disable: + hostname: + verifier: false + ping_timeout: 5000 + spring: + jpa: + hibernate: + ddl-auto: none + database-platform: org.hibernate.dialect.MySQL5InnoDBDialect + properties: + hibernate: + format_sql: true + show-sql: false + ping_interval: 60 + use_network_address_detector: false + ttl_scheduled: false + allow_non_routable_addressing: true + use_strict_service_intf_name_verifier: false + domain: + port: 3000 + name: + allow_self_addressing: true + ping_scheduled: false + ttl_interval: 10 + +authorization: + image: arrowheadcr.azurecr.io/authorization:4.4.0 + port: 80 + address: arrowhead-authorization + sslEnabled: true + datasourceUsername: auth + datasourcePassword: CDif68aDUWTvHWaN + keystore: test-auth-system.p12 + keystorePassword: 3Irx3m9D8RfkEoyP + keystoreType: PKCS12 + keyAlias: authorization.testcloud2.aitia.arrowhead.eu + keyPassword: 3Irx3m9D8RfkEoyP + truststore: test-cloud.truststore.p12 + truststorePassword: "123456" + truststoreType: PKCS12 + properties: + log_all_request_and_response: false + server: + address: 0.0.0.0 + port: 80 + use_strict_service_definition_verifier: true + disable: + hostname: + verifier: false + ping_timeout: 5000 + spring: + jpa: + hibernate: + ddl-auto: none + database-platform: org.hibernate.dialect.MySQL5InnoDBDialect + properties: + hibernate: + format_sql: true + show-sql: false + use_strict_service_intf_name_verifier: false + domain: + port: 3001 + name: + +orchestrator: + image: arrowheadcr.azurecr.io/orchestrator:4.4.0 + port: 80 + address: arrowhead-orchestration + sslEnabled: true + datasourceUsername: orch + datasourcePassword: 6zXzDAQ8vkgyJipI + keystore: test-orch.p12 + keystorePassword: C40HciVwR6V6Adbf + keystoreType: PKCS12 + keyAlias: orchestrator.testcloud2.aitia.arrowhead.eu + keyPassword: C40HciVwR6V6Adbf + truststore: test-cloud.truststore.p12 + truststorePassword: "123456" + truststoreType: PKCS12 + logging: false + domain: + exposedPort: 3002 + properties: + log_all_request_and_response: false + server: + address: 0.0.0.0 + port: 80 + use_strict_service_definition_verifier: true + disable: + hostname: + verifier: false + ping_timeout: 5000 + spring: + jpa: + hibernate: + ddl-auto: none + database-platform: org.hibernate.dialect.MySQL5InnoDBDialect + properties: + hibernate: + format_sql: true + show-sql: false + use_strict_service_intf_name_verifier: false + domain: + port: 3002 + name: \ No newline at end of file diff --git a/onboarding/.project b/onboarding/.project index eb567f148..bbe5978bb 100644 --- a/onboarding/.project +++ b/onboarding/.project @@ -15,6 +15,11 @@ + + org.springframework.ide.eclipse.boot.validation.springbootbuilder + + + org.eclipse.m2e.core.maven2Builder diff --git a/orchestrator/src/main/java/eu/arrowhead/core/orchestrator/protocols/coap/CoapOrchestrator.java b/orchestrator/src/main/java/eu/arrowhead/core/orchestrator/protocols/coap/CoapOrchestrator.java new file mode 100644 index 000000000..20b373cc9 --- /dev/null +++ b/orchestrator/src/main/java/eu/arrowhead/core/orchestrator/protocols/coap/CoapOrchestrator.java @@ -0,0 +1,363 @@ +package eu.arrowhead.core.orchestrator.protocols.coap; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import org.apache.http.HttpStatus; +import eu.arrowhead.common.CommonConstants; +import eu.arrowhead.common.CoreCommonConstants; +import eu.arrowhead.common.Utilities; +import eu.arrowhead.common.coap.AhCoapServer; +import eu.arrowhead.common.coap.configuration.CoapCertificates; +import eu.arrowhead.common.coap.configuration.CoapCredentials; +import eu.arrowhead.common.coap.configuration.CoapServerConfiguration; +import eu.arrowhead.common.coap.tools.CoapTools; +import eu.arrowhead.common.dto.shared.CloudRequestDTO; +import eu.arrowhead.common.dto.shared.OrchestrationFlags.Flag; +import eu.arrowhead.common.dto.shared.OrchestrationFormRequestDTO; +import eu.arrowhead.common.dto.shared.PreferredProviderDataDTO; +import eu.arrowhead.common.dto.shared.SystemRequestDTO; +import eu.arrowhead.common.exception.BadPayloadException; +import eu.arrowhead.core.orchestrator.service.OrchestratorService; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.eclipse.californium.core.CoapResource; +import org.eclipse.californium.core.coap.CoAP.ResponseCode; +import org.eclipse.californium.core.coap.MediaTypeRegistry; +import org.eclipse.californium.core.server.resources.CoapExchange; +import org.eclipse.californium.core.server.resources.Resource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +public class CoapOrchestrator { + // ================================================================================================= + // members + private final Logger logger = LogManager.getLogger(CoapOrchestrator.class); + private AhCoapServer coapServer; + + @Value(CoreCommonConstants.$ORCHESTRATOR_USE_FLEXIBLE_STORE_WD) + private boolean useFlexibleStore; + + @Autowired + private OrchestratorService orchestratorService; + + @Value(CoreCommonConstants.$ORCHESTRATOR_IS_GATEKEEPER_PRESENT_WD) + private boolean gatekeeperIsPresent; + + @Value(CoreCommonConstants.$COAP_SERVER_ADDRESS_ENABLED) + private boolean coapServerEnabled; + + @Value(CoreCommonConstants.$COAP_SERVER_ADDRESS) + private String coapServerAddress; + + @Value(CoreCommonConstants.$COAP_SERVER_PORT) + private int coapServerPort; + + @Value(CommonConstants.$SERVER_SSL_ENABLED_WD) + private boolean serverSslEnabled; + + @Value(CommonConstants.$KEYSTORE_TYPE_WD) + private String keyStoreType; + + @Value(CommonConstants.$KEYSTORE_PATH_WD) + private String keyStorePath; + + @Value(CommonConstants.$KEYSTORE_PASSWORD_WD) + private String keyStorePassword; + + @Value(CommonConstants.$KEY_PASSWORD_WD) + private String keyPassword; + + @Value(CommonConstants.$TRUSTSTORE_PATH_WD) + private String trustStorePath; + + @Value(CommonConstants.$TRUSTSTORE_PASSWORD_WD) + private String trustStorePassword; + + final String URL_PATH_ORCHESTRATION = "orchestration2"; + final String URL_PATH_ID = "id"; + + private static final String NULL_PARAMETER_ERROR_MESSAGE = " is null."; + private static final String NULL_OR_BLANK_PARAMETER_ERROR_MESSAGE = " is null or blank."; + private static final String GATEKEEPER_IS_NOT_PRESENT_ERROR_MESSAGE = " can not be served. Orchestrator runs in NO GATEKEEPER mode."; + private static final String ID_NOT_VALID_ERROR_MESSAGE = " Id must be greater than 0. "; + + // ================================================================================================= + // methods + // ------------------------------------------------------------------------------------------------- + @PostConstruct + public void init() { + logger.info("CoAP Protocol Init"); + + if (coapServerEnabled) { + logger.info("CoAP Protocol Enabled"); + CoapServerConfiguration coapServerConfiguration = new CoapServerConfiguration( + coapServerAddress, + coapServerPort, + serverSslEnabled, + new CoapCredentials( + keyStorePath, + keyStorePassword, + keyPassword, + "orchestrator-coap"), + new CoapCertificates( + "coap-root", + trustStorePassword, + trustStorePath)); + coapServer = new AhCoapServer(coapServerConfiguration); + initializateResources(); + coapServer.start(); + } else { + logger.info("CoAP Protocol Disabled"); + } + } + + @PreDestroy + public void preDestroy() { + if (coapServerEnabled) { + logger.info("Killing CoAP Server"); + coapServer.stop(); + } + } + + // ================================================================================================= + // assistant methods + // ------------------------------------------------------------------------------------------------- + private void initializateResources() { + coapServer.add(new EchoResource()); + coapServer.add(new OrchestrationResource()); + } + + // ================================================================================================= + // CoAP resources + // ------------------------------------------------------------------------------------------------- + class EchoResource extends CoapResource { + + public EchoResource() { + super("echo"); + getAttributes().setTitle("Echo Resource"); + } + + @Override + public void handleGET(CoapExchange exchange) { + exchange.respond( + ResponseCode.CONTENT, + "Got it!", + MediaTypeRegistry.TEXT_PLAIN); + } + + } + + // ------------------------------------------------------------------------------------------------- + class OrchestrationResource extends CoapResource { + private final ObjectMapper mapper = new ObjectMapper(); + + public OrchestrationResource() { + super(URL_PATH_ORCHESTRATION); + add(new OrchestrationIdResource()); + getAttributes().setTitle("Start Orchestration Process"); + } + + @Override + public void handlePOST(CoapExchange exchange) { + + try { + OrchestrationFormRequestDTO request = mapper.readValue(exchange.getRequestText(), + OrchestrationFormRequestDTO.class); + + checkOrchestratorFormRequestDTO(request); + + if (request.getOrchestrationFlags().getOrDefault(Flag.EXTERNAL_SERVICE_REQUEST, false)) { + if (!gatekeeperIsPresent) { + throw new BadPayloadException( + "External service request" + GATEKEEPER_IS_NOT_PRESENT_ERROR_MESSAGE, + HttpStatus.SC_BAD_REQUEST, URL_PATH_ORCHESTRATION); + } + + exchange.respond( + ResponseCode.CONTENT, + mapper.writeValueAsString(orchestratorService.externalServiceRequest(request)), + MediaTypeRegistry.APPLICATION_JSON); + return; + + } else if (request.getOrchestrationFlags().getOrDefault(Flag.TRIGGER_INTER_CLOUD, false)) { + if (!gatekeeperIsPresent) { + throw new BadPayloadException( + "Forced inter cloud service request" + GATEKEEPER_IS_NOT_PRESENT_ERROR_MESSAGE, + HttpStatus.SC_BAD_REQUEST, URL_PATH_ORCHESTRATION); + } + + exchange.respond( + ResponseCode.CONTENT, + mapper.writeValueAsString(orchestratorService.triggerInterCloud(request)), + MediaTypeRegistry.APPLICATION_JSON); + return; + } else if (!request.getOrchestrationFlags().getOrDefault(Flag.OVERRIDE_STORE, false)) { + exchange.respond( + ResponseCode.CONTENT, + mapper.writeValueAsString(orchestratorService.orchestrationFromStore(request)), + MediaTypeRegistry.APPLICATION_JSON); + return; + } else { + exchange.respond( + ResponseCode.CONTENT, + mapper.writeValueAsString(orchestratorService.dynamicOrchestration(request)), + MediaTypeRegistry.APPLICATION_JSON); + return; + } + + } catch (Exception ex) { + exchange.respond( + ResponseCode.INTERNAL_SERVER_ERROR, + ex.getMessage(), + MediaTypeRegistry.TEXT_PLAIN); + } + + } + + } + + // ------------------------------------------------------------------------------------------------- + class OrchestrationIdResource extends CoapResource { + private final ObjectMapper mapper = new ObjectMapper(); + + public OrchestrationIdResource() { + super(URL_PATH_ID); + getAttributes() + .setTitle("Start ochestration process from the ochestrator store based on consumer system id"); + } + + @Override + public Resource getChild(String name) { + return this; + } + + @Override + public void handleGET(CoapExchange exchange) { + try { + int systemId = Integer.parseInt(CoapTools.getUrlPathValue(exchange, URL_PATH_ID)); + + if (systemId < 1) { + throw new Exception(String.format("Id %d not valid!", systemId)); + } + + if (useFlexibleStore) { + throw new BadPayloadException("Orchestrator use flexible store!", HttpStatus.SC_BAD_REQUEST); + } + + if (systemId < 1) { + throw new BadPayloadException("Consumer system : " + ID_NOT_VALID_ERROR_MESSAGE, + HttpStatus.SC_BAD_REQUEST); + } + + exchange.respond( + ResponseCode.CONTENT, + mapper.writeValueAsString(orchestratorService.storeOchestrationProcessResponse(systemId)), + MediaTypeRegistry.APPLICATION_JSON); + return; + + } catch (Exception ex) { + exchange.respond( + ResponseCode.BAD_REQUEST, + ex.getMessage(), + MediaTypeRegistry.TEXT_PLAIN); + } + } + + } + + // ================================================================================================= + // assistant methods + + // ------------------------------------------------------------------------------------------------- + private void checkOrchestratorFormRequestDTO(final OrchestrationFormRequestDTO request) { + logger.debug("checkOrchestratorFormRequestDTO started..."); + + if (request == null) { + throw new BadPayloadException("Request" + NULL_PARAMETER_ERROR_MESSAGE, HttpStatus.SC_BAD_REQUEST); + } + + request.validateCrossParameterConstraints(); + + // Requester system + checkSystemRequestDTO(request.getRequesterSystem()); + + // Requester cloud + if (request.getRequesterCloud() != null) { + checkCloudRequestDTO(request.getRequesterCloud()); + } + + // Requested service + if (request.getRequestedService() != null + && Utilities.isEmpty(request.getRequestedService().getServiceDefinitionRequirement())) { + throw new BadPayloadException( + "Requested service definition requirement" + NULL_OR_BLANK_PARAMETER_ERROR_MESSAGE, + HttpStatus.SC_BAD_REQUEST); + } + + // Preferred Providers + if (request.getPreferredProviders() != null) { + for (final PreferredProviderDataDTO provider : request.getPreferredProviders()) { + checkSystemRequestDTO(provider.getProviderSystem()); + if (provider.getProviderCloud() != null) { + checkCloudRequestDTO(provider.getProviderCloud()); + } + } + } + } + + // ------------------------------------------------------------------------------------------------- + private void checkSystemRequestDTO(final SystemRequestDTO system) { + logger.debug("checkSystemRequestDTO started..."); + + if (system == null) { + throw new BadPayloadException("System" + NULL_PARAMETER_ERROR_MESSAGE, HttpStatus.SC_BAD_REQUEST); + } + + if (Utilities.isEmpty(system.getSystemName())) { + throw new BadPayloadException("System name" + NULL_OR_BLANK_PARAMETER_ERROR_MESSAGE, + HttpStatus.SC_BAD_REQUEST); + } + + if (Utilities.isEmpty(system.getAddress())) { + throw new BadPayloadException("System address" + NULL_OR_BLANK_PARAMETER_ERROR_MESSAGE, + HttpStatus.SC_BAD_REQUEST); + } + + if (system.getPort() == null) { + throw new BadPayloadException("System port" + NULL_PARAMETER_ERROR_MESSAGE, HttpStatus.SC_BAD_REQUEST); + } + + final int validatedPort = system.getPort().intValue(); + if (validatedPort < CommonConstants.SYSTEM_PORT_RANGE_MIN + || validatedPort > CommonConstants.SYSTEM_PORT_RANGE_MAX) { + throw new BadPayloadException("System port must be between " + CommonConstants.SYSTEM_PORT_RANGE_MIN + + " and " + CommonConstants.SYSTEM_PORT_RANGE_MAX + ".", HttpStatus.SC_BAD_REQUEST); + } + } + + // ------------------------------------------------------------------------------------------------- + private void checkCloudRequestDTO(final CloudRequestDTO cloud) { + logger.debug("checkCloudRequestDTO started..."); + + if (cloud == null) { + throw new BadPayloadException("Cloud" + NULL_PARAMETER_ERROR_MESSAGE, HttpStatus.SC_BAD_REQUEST); + } + + if (Utilities.isEmpty(cloud.getOperator())) { + throw new BadPayloadException("Cloud operator" + NULL_OR_BLANK_PARAMETER_ERROR_MESSAGE, + HttpStatus.SC_BAD_REQUEST); + } + + if (Utilities.isEmpty(cloud.getName())) { + throw new BadPayloadException("Cloud name" + NULL_OR_BLANK_PARAMETER_ERROR_MESSAGE, + HttpStatus.SC_BAD_REQUEST); + } + } + +} diff --git a/orchestrator/src/main/java/eu/arrowhead/core/orchestrator/protocols/mqtt/MqttOrchestrator.java b/orchestrator/src/main/java/eu/arrowhead/core/orchestrator/protocols/mqtt/MqttOrchestrator.java new file mode 100644 index 000000000..34536cc25 --- /dev/null +++ b/orchestrator/src/main/java/eu/arrowhead/core/orchestrator/protocols/mqtt/MqttOrchestrator.java @@ -0,0 +1,408 @@ +package eu.arrowhead.core.orchestrator.protocols.mqtt; + +import eu.arrowhead.core.orchestrator.protocols.coap.*; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.DeserializationFeature; +import javax.annotation.PostConstruct; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import eu.arrowhead.common.CommonConstants; +import eu.arrowhead.common.CoreCommonConstants; +import eu.arrowhead.common.SSLProperties; +import eu.arrowhead.common.Utilities; +import eu.arrowhead.common.SslUtil; +import eu.arrowhead.common.dto.shared.CloudRequestDTO; +import eu.arrowhead.common.dto.shared.OrchestrationFlags.Flag; +import eu.arrowhead.common.dto.shared.OrchestrationFormRequestDTO; +import eu.arrowhead.common.dto.shared.PreferredProviderDataDTO; +import eu.arrowhead.common.dto.shared.SystemRequestDTO; +import eu.arrowhead.common.dto.shared.MqttRequestDTO; +import eu.arrowhead.common.dto.shared.MqttResponseDTO; +import eu.arrowhead.core.orchestrator.service.OrchestratorService; + +import org.springframework.beans.factory.annotation.Value; + +import java.nio.charset.StandardCharsets; +import java.util.Map; + +import org.eclipse.paho.client.mqttv3.MqttClient; +import org.eclipse.paho.client.mqttv3.MqttCallback; +import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; +import org.eclipse.paho.client.mqttv3.MqttConnectOptions; +import org.eclipse.paho.client.mqttv3.MqttException; +import org.eclipse.paho.client.mqttv3.MqttMessage; +import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; + +@Component +public class MqttOrchestrator implements MqttCallback, Runnable { + + // ================================================================================================= + // members + private final Logger logger = LogManager.getLogger(MqttOrchestrator.class); + + @Value(CoreCommonConstants.$ORCHESTRATOR_IS_GATEKEEPER_PRESENT_WD) + private boolean gatekeeperIsPresent; + + @Autowired + private OrchestratorService orchestratorService; + + @Value(CoreCommonConstants.$CORE_SYSTEM_NAME) + private String mqttSystemName; + + @Value(CoreCommonConstants.$MQTT_BROKER_ENABLED) + private boolean mqttBrokerEnabled; + + @Value(CoreCommonConstants.$MQTT_BROKER_ADDRESS) + private String mqttBrokerAddress; + + @Value(CoreCommonConstants.$MQTT_BROKER_PORT) + private int mqttBrokerPort; + + @Autowired + private SSLProperties sslProperties; + + @Value(CoreCommonConstants.$MQTT_BROKER_USERNAME) + private String mqttBrokerUsername; + + @Value(CoreCommonConstants.$MQTT_BROKER_PASSWORD) + private String mqttBrokerPassword; + + @Value(CoreCommonConstants.$MQTT_BROKER_CAFILE) + private String mqttBrokerCAFile; + + @Value(CoreCommonConstants.$MQTT_BROKER_CERTFILE) + private String mqttBrokerCertFile; + + @Value(CoreCommonConstants.$MQTT_BROKER_KEYFILE) + private String mqttBrokerKeyFile; + + @Value(CommonConstants.$SERVER_SSL_ENABLED_WD) + private boolean serverSslEnabled; + + private final String URL_PATH_ORCHESTRATOR = "orchestrator"; + private final String URL_PATH_ID = "id"; + + private final String ECHO_TOPIC = "ah/orchestration/echo"; + private final String ORCHESTRATION_TOPIC = "ah/orchestration"; + private final String ORCHESTRATION_BY_ID_TOPIC = "ah/orchestration/id"; + + Thread t = null; + ObjectMapper mapper; + + // ================================================================================================= + // methods + // ------------------------------------------------------------------------------------------------- + @PostConstruct + public void init() { + + if (mqttBrokerEnabled) { + logger.info("Starting MQTT protocol"); + + if(Utilities.isEmpty(mqttBrokerUsername) || Utilities.isEmpty(mqttBrokerPassword)) { + logger.info("Missing MQTT broker username or password! Using anonymoues login."); + } + + mapper = new ObjectMapper(); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + + t = new Thread(this); + t.start(); + } + } + + MqttClient client = null; + MemoryPersistence persistence = null; + + private void connectBroker() { + MemoryPersistence persistence = new MemoryPersistence(); + + try { + MqttConnectOptions connOpts = new MqttConnectOptions(); + connOpts.setCleanSession(true); + connOpts.setUserName(mqttBrokerUsername); + connOpts.setPassword(mqttBrokerPassword.toCharArray()); + + connOpts.setConnectionTimeout(60); + connOpts.setKeepAliveInterval(60); + connOpts.setMqttVersion(MqttConnectOptions.MQTT_VERSION_3_1); + + if (sslProperties.isSslEnabled()) { + try { + SSLSocketFactory socketFactory = null; + socketFactory = SslUtil.getSSLSocketFactory(sslProperties); + connOpts.setSocketFactory(socketFactory); + } catch (Exception e) { + logger.info("Could not load certificate: " + e.toString()); + } + } + + client.setCallback(this); + client.connect(connOpts); + String topics[] = { ECHO_TOPIC, ORCHESTRATION_TOPIC, ORCHESTRATION_BY_ID_TOPIC }; + client.subscribe(topics); + } catch (MqttException me) { + logger.info("Could not connect to MQTT broker!\n\t" + me.toString()); + } + + } + + // ================================================================================================= + // assistant methods + // ------------------------------------------------------------------------------------------------- + private void checkOrchestratorFormRequestDTO(final OrchestrationFormRequestDTO request, final String origin) + throws Exception { + if (request == null) { + throw new Exception("Request null"); + } + + request.validateCrossParameterConstraints(); + + // Requester system + checkSystemRequestDTO(request.getRequesterSystem(), origin); + + // Requester cloud + if (request.getRequesterCloud() != null) { + checkCloudRequestDTO(request.getRequesterCloud(), origin); + } + + // Requested service + if (request.getRequestedService() != null + && Utilities.isEmpty(request.getRequestedService().getServiceDefinitionRequirement())) { + throw new Exception("Requested service definition requirement"); + } + + // Preferred Providers + if (request.getPreferredProviders() != null) { + for (final PreferredProviderDataDTO provider : request.getPreferredProviders()) { + checkSystemRequestDTO(provider.getProviderSystem(), origin); + if (provider.getProviderCloud() != null) { + checkCloudRequestDTO(provider.getProviderCloud(), origin); + } + } + } + } + + // ------------------------------------------------------------------------------------------------- + private void checkSystemRequestDTO(final SystemRequestDTO system, final String origin) throws Exception { + logger.debug("checkSystemRequestDTO started..."); + + if (system == null) { + throw new Exception("System null"); + } + + if (Utilities.isEmpty(system.getSystemName())) { + throw new Exception("System name null"); + } + + if (Utilities.isEmpty(system.getAddress())) { + throw new Exception("System address null"); + } + + if (system.getPort() == null) { + throw new Exception("System port null"); + } + + final int validatedPort = system.getPort().intValue(); + if (validatedPort < CommonConstants.SYSTEM_PORT_RANGE_MIN + || validatedPort > CommonConstants.SYSTEM_PORT_RANGE_MAX) { + throw new Exception("System port must be between " + CommonConstants.SYSTEM_PORT_RANGE_MIN + " and " + + CommonConstants.SYSTEM_PORT_RANGE_MAX + "."); + } + } + + // ------------------------------------------------------------------------------------------------- + private void checkCloudRequestDTO(final CloudRequestDTO cloud, final String origin) throws Exception { + logger.debug("checkCloudRequestDTO started..."); + + if (cloud == null) { + throw new Exception("Cloud null"); + } + + if (Utilities.isEmpty(cloud.getOperator())) { + throw new Exception("Cloud operator null"); + } + + if (Utilities.isEmpty(cloud.getName())) { + throw new Exception("Cloud name null"); + } + } + + @Override + public void run() { + while (true) { + try { + if (client == null) { + persistence = new MemoryPersistence(); + client = new MqttClient("ssl://" + mqttBrokerAddress + ":" + mqttBrokerPort, mqttSystemName, persistence); + } + if (!client.isConnected()) { + connectBroker(); + } + Thread.sleep(1000 * 15); + } catch (InterruptedException iex) { + logger.info("Error starting MQTT timeout thread"); + } catch (MqttException mex) { + logger.info("MQTT error: " + mex.toString()); + } + } + + } + + @Override + public void connectionLost(Throwable cause) { + logger.info("Connection lost to MQTT broker"); + client = null; + } + + @Override + public void messageArrived(String topic, MqttMessage message) { + MqttRequestDTO request = null; + MqttResponseDTO response = null; + + try { + request = mapper.readValue(message.toString(), MqttRequestDTO.class); + } catch (Exception ae) { + logger.info("Could not convert MQTT message to REST request!"); + return; + } + + logger.info(request.toString()); + + switch (topic) { + case ECHO_TOPIC: + + handleEcho(topic, message, request); + return; + + case ORCHESTRATION_TOPIC: + + handleOrchestration(topic, message, request); + return; + + case ORCHESTRATION_BY_ID_TOPIC: + logger.info("orchestration/id(): " + new String(message.getPayload(), StandardCharsets.UTF_8)); + if (!request.getMethod().toLowerCase().equals("post")) { + return; + } + + try { + int id = Integer.parseInt(request.getQueryParameters().get("id")); + + if (id < 1) { + throw new Exception("Id not valid"); + } + + response = new MqttResponseDTO("200", "application/json", null); + response.setPayload(orchestratorService.storeOchestrationProcessResponse(id)); + MqttMessage resp = new MqttMessage(mapper.writeValueAsString(response).getBytes()); + resp.setQos(2); + client.publish(request.getReplyTo(), resp); + } catch (Exception e) { + logger.info("illegal request: " + e.toString()); + } + + break; + default: + logger.info("Received message to unsupported topic"); + } + + } + + @Override + public void deliveryComplete(IMqttDeliveryToken token) { + + } + + //------------------------------------------------------------------------------------------------- + private boolean handleEcho(String topic, MqttMessage message, MqttRequestDTO request) { + logger.info(request.getMethod() + " echo(): " + new String(message.getPayload(), StandardCharsets.UTF_8)); + if (!request.getMethod().toLowerCase().equals("get")) { + return false; + } + + try { + MqttResponseDTO response = new MqttResponseDTO("200", "text/plain", new String("Got it")); + MqttMessage resp = new MqttMessage(Utilities.toJson(response).getBytes()); + resp.setQos(2); + client.publish(request.getReplyTo(), resp); + return true; + } catch (MqttException mex) { + logger.info("echo(): Couldn't reply " + mex.toString()); + } + return false; + } + + //------------------------------------------------------------------------------------------------- + private boolean handleOrchestration(String topic, MqttMessage message, MqttRequestDTO request) { + MqttResponseDTO response = null; + + logger.info("ah/orchestration(): " + new String(message.getPayload(), StandardCharsets.UTF_8)); + if (!request.getMethod().equalsIgnoreCase("post")) { + return false; + } + + try { + OrchestrationFormRequestDTO orchRequest = mapper.convertValue(request.getPayload(), + OrchestrationFormRequestDTO.class); + + final String origin = CommonConstants.ORCHESTRATOR_URI + CommonConstants.CORE_SERVICE_ORCH_PROCESS; + checkOrchestratorFormRequestDTO(orchRequest, origin); + + if (orchRequest.getOrchestrationFlags().getOrDefault(Flag.EXTERNAL_SERVICE_REQUEST, false)) { + if (!gatekeeperIsPresent) { + throw new Exception("External service request, Gatekeeper is not present."); + } + response = new MqttResponseDTO("200", "application/json", null); + response.setPayload(orchestratorService.externalServiceRequest(orchRequest)); + MqttMessage resp = new MqttMessage(mapper.writeValueAsString(response).getBytes()); + resp.setQos(2); + client.publish(request.getReplyTo(), resp); + + return true; + } else if (orchRequest.getOrchestrationFlags().getOrDefault(Flag.TRIGGER_INTER_CLOUD, false)) { + if (!gatekeeperIsPresent) { + throw new Exception("External service request, Gatekeeper is not present."); + } + response = new MqttResponseDTO("200", "application/json", null); + response.setPayload(orchestratorService.triggerInterCloud(orchRequest)); + MqttMessage resp = new MqttMessage(mapper.writeValueAsString(response).getBytes()); + resp.setQos(2); + client.publish(request.getReplyTo(), resp); + + return true; + } else if (!orchRequest.getOrchestrationFlags().getOrDefault(Flag.OVERRIDE_STORE, false)) { + response = new MqttResponseDTO("200", "application/json", null); + response.setPayload(orchestratorService.orchestrationFromStore(orchRequest)); + MqttMessage resp = new MqttMessage(mapper.writeValueAsString(response).getBytes()); + resp.setQos(2); + client.publish(request.getReplyTo(), resp); + + return true; + } else { + response = new MqttResponseDTO("200", "application/json", null); + response.setPayload(orchestratorService.dynamicOrchestration(orchRequest)); + MqttMessage resp = new MqttMessage(mapper.writeValueAsString(response).getBytes()); + resp.setQos(2); + client.publish(request.getReplyTo(), resp); + + return true; + } + + } catch (Exception ex) { + try { + response = new MqttResponseDTO("500", "text/plain", null); + MqttMessage resp = new MqttMessage(mapper.writeValueAsString(response).getBytes()); + resp.setQos(2); + client.publish(request.getReplyTo(), resp); + } catch (Exception mex) { + } + } + + return false; + } +} diff --git a/orchestrator/src/main/resources/application.properties b/orchestrator/src/main/resources/application.properties index d3d6dc649..f4c4f7f39 100644 --- a/orchestrator/src/main/resources/application.properties +++ b/orchestrator/src/main/resources/application.properties @@ -21,6 +21,18 @@ server.port=8441 domain.name=127.0.0.1 domain.port=8441 +# CoAP server parameters +coap.server.enabled=false +coap.server.address=0.0.0.0 +coap.server.port=5684 + +# MQTT broker parameters +mqtt.broker.enabled=false +mqtt.broker.address=127.0.0.1 +mqtt.broker.port=8883 +mqtt.broker.username=orchestrator +mqtt.broker.password=secretpassword + ############################################ ### CUSTOM PARAMETERS ### ############################################ @@ -79,4 +91,4 @@ server.ssl.trust-store-password=123456 #If true, http client does not check whether the hostname is match one of the server's SAN in its certificate #Just for testing, DO NOT USE this feature in production environment -disable.hostname.verifier=false \ No newline at end of file +disable.hostname.verifier=false diff --git a/pom.xml b/pom.xml index 55cf3a08b..cfa34fcab 100644 --- a/pom.xml +++ b/pom.xml @@ -42,6 +42,7 @@ qos-monitor translator plantdescriptionengine + hawkbit-configuration-manager deb-installer docker-all @@ -215,6 +216,7 @@ gson ${google.gson.version} + commons-net commons-net diff --git a/scripts/initSQL.sh b/scripts/initSQL.sh index 2ced347e4..c45918594 100644 --- a/scripts/initSQL.sh +++ b/scripts/initSQL.sh @@ -4,26 +4,25 @@ rm -rf sql mkdir sql cd sql -curl -O https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/create_empty_arrowhead_db.sql +wget https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/create_empty_arrowhead_db.sql sed -i 's/source /& docker-entrypoint-initdb.d\/privileges\//g' create_empty_arrowhead_db.sql mkdir privileges cd privileges -curl -O https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/authorization_privileges.sql -curl -O https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/certificate_authority_privileges.sql -curl -O https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/choreographer_privileges.sql -curl -O https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/create_arrowhead_tables.sql -curl -O https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/device_registry_privileges.sql -curl -O https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/event_handler_privileges.sql -curl -O https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/gatekeeper_privileges.sql -curl -O https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/gateway_privileges.sql -curl -O https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/onboarding_controller_privileges.sql -curl -O https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/orchestrator_privileges.sql -curl -O https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/qos_monitor_privileges.sql -curl -O https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/service_registry_privileges.sql -curl -O https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/system_registry_privileges.sql -curl -O https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/translator_privileges.sql -curl -O https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/datamanager_privileges.sql -curl -O https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/configuration_privileges.sql -curl -O https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/plant_description_engine_privileges.sql -curl -O https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/timemanager_privileges.sql +wget https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/authorization_privileges.sql +wget https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/certificate_authority_privileges.sql +wget https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/choreographer_privileges.sql +wget https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/create_arrowhead_tables.sql +wget https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/device_registry_privileges.sql +wget https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/event_handler_privileges.sql +wget https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/gatekeeper_privileges.sql +wget https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/gateway_privileges.sql +wget https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/onboarding_controller_privileges.sql +wget https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/orchestrator_privileges.sql +wget https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/qos_monitor_privileges.sql +wget https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/service_registry_privileges.sql +wget https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/system_registry_privileges.sql +wget https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/translator_privileges.sql +wget https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/datamanager_privileges.sql +wget https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/configuration_privileges.sql +wget https://raw.githubusercontent.com/eclipse-arrowhead/core-java-spring/master/scripts/plant_description_engine_privileges.sql diff --git a/serviceregistry/src/main/java/eu/arrowhead/core/serviceregistry/protocols/coap/CoapServiceRegistry.java b/serviceregistry/src/main/java/eu/arrowhead/core/serviceregistry/protocols/coap/CoapServiceRegistry.java new file mode 100644 index 000000000..aefb74543 --- /dev/null +++ b/serviceregistry/src/main/java/eu/arrowhead/core/serviceregistry/protocols/coap/CoapServiceRegistry.java @@ -0,0 +1,578 @@ +package eu.arrowhead.core.serviceregistry.protocols.coap; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import org.apache.http.HttpStatus; +import eu.arrowhead.common.CommonConstants; +import eu.arrowhead.common.CoreDefaults; +import eu.arrowhead.common.CoreCommonConstants; +import eu.arrowhead.common.Utilities; +import eu.arrowhead.common.coap.AhCoapServer; +import eu.arrowhead.common.coap.configuration.CoapCertificates; +import eu.arrowhead.common.coap.configuration.CoapCredentials; +import eu.arrowhead.common.coap.configuration.CoapServerConfiguration; +import eu.arrowhead.common.coap.tools.CoapTools; +import eu.arrowhead.common.core.CoreSystem; +import eu.arrowhead.common.dto.shared.ServiceQueryFormDTO; +import eu.arrowhead.common.dto.shared.ServiceQueryFormListDTO; +import eu.arrowhead.common.dto.shared.ServiceRegistryRequestDTO; +import eu.arrowhead.common.dto.shared.ServiceSecurityType; +import eu.arrowhead.common.dto.shared.SystemRequestDTO; +import eu.arrowhead.common.exception.BadPayloadException; +import eu.arrowhead.common.verifier.CommonNamePartVerifier; +import eu.arrowhead.core.serviceregistry.database.service.ServiceRegistryDBService; + +import java.time.format.DateTimeParseException; +import java.util.Map; +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.eclipse.californium.core.CoapResource; +import org.eclipse.californium.core.coap.CoAP.ResponseCode; +import org.eclipse.californium.core.coap.MediaTypeRegistry; +import org.eclipse.californium.core.server.resources.CoapExchange; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +public class CoapServiceRegistry { + + // ================================================================================================= + // members + private final Logger logger = LogManager.getLogger(CoapServiceRegistry.class); + + private AhCoapServer coapServer; + + @Value(CoreCommonConstants.$ORCHESTRATOR_IS_GATEKEEPER_PRESENT_WD) + private boolean gatekeeperIsPresent; + + @Autowired + private ServiceRegistryDBService serviceRegistryDBService; + + @Autowired + private CommonNamePartVerifier cnVerifier; + + @Value(CoreCommonConstants.$COAP_SERVER_ADDRESS_ENABLED) + private boolean coapServerEnabled; + + @Value(CoreCommonConstants.$COAP_SERVER_ADDRESS) + private String coapServerAddress; + + @Value(CoreCommonConstants.$COAP_SERVER_PORT) + private int coapServerPort; + + @Value(CommonConstants.$SERVER_SSL_ENABLED_WD) + private boolean serverSslEnabled; + + @Value(CommonConstants.$KEYSTORE_TYPE_WD) + private String keyStoreType; + + @Value(CommonConstants.$KEYSTORE_PATH_WD) + private String keyStorePath; + + @Value(CommonConstants.$KEYSTORE_PASSWORD_WD) + private String keyStorePassword; + + @Value(CommonConstants.$KEY_PASSWORD_WD) + private String keyPassword; + + @Value(CommonConstants.$TRUSTSTORE_PATH_WD) + private String trustStorePath; + + @Value(CommonConstants.$TRUSTSTORE_PASSWORD_WD) + private String trustStorePassword; + + @Value(CoreCommonConstants.$USE_STRICT_SERVICE_DEFINITION_VERIFIER_WD) + private boolean useStrictServiceDefinitionVerifier; + + final String DIRECTION_KEY = "direction"; + final String DIRECTION_DEFAULT = CoreDefaults.DEFAULT_REQUEST_PARAM_DIRECTION_VALUE; + final String NULL_DEFAULT = null; + final String PAGE_KEY = "page"; + final int PAGE_DEFAULT = -1; + final String ID_KEY = "id"; + final int ID_DEFAULT = 0; + final String SIZE_KEY = "size"; + final int SIZE_DEFAULT = -1; + final String SORT_KEY = "sortField"; + final String SORT_DEFAULT = CoreCommonConstants.COMMON_FIELD_NAME_ID; + final String UNREGISTER_SERVICE_DEFINITION_KEY = "service_definition"; + final String UNREGISTER_SERVICE_PROVIDER_ADDRESS_KEY = "address"; + final String UNREGISTER_SERVICE_PROVIDER_PORT_KEY = "port"; + final String UNREGISTER_SERVICE_PROVIDER_SYSTEM_NAME_KEY = "system_name"; + + private static final String SERVICE_DEFINITION_REQUIREMENT_WRONG_FORMAT_ERROR_MESSAGE = "Service definition requirement has invalid format. Service definition only contains maximum 63 character of letters (english alphabet), numbers and dash (-), and has to start with a letter (also cannot ends with dash)."; + private static final String SYSTEM_NAME_NULL_ERROR_MESSAGE = " System name must have value "; + private static final String SYSTEM_NAME_WRONG_FORMAT_ERROR_MESSAGE = "System name has invalid format. System names only contain maximum 63 character of letters (english alphabet), numbers and dash (-), and have to start with a letter (also cannot end with dash)."; + private static final String SYSTEM_PORT_NULL_ERROR_MESSAGE = " System port must have value "; + + final String URL_PATH_ALL = "all"; + final String URL_PATH_GROUPED = "grouped"; + final String URL_PATH_ID = "id"; + final String URL_PATH_MGMT = "mgmt"; + final String URL_PATH_MULTI = "multi"; + final String URL_PATH_PULL_SYSTEMS = "pull-systems"; + final String URL_PATH_QUERY = "query"; + final String URL_PATH_REGISTER = "register"; + final String URL_PATH_REGISTER_SYSTEM = "register-system"; + final String URL_PATH_SERVICE_DEFINITION = "servicedef"; + final String URL_PATH_SERVICES = "services"; + final String URL_PATH_SYSTEM = "system"; + final String URL_PATH_SYSTEMS = "systems"; + final String URL_PATH_UNREGISTER = "unregister"; + final String URL_PATH_UNREGISTER_SYSTEM = "unregister-system"; + + // ================================================================================================= + // methods + // ------------------------------------------------------------------------------------------------- + @PostConstruct + public void init() { + logger.info("CoAP Protocol Init"); + + if (coapServerEnabled) { + logger.info("CoAP Protocol Enabled"); + CoapServerConfiguration coapServerConfiguration = new CoapServerConfiguration( + coapServerAddress, + coapServerPort, + serverSslEnabled, + new CoapCredentials( + keyStorePath, + keyStorePassword, + keyPassword, + "serviceregistry-coap"), + new CoapCertificates( + "coap-root", + trustStorePassword, + trustStorePath)); + coapServer = new AhCoapServer(coapServerConfiguration); + initializateResources(); + coapServer.start(); + } else { + logger.info("CoAP Protocol Disabled"); + } + } + + @PreDestroy + public void preDestroy() { + if (coapServerEnabled) { + logger.info("Killing CoAP Server"); + coapServer.stop(); + } + } + + // ================================================================================================= + // assistant methods + // -------------------------------------------------------------------------------------------------//------------------------------------------------------------------------------------------------- + private void initializateResources() { + coapServer.add(new EchoResource()); + coapServer.add(new QueryResource()); + coapServer.add(new RegisterResource()); + coapServer.add(new RegisterSystemResource()); + coapServer.add(new UnregisterResource()); + coapServer.add(new UnregisterSystemResource()); + } + + // ================================================================================================= + // CoAP resources + // ------------------------------------------------------------------------------------------------- + class EchoResource extends CoapResource { + + public EchoResource() { + super("echo"); + getAttributes().setTitle("Echo Resource"); + } + + @Override + public void handleGET(CoapExchange exchange) { + exchange.respond( + ResponseCode.CONTENT, + "Got it!", + MediaTypeRegistry.TEXT_PLAIN); + } + + } + + // ------------------------------------------------------------------------------------------------- + class QueryResource extends CoapResource { + + private final ObjectMapper mapper = new ObjectMapper(); + + public QueryResource() { + super(URL_PATH_QUERY); + add(new QueryMulti()); + getAttributes().setTitle("Query Resource"); + } + + @Override + public void handlePOST(CoapExchange exchange) { + try { + ServiceQueryFormDTO serviceQueryFormDTO = mapper.readValue(exchange.getRequestText(), + ServiceQueryFormDTO.class); + + if (Utilities.isEmpty(serviceQueryFormDTO.getServiceDefinitionRequirement())) { + throw new Exception("Service definition requirement is null or blank"); + } + exchange.respond( + ResponseCode.CONTENT, + mapper.writeValueAsString(serviceRegistryDBService.queryRegistry(serviceQueryFormDTO)), + MediaTypeRegistry.APPLICATION_JSON); + } catch (Exception ex) { + exchange.respond( + ResponseCode.INTERNAL_SERVER_ERROR, + ex.getMessage(), + MediaTypeRegistry.TEXT_PLAIN); + } + } + + } + + // ------------------------------------------------------------------------------------------------- + class QueryMulti extends CoapResource { + + private final ObjectMapper mapper = new ObjectMapper(); + + public QueryMulti() { + super(URL_PATH_MULTI); + getAttributes().setTitle("Multi Query Resource"); + } + + @Override + public void handlePOST(CoapExchange exchange) { + try { + ServiceQueryFormListDTO forms = mapper.readValue(exchange.getRequestText(), + ServiceQueryFormListDTO.class); + + String badRequestTxt = checkQueryFormList(forms); + if (badRequestTxt == null) { + + exchange.respond( + ResponseCode.CONTENT, + mapper.writeValueAsString(serviceRegistryDBService.multiQueryRegistry(forms)), + MediaTypeRegistry.APPLICATION_JSON); + } else { + exchange.respond( + ResponseCode.BAD_REQUEST, + badRequestTxt, + MediaTypeRegistry.TEXT_PLAIN); + } + + } catch (Exception ex) { + exchange.respond( + ResponseCode.INTERNAL_SERVER_ERROR, + ex.getMessage(), + MediaTypeRegistry.TEXT_PLAIN); + } + } + + } + + // ------------------------------------------------------------------------------------------------- + class RegisterResource extends CoapResource { + + private final ObjectMapper mapper = new ObjectMapper(); + + public RegisterResource() { + super(URL_PATH_REGISTER); + getAttributes().setTitle("Register Resource"); + } + + @Override + public void handlePOST(CoapExchange exchange) { + try { + + ServiceRegistryRequestDTO serviceRegistryRequestDTO = mapper.readValue(exchange.getRequestText(), + ServiceRegistryRequestDTO.class); + + if (Utilities.isEmpty(serviceRegistryRequestDTO.getServiceDefinition())) { + throw new Exception("Service definition is null or blank"); + } + + if (!Utilities.isEmpty(serviceRegistryRequestDTO.getEndOfValidity())) { + try { + Utilities.parseUTCStringToLocalZonedDateTime( + serviceRegistryRequestDTO.getEndOfValidity().trim()); + } catch (final DateTimeParseException ex) { + throw new Exception( + "End of validity is specified in the wrong format. Please provide UTC time using pattern."); + } + } + + ServiceSecurityType securityType = null; + if (serviceRegistryRequestDTO.getSecure() != null) { + for (final ServiceSecurityType type : ServiceSecurityType.values()) { + if (type.name().equalsIgnoreCase(serviceRegistryRequestDTO.getSecure())) { + securityType = type; + break; + } + } + + if (securityType == null) { + throw new Exception("Security type is not valid."); + } + } else { + securityType = ServiceSecurityType.NOT_SECURE; + } + + if (securityType != ServiceSecurityType.NOT_SECURE + && serviceRegistryRequestDTO.getProviderSystem().getAuthenticationInfo() == null) { + throw new Exception( + "Security type is in conflict with the availability of the authentication info."); + } + + exchange.respond( + ResponseCode.CONTENT, + mapper.writeValueAsString( + serviceRegistryDBService.registerServiceResponse(serviceRegistryRequestDTO)), + MediaTypeRegistry.APPLICATION_JSON); + } catch (Exception ex) { + exchange.respond( + ResponseCode.INTERNAL_SERVER_ERROR, + ex.getMessage(), + MediaTypeRegistry.TEXT_PLAIN); + } + } + + } + + // ------------------------------------------------------------------------------------------------- + class RegisterSystemResource extends CoapResource { + + private final ObjectMapper mapper = new ObjectMapper(); + + public RegisterSystemResource() { + super(URL_PATH_REGISTER_SYSTEM); + getAttributes().setTitle("Register System Resource"); + } + + @Override + public void handlePOST(CoapExchange exchange) { + try { + + SystemRequestDTO dto = mapper.readValue(exchange.getRequestText(), SystemRequestDTO.class); + callCreateSystem(exchange, dto); + } catch (Exception ex) { + exchange.respond( + ResponseCode.INTERNAL_SERVER_ERROR, + ex.getMessage(), + MediaTypeRegistry.TEXT_PLAIN); + } + } + + } + + // ------------------------------------------------------------------------------------------------- + class UnregisterResource extends CoapResource { + + public UnregisterResource() { + super(URL_PATH_UNREGISTER); + getAttributes().setTitle("Unegister Resource"); + } + + @Override + public void handleDELETE(CoapExchange exchange) { + try { + Map queries = CoapTools.getQueryParams(exchange); + + String serviceDefinition = CoapTools.getParam(queries, UNREGISTER_SERVICE_DEFINITION_KEY, NULL_DEFAULT); + String providerName = CoapTools.getParam(queries, UNREGISTER_SERVICE_PROVIDER_SYSTEM_NAME_KEY, + NULL_DEFAULT); + String providerAddress = CoapTools.getParam(queries, UNREGISTER_SERVICE_PROVIDER_ADDRESS_KEY, + NULL_DEFAULT); + int providerPort = CoapTools.getParam(queries, UNREGISTER_SERVICE_PROVIDER_PORT_KEY, 0); + + if (Utilities.isEmpty(serviceDefinition)) { + throw new Exception("Service definition is blank"); + } + + if (Utilities.isEmpty(providerName)) { + throw new Exception("Name of the provider system is blank"); + } + + if (Utilities.isEmpty(providerAddress)) { + throw new Exception("Address of the provider system is blank"); + } + + if (providerPort < CommonConstants.SYSTEM_PORT_RANGE_MIN + || providerPort > CommonConstants.SYSTEM_PORT_RANGE_MAX) { + throw new Exception("Port must be between " + CommonConstants.SYSTEM_PORT_RANGE_MIN + " and " + + CommonConstants.SYSTEM_PORT_RANGE_MAX + "."); + } + exchange.respond(ResponseCode.VALID); + } catch (Exception ex) { + exchange.respond( + ResponseCode.INTERNAL_SERVER_ERROR, + ex.getMessage(), + MediaTypeRegistry.TEXT_PLAIN); + } + } + + } + + // ------------------------------------------------------------------------------------------------- + class UnregisterSystemResource extends CoapResource { + + + public UnregisterSystemResource() { + super(URL_PATH_UNREGISTER_SYSTEM); + getAttributes().setTitle("Unegister Resource"); + } + + @Override + public void handleDELETE(CoapExchange exchange) { + try { + Map queries = CoapTools.getQueryParams(exchange); + + String systemName = CoapTools.getParam(queries, + CommonConstants.OP_SERVICEREGISTRY_UNREGISTER_REQUEST_PARAM_SYSTEM_NAME, NULL_DEFAULT); + String address = CoapTools.getParam(queries, + CommonConstants.OP_SERVICEREGISTRY_UNREGISTER_REQUEST_PARAM_ADDRESS, NULL_DEFAULT); + int port = CoapTools.getParam(queries, CommonConstants.OP_SERVICEREGISTRY_UNREGISTER_REQUEST_PARAM_PORT, + 0); + + try { + + final String checkedAddress = checkUnregisterSystemParameters(systemName, address, port); + serviceRegistryDBService.removeSystemByNameAndAddressAndPort(systemName, checkedAddress, port); + + } catch (BadPayloadException ex) { + exchange.respond( + ResponseCode.BAD_REQUEST, + ex.getMessage(), + MediaTypeRegistry.TEXT_PLAIN); + } + + exchange.respond(ResponseCode.VALID); + } catch (Exception ex) { + exchange.respond( + ResponseCode.INTERNAL_SERVER_ERROR, + ex.getMessage(), + MediaTypeRegistry.TEXT_PLAIN); + } + } + + } + + // ================================================================================================= + // assistant methods + // ------------------------------------------------------------------------------------------------- + private String checkQueryFormList(final ServiceQueryFormListDTO forms) { + + if (forms == null || forms.getForms() == null || forms.getForms().isEmpty()) { + return "Form list is null"; + } + + for (final ServiceQueryFormDTO form : forms.getForms()) { + if (form == null) { + return "A form is null"; + } + + if (Utilities.isEmpty(form.getServiceDefinitionRequirement())) { + return "Service definition requirement is null or blank"; + } + + if (useStrictServiceDefinitionVerifier && !cnVerifier.isValid(form.getServiceDefinitionRequirement())) { + return SERVICE_DEFINITION_REQUIREMENT_WRONG_FORMAT_ERROR_MESSAGE; + } + } + + return null; + } + + // ------------------------------------------------------------------------------------------------- + private void callCreateSystem(CoapExchange exchange, final SystemRequestDTO dto) { + ObjectMapper mapper = new ObjectMapper(); + + String badPayloadMessage = checkSystemRequest(dto, true); + if (badPayloadMessage != null) { + exchange.respond( + ResponseCode.BAD_REQUEST, + badPayloadMessage, + MediaTypeRegistry.APPLICATION_JSON); + return; + } + + final String systemName = dto.getSystemName().toLowerCase().trim(); + final String address = dto.getAddress().toLowerCase().trim(); + final int port = dto.getPort(); + final String authenticationInfo = dto.getAuthenticationInfo(); + final Map metadata = dto.getMetadata(); + + try { + exchange.respond( + ResponseCode.CONTENT, + mapper.writeValueAsString( + serviceRegistryDBService.createSystemResponse(systemName, address, port, authenticationInfo, + metadata)), + MediaTypeRegistry.APPLICATION_JSON); + } catch ( + + Exception ex) { + exchange.respond( + ResponseCode.INTERNAL_SERVER_ERROR, + ex.getMessage(), + MediaTypeRegistry.TEXT_PLAIN); + } + } + + // ------------------------------------------------------------------------------------------------- + private String checkSystemRequest(final SystemRequestDTO dto, + final boolean checkReservedCoreSystemNames) { + + if (dto == null) { + return "System is null."; + } + + if (Utilities.isEmpty(dto.getSystemName())) { + return SYSTEM_NAME_NULL_ERROR_MESSAGE; + } + + if (checkReservedCoreSystemNames) { + for (final CoreSystem coreSysteam : CoreSystem.values()) { + if (coreSysteam.name().equalsIgnoreCase(dto.getSystemName().trim())) { + return "System name '" + dto.getSystemName() + "' is a reserved arrowhead core system name."; + } + } + } + + if (!cnVerifier.isValid(dto.getSystemName())) { + return SYSTEM_NAME_WRONG_FORMAT_ERROR_MESSAGE; + } + + if (dto.getPort() == null) { + return SYSTEM_PORT_NULL_ERROR_MESSAGE; + } + + final int validatedPort = dto.getPort(); + if (validatedPort < CommonConstants.SYSTEM_PORT_RANGE_MIN + || validatedPort > CommonConstants.SYSTEM_PORT_RANGE_MAX) { + return "Port must be between " + CommonConstants.SYSTEM_PORT_RANGE_MIN + " and " + + CommonConstants.SYSTEM_PORT_RANGE_MAX; + } + + return null; + } + + // ------------------------------------------------------------------------------------------------- + private String checkUnregisterSystemParameters(final String systemName, final String address, final int port) { + + final String origin = CommonConstants.SERVICEREGISTRY_URI + + CommonConstants.OP_SERVICEREGISTRY_UNREGISTER_SYSTEM_URI; + if (Utilities.isEmpty(systemName)) { + throw new BadPayloadException("Name of the application system is blank", HttpStatus.SC_BAD_REQUEST, origin); + } + + if (!cnVerifier.isValid(systemName)) { + throw new BadPayloadException(SYSTEM_NAME_WRONG_FORMAT_ERROR_MESSAGE, HttpStatus.SC_BAD_REQUEST, origin); + } + + if (port < CommonConstants.SYSTEM_PORT_RANGE_MIN || port > CommonConstants.SYSTEM_PORT_RANGE_MAX) { + throw new BadPayloadException("Port must be between " + CommonConstants.SYSTEM_PORT_RANGE_MIN + " and " + + CommonConstants.SYSTEM_PORT_RANGE_MAX + ".", HttpStatus.SC_BAD_REQUEST, origin); + } + + return address; + } +} \ No newline at end of file diff --git a/serviceregistry/src/main/java/eu/arrowhead/core/serviceregistry/protocols/mqtt/MqttServiceRegistry.java b/serviceregistry/src/main/java/eu/arrowhead/core/serviceregistry/protocols/mqtt/MqttServiceRegistry.java new file mode 100644 index 000000000..670ae8642 --- /dev/null +++ b/serviceregistry/src/main/java/eu/arrowhead/core/serviceregistry/protocols/mqtt/MqttServiceRegistry.java @@ -0,0 +1,423 @@ +/******************************************************************************** + * Copyright (c) 2022 ThingWave AB + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * ThingWave - design and implementation + * Arrowhead Consortia - conceptualization + ********************************************************************************/ + +package eu.arrowhead.core.serviceregistry.protocols.mqtt; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.DeserializationFeature; + +import eu.arrowhead.common.CommonConstants; +import eu.arrowhead.common.CoreDefaults; +import eu.arrowhead.common.CoreCommonConstants; +import eu.arrowhead.common.SSLProperties; +import eu.arrowhead.common.CoreUtilities; +import eu.arrowhead.common.Utilities; +import eu.arrowhead.common.SslUtil; +import eu.arrowhead.common.exception.ArrowheadException; +import eu.arrowhead.common.core.CoreSystemService; +import eu.arrowhead.common.dto.internal.ServiceDefinitionRequestDTO; +import eu.arrowhead.common.dto.shared.ServiceQueryFormDTO; +import eu.arrowhead.common.dto.shared.ServiceQueryResultDTO; +import eu.arrowhead.common.dto.shared.ServiceRegistryRequestDTO; +import eu.arrowhead.common.dto.shared.ServiceSecurityType; +import eu.arrowhead.common.dto.shared.SystemRequestDTO; +import eu.arrowhead.common.dto.shared.MqttRequestDTO; +import eu.arrowhead.common.dto.shared.MqttResponseDTO; +import eu.arrowhead.core.serviceregistry.database.service.ServiceRegistryDBService; + +import java.nio.charset.StandardCharsets; +import java.time.format.DateTimeParseException; +import java.util.Map; +import javax.annotation.PostConstruct; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.domain.Sort.Direction; +import org.springframework.stereotype.Component; +import eu.arrowhead.common.exception.ArrowheadException; + +import org.eclipse.paho.client.mqttv3.MqttClient; +import org.eclipse.paho.client.mqttv3.MqttCallback; +import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; +import org.eclipse.paho.client.mqttv3.MqttConnectOptions; +import org.eclipse.paho.client.mqttv3.MqttException; +import org.eclipse.paho.client.mqttv3.MqttMessage; +import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; + +@Component +public class MqttServiceRegistry implements MqttCallback, Runnable { + + // ================================================================================================= + // members + private final Logger logger = LogManager.getLogger(MqttServiceRegistry.class); + + @Value(CoreCommonConstants.$ORCHESTRATOR_IS_GATEKEEPER_PRESENT_WD) + private boolean gatekeeperIsPresent; + + @Autowired + private ServiceRegistryDBService serviceRegistryDBService; + + @Value(CoreCommonConstants.$CORE_SYSTEM_NAME) + private String mqttSystemName; + + @Value(CoreCommonConstants.$MQTT_BROKER_ENABLED) + private boolean mqttBrokerEnabled; + + @Value(CoreCommonConstants.$MQTT_BROKER_ADDRESS) + private String mqttBrokerAddress; + + @Value(CoreCommonConstants.$MQTT_BROKER_PORT) + private int mqttBrokerPort; + + @Autowired + private SSLProperties sslProperties; + + @Value(CoreCommonConstants.$MQTT_BROKER_USERNAME) + private String mqttBrokerUsername; + + @Value(CoreCommonConstants.$MQTT_BROKER_PASSWORD) + private String mqttBrokerPassword; + + @Value(CoreCommonConstants.$MQTT_BROKER_CAFILE) + private String mqttBrokerCAFile; + + @Value(CoreCommonConstants.$MQTT_BROKER_CERTFILE) + private String mqttBrokerCertFile; + + @Value(CoreCommonConstants.$MQTT_BROKER_KEYFILE) + private String mqttBrokerKeyFile; + + @Value(CommonConstants.$SERVER_SSL_ENABLED_WD) + private boolean serverSslEnabled; + + final String DIRECTION_KEY = "direction"; + final String DIRECTION_DEFAULT = CoreDefaults.DEFAULT_REQUEST_PARAM_DIRECTION_VALUE; + final String NULL_DEFAULT = null; + final String PAGE_KEY = "page"; + final int PAGE_DEFAULT = -1; + final String ID_KEY = "id"; + final int ID_DEFAULT = 0; + final String SIZE_KEY = "size"; + final int SIZE_DEFAULT = -1; + final String SORT_KEY = "sortField"; + final String SORT_DEFAULT = CoreCommonConstants.COMMON_FIELD_NAME_ID; + final String UNREGISTER_SERVICE_DEFINITION_KEY = "service_definition"; + final String UNREGISTER_SERVICE_PROVIDER_ADDRESS_KEY = "address"; + final String UNREGISTER_SERVICE_PROVIDER_PORT_KEY = "port"; + final String UNREGISTER_SERVICE_PROVIDER_SYSTEM_NAME_KEY = "system_name"; + + final String ECHO_TOPIC = "ah/serviceregistry/echo"; + final String REGISTER_TOPIC = "ah/serviceregistry/register"; + final String UNREGISTER_TOPIC = "ah/serviceregistry/unregister"; + final String QUERY_TOPIC = "ah/serviceregistry/query"; + + Thread t = null; + ObjectMapper mapper; + + // ================================================================================================= + // methods + // ------------------------------------------------------------------------------------------------- + @PostConstruct + public void init() { + + if (mqttBrokerEnabled) { + logger.info("Starting MQTT protocol"); + + if(Utilities.isEmpty(mqttBrokerUsername) || Utilities.isEmpty(mqttBrokerPassword)) { + logger.info("Missing MQTT broker username or password! Using anonymoues login."); + } + + mapper = new ObjectMapper(); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + + t = new Thread(this); + t.start(); + } + } + + MqttClient client = null; + MemoryPersistence persistence = null; + + private void connectBroker() { + + try { + MqttConnectOptions connOpts = new MqttConnectOptions(); + connOpts.setCleanSession(true); + connOpts.setUserName(mqttBrokerUsername); + connOpts.setPassword(mqttBrokerPassword.toCharArray()); + + connOpts.setConnectionTimeout(60); + connOpts.setKeepAliveInterval(60); + connOpts.setMqttVersion(MqttConnectOptions.MQTT_VERSION_3_1); + + if (sslProperties.isSslEnabled()) { + try { + SSLSocketFactory socketFactory = null; + socketFactory = SslUtil.getSSLSocketFactory(sslProperties); + connOpts.setSocketFactory(socketFactory); + } catch (Exception e) { + logger.info("Could not load certificate: " + e.toString()); + } + } + + client.setCallback(this); + client.connect(connOpts); + + String topics[] = { ECHO_TOPIC, REGISTER_TOPIC, UNREGISTER_TOPIC, QUERY_TOPIC }; + client.subscribe(topics); + } catch (MqttException mex) { + logger.info("Could not connect to MQTT broker!\n\t" + mex.toString()); + } + + } + + @Override + public void run() { + + while (true) { + try { + if (client == null) { + persistence = new MemoryPersistence(); + if (!Utilities.isEmpty(mqttBrokerCAFile) && !Utilities.isEmpty(mqttBrokerCertFile) && !Utilities.isEmpty(mqttBrokerKeyFile)) { + client = new MqttClient("ssl://" + mqttBrokerAddress + ":" + mqttBrokerPort, mqttSystemName, persistence); + } else { + client = new MqttClient("tcp://" + mqttBrokerAddress + ":" + mqttBrokerPort, mqttSystemName, persistence); + } + } + if (!client.isConnected()) { + connectBroker(); + } + Thread.sleep(1000 * 15); + } catch (InterruptedException iex) { + logger.info("Error starting MQTT timeout thread"); + } catch (MqttException mex) { + logger.info("MQTT error: " + mex.toString()); + } + } + + } + + @Override + public void connectionLost(Throwable cause) { + logger.info("Connection lost to MQTT broker"); + client = null; + } + + @Override + public void messageArrived(String topic, MqttMessage message) { + MqttRequestDTO request = null; + MqttResponseDTO response = null; + + try { + // request = Utilities.fromJson(message.toString(), MqttRequestDTO.class); + request = mapper.readValue(message.toString(), MqttRequestDTO.class); + } catch (Exception ae) { + logger.info("Could not convert MQTT message to REST request!"); + return; + } + + logger.info(request.toString()); + + switch (topic) { + case ECHO_TOPIC: + + handleEcho(topic, message, request); + return; + + case REGISTER_TOPIC: + + handleRegister(topic, message, request); + return; + case UNREGISTER_TOPIC: + + handleUnregister(topic, message, request); + return; + case QUERY_TOPIC: + + handleQuery(topic, message, request); + return; + default: + logger.info("Received message to unsupported topic"); + } + + try { + MqttResponseDTO srResponse = new MqttResponseDTO("400", null, null); + String respJson = mapper.convertValue(srResponse, String.class); + MqttMessage resp = new MqttMessage(respJson.getBytes()); + resp.setQos(2); + client.publish(request.getReplyTo(), resp); + } catch (Exception me) { + logger.info("Could not reply: " + me.toString()); + } + } + + @Override + public void deliveryComplete(IMqttDeliveryToken token) { + } + + //------------------------------------------------------------------------------------------------- + private boolean handleEcho(String topic, MqttMessage message, MqttRequestDTO request) { + logger.info(request.getMethod() + " echo(): " + new String(message.getPayload(), StandardCharsets.UTF_8)); + if (!request.getMethod().toLowerCase().equals("get")) { + return false; + } + + try { + MqttResponseDTO response = new MqttResponseDTO("200", "text/plain", new String("Got it")); + MqttMessage resp = new MqttMessage(Utilities.toJson(response).getBytes()); + resp.setQos(2); + client.publish(request.getReplyTo(), resp); + return true; + } catch (MqttException mex) { + logger.info("echo(): Couldn't reply " + mex.toString()); + } + return false; + } + + //------------------------------------------------------------------------------------------------- + private boolean handleRegister(String topic, MqttMessage message, MqttRequestDTO request) { + MqttResponseDTO response = null; + + try { + logger.info("register(): " + new String(message.getPayload(), StandardCharsets.UTF_8)); + if (!request.getMethod().toLowerCase().equals("post")) { + return false; + } + + ServiceRegistryRequestDTO serviceRegistryRequestDTO = mapper.convertValue(request.getPayload(), ServiceRegistryRequestDTO.class); + + if (Utilities.isEmpty(serviceRegistryRequestDTO.getServiceDefinition())) { + throw new Exception("Service definition is null or blank"); + } + + if (!Utilities.isEmpty(serviceRegistryRequestDTO.getEndOfValidity())) { + try { + Utilities.parseUTCStringToLocalZonedDateTime(serviceRegistryRequestDTO.getEndOfValidity().trim()); + } catch (final DateTimeParseException ex) { + throw new Exception("End of validity is specified in the wrong format. Please provide UTC time using an ISO 8601 pattern."); + } + } + + ServiceSecurityType securityType = null; + if (serviceRegistryRequestDTO.getSecure() != null) { + for (final ServiceSecurityType type : ServiceSecurityType.values()) { + if (type.name().equalsIgnoreCase(serviceRegistryRequestDTO.getSecure())) { + securityType = type; + break; + } + } + + if (securityType == null) { + throw new Exception("Security type is not valid."); + } + } else { + securityType = ServiceSecurityType.NOT_SECURE; + } + + if (securityType != ServiceSecurityType.NOT_SECURE && serviceRegistryRequestDTO.getProviderSystem().getAuthenticationInfo() == null) { + throw new Exception("Security type is in conflict with the availability of the authentication info."); + } + + logger.info("SRREQ:: " + serviceRegistryRequestDTO.toString()); + response = new MqttResponseDTO("200", "application/json", null); + response.setPayload(serviceRegistryDBService.registerServiceResponse(serviceRegistryRequestDTO)); + MqttMessage resp = new MqttMessage(mapper.writeValueAsString(response).getBytes()); + resp.setQos(2); + client.publish(request.getReplyTo(), resp); + return true; + + } catch (Exception e) { + logger.info("Could not register: " + e.toString()); + return false; + } + } + + //------------------------------------------------------------------------------------------------- + private boolean handleUnregister(String topic, MqttMessage message, MqttRequestDTO request) { + MqttResponseDTO response = null; + + logger.info("unregister(): " + message.toString()); + if (!request.getMethod().equalsIgnoreCase("delete")) { + return false; + } + + try { + String serviceDefinition = request.getQueryParameters().get("serviceDefinition"); + String providerName = request.getQueryParameters().get("providerName"); + String providerAddress = request.getQueryParameters().get("providerAddress"); + int providerPort = Integer.parseInt(request.getQueryParameters().get("providerPort")); + String serviceUri = request.getQueryParameters().get("serviceUri"); + + if (Utilities.isEmpty(serviceDefinition)) { + throw new Exception("Service definition is blank"); + } + + if (Utilities.isEmpty(providerName)) { + throw new Exception("Name of the provider system is blank"); + } + + if (Utilities.isEmpty(providerAddress)) { + throw new Exception("Address of the provider system is blank"); + } + + if (providerPort < CommonConstants.SYSTEM_PORT_RANGE_MIN || providerPort > CommonConstants.SYSTEM_PORT_RANGE_MAX) { + throw new Exception("Port must be between " + CommonConstants.SYSTEM_PORT_RANGE_MIN + " and " + CommonConstants.SYSTEM_PORT_RANGE_MAX + "."); + } + serviceRegistryDBService.removeServiceRegistry(serviceDefinition, providerName, providerAddress, providerPort, serviceUri); + logger.debug("{} successfully removed its service {} ({})", providerName, serviceDefinition, serviceUri); + + return true; + } catch (Exception e) { + logger.info("illegal request: " + e.toString()); + return false; + } + } + + //------------------------------------------------------------------------------------------------- + private boolean handleQuery(String topic, MqttMessage message, MqttRequestDTO request) { + MqttResponseDTO response = null; + + logger.debug("Service query request received"); + //logger.info("query(): " + message.toString()); + if (!request.getMethod().toLowerCase().equals("post")) { + return false; + } + + try { + ServiceQueryFormDTO form = mapper.convertValue(request.getPayload(), ServiceQueryFormDTO.class); + + if (Utilities.isEmpty(form.getServiceDefinitionRequirement())) { + throw new Exception("Service definition requirement is null or blank"); + } + + //logger.info("SRQUERY:: " + serviceQueryFormDTO.toString()); + response = new MqttResponseDTO("200", "application/json", null); + final ServiceQueryResultDTO result = serviceRegistryDBService.queryRegistry(form); + logger.debug("Return {} providers for service {}", result.getServiceQueryData().size(), form.getServiceDefinitionRequirement()); + response.setPayload(result); + MqttMessage resp = new MqttMessage(mapper.writeValueAsString(response).getBytes()); + resp.setQos(2); + client.publish(request.getReplyTo(), resp); + return true; + } catch (Exception e) { + return false; + } + + } +} diff --git a/serviceregistry/src/main/java/eu/arrowhead/core/serviceregistry/protocols/mqtt/README.md b/serviceregistry/src/main/java/eu/arrowhead/core/serviceregistry/protocols/mqtt/README.md new file mode 100644 index 000000000..36418b3f7 --- /dev/null +++ b/serviceregistry/src/main/java/eu/arrowhead/core/serviceregistry/protocols/mqtt/README.md @@ -0,0 +1,47 @@ + +## MQTT Setup + +### Installation +On Debian-based systems, install the Mosquitto MQTT broker with the following command: +``` +> sudo apt-get install mosquitto mosquitto-clients +``` + +### Configuration +To setup username and password for all clients, first create a password file: +``` +> sudo touch /etc/mosquitto/passwords.dat +``` + +Then add user(s) for each core system: +``` +> sudo mosquitto_password /etc/mosquitto/passwords.dat serviceregistry secretpassword +``` + +Remember to use strong username and password combinations. +Then add a custom configuration to Mosquitto to disable anonymous logins. Edit /etc/mosquitto/conf.d/broker.conf and add the following lines: +``` +allow_anonymous false +password_file /etc/mosquitto/passwords.dat +``` + +Restart the broker with: +``` +> sudo service mosquitto restart +``` + +Edit application.properties in the target/ folder and make sure these lines are there (edit username and password to match prev. steps): + +``` +# MQTT broker parameters +mqtt.broker.enabled=true +mqtt.broker.address=127.0.0.1 +mqtt.broker.port=1883 +mqtt.broker.username=serviceregistry +mqtt.broker.password=secretpassword +``` + +## Testing +The Mosquitto broker stores log entries in /var/log/mosquitto/mosquitto.log +Here a user can see login attempts etc. + diff --git a/serviceregistry/src/main/java/eu/arrowhead/core/serviceregistry/security/SRAccessControlFilter.java b/serviceregistry/src/main/java/eu/arrowhead/core/serviceregistry/security/SRAccessControlFilter.java index 9a02dd0b2..29a33ceb9 100644 --- a/serviceregistry/src/main/java/eu/arrowhead/core/serviceregistry/security/SRAccessControlFilter.java +++ b/serviceregistry/src/main/java/eu/arrowhead/core/serviceregistry/security/SRAccessControlFilter.java @@ -40,7 +40,7 @@ public class SRAccessControlFilter extends CoreSystemAccessControlFilter { private static final CoreSystem[] allowedCoreSystemsForQuery = { CoreSystem.ORCHESTRATOR, CoreSystem.GATEKEEPER, CoreSystem.CERTIFICATEAUTHORITY, CoreSystem.EVENTHANDLER, CoreSystem.AUTHORIZATION, CoreSystem.QOSMONITOR, CoreSystem.ONBOARDINGCONTROLLER, CoreSystem.DEVICEREGISTRY, - CoreSystem.SYSTEMREGISTRY, CoreSystem.PLANTDESCRIPTIONENGINE, CoreSystem.CHOREOGRAPHER }; + CoreSystem.SYSTEMREGISTRY, CoreSystem.PLANTDESCRIPTIONENGINE, CoreSystem.CHOREOGRAPHER, CoreSystem.HAWKBITCONFIGURATIONMANAGER }; private static final CoreSystem[] allowedCoreSystemsForQueryBySystemId = { CoreSystem.ORCHESTRATOR }; private static final CoreSystem[] allowedCoreSystemsForQueryBySystemDTO = { CoreSystem.ORCHESTRATOR }; private static final CoreSystem[] allowedCoreSystemsForQueryAll = { CoreSystem.QOSMONITOR, CoreSystem.GATEKEEPER }; diff --git a/serviceregistry/src/main/resources/application.properties b/serviceregistry/src/main/resources/application.properties index 55c606ed4..1659eca83 100644 --- a/serviceregistry/src/main/resources/application.properties +++ b/serviceregistry/src/main/resources/application.properties @@ -21,6 +21,18 @@ server.port=8443 domain.name=127.0.0.1 domain.port=8443 +# CoAP server parameters +coap.server.enabled=false +coap.server.address=0.0.0.0 +coap.server.port=5683 + +# MQTT broker parameters +mqtt.broker.enabled=false +mqtt.broker.address=127.0.0.1 +mqtt.broker.port=8883 +mqtt.broker.username=serviceregistry +mqtt.broker.password=secretpassword + ############################################ ### CUSTOM PARAMETERS ### ############################################ diff --git a/systemregistry/.project b/systemregistry/.project index b76eb03f2..3c3c6f0f7 100644 --- a/systemregistry/.project +++ b/systemregistry/.project @@ -10,6 +10,11 @@ + + org.springframework.ide.eclipse.boot.validation.springbootbuilder + + + org.eclipse.m2e.core.maven2Builder diff --git a/translator/pom.xml b/translator/pom.xml index 2970d6f28..223974932 100644 --- a/translator/pom.xml +++ b/translator/pom.xml @@ -5,45 +5,138 @@ core ${revision} - + arrowhead-translator jar Arrowhead Translator Arrowhead Translator core service - + eu.arrowhead arrowhead-core-common ${revision} + + org.eclipse.jetty + jetty-server + 9.4.45.v20220203 + jar + + + + org.eclipse.jetty + jetty-client + 9.4.45.v20220203 + jar + + + + org.eclipse.jetty + jetty-servlet + 9.4.45.v20220203 + + + + org.eclipse.jetty + jetty-util + 9.4.45.v20220203 + + + + org.eclipse.jetty + jetty-io + 9.4.45.v20220203 + + + + org.eclipse.jetty + jetty-http + 9.4.45.v20220203 + + + + org.eclipse.jetty + jetty-webapp + 9.4.45.v20220203 + jar + + + + org.eclipse.jetty.websocket + websocket-server + 9.4.45.v20220203 + jar + + + org.eclipse.jetty.websocket + websocket-api + 9.4.45.v20220203 + provided + + + + + org.eclipse.jetty.websocket + websocket-servlet + 9.4.45.v20220203 + provided + + + + org.eclipse.jetty.websocket + websocket-client + 9.4.45.v20220203 + provided + + + + org.eclipse.jetty.websocket + websocket-common + 9.4.45.v20220203 + + + + org.eclipse.paho + org.eclipse.paho.client.mqttv3 + 1.2.5 + jar + + + + commons-io + commons-io + 2.11.0 + jar + + org.eclipse.californium californium-core - 2.0.0-M12 - - - commons-net - commons-net - 3.6 + 2.7.0 + + - org.apache.httpcomponents - httpcore - 4.4.10 + org.json + json + 20180130 + jar - org.apache.httpcomponents - httpcore-nio - 4.4.10 + com.upokecenter + cbor + 4.5.2 - org.apache.httpcomponents - httpclient - 4.5.6 + com.moandjiezana.toml + toml4j + 0.7.2 + jar - - + + + @@ -72,7 +165,7 @@ - + jdeb org.vafer @@ -99,13 +192,13 @@ 755 - + link /usr/share/arrowhead/translator/${project.artifactId}.jar /usr/share/arrowhead/translator/${project.build.finalName}.jar - + file ${project.build.directory}/log4j2.xml @@ -115,7 +208,7 @@ 644 - + file ${project.build.directory}/application.properties @@ -125,13 +218,13 @@ 644 - + link /usr/share/arrowhead/translator/application.properties /etc/arrowhead/systems/translator/application.properties - + file ${project.basedir}/src/deb/arrowhead-translator.service @@ -141,7 +234,7 @@ 644 - + file ${project.basedir}/../scripts/translator_privileges.sql diff --git a/translator/src/deb/control/control b/translator/src/deb/control/control index 9ca2a4bc3..ad0222b98 100644 --- a/translator/src/deb/control/control +++ b/translator/src/deb/control/control @@ -3,7 +3,7 @@ Version: [[version]] Section: contrib/java Priority: optional Architecture: all -Maintainer: Pablo Puñal Pereira +Maintainer: Pablo Puñal Pereira Homepage: http://www.arrowhead.eu Description: Arrowhead Translator System Distribution: development diff --git a/translator/src/main/java/eu/arrowhead/core/translator/TranslatorApplicationInitListener.java b/translator/src/main/java/eu/arrowhead/core/translator/TranslatorApplicationInitListener.java index 9747c4c43..85af60b9a 100644 --- a/translator/src/main/java/eu/arrowhead/core/translator/TranslatorApplicationInitListener.java +++ b/translator/src/main/java/eu/arrowhead/core/translator/TranslatorApplicationInitListener.java @@ -1,53 +1,48 @@ package eu.arrowhead.core.translator; -import java.util.List; - - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.stereotype.Component; import eu.arrowhead.common.ApplicationInitListener; -import eu.arrowhead.common.core.CoreSystemService; import eu.arrowhead.core.translator.services.fiware.FiwareService; import eu.arrowhead.core.translator.services.translator.TranslatorService; @Component public class TranslatorApplicationInitListener extends ApplicationInitListener { - - //================================================================================================= + + // ================================================================================================= // members private TranslatorService translatorService; - private FiwareService fiwareService; - + private FiwareService fiwareService; + @Autowired private ApplicationContext applicationContext; - - //================================================================================================= + + // ================================================================================================= // methods - - //------------------------------------------------------------------------------------------------- - - - //================================================================================================= + + // ------------------------------------------------------------------------------------------------- + + // ================================================================================================= // assistant methods - //------------------------------------------------------------------------------------------------- + // ------------------------------------------------------------------------------------------------- @Override protected void customInit(final ContextRefreshedEvent event) { logger.debug("customInit started..."); - translatorService = applicationContext.getBean(TranslatorService.class); - translatorService.start(); - fiwareService = applicationContext.getBean(FiwareService.class); - fiwareService.start(); + translatorService = applicationContext.getBean(TranslatorService.class); + translatorService.start(); + fiwareService = applicationContext.getBean(FiwareService.class); + fiwareService.start(); } - - //------------------------------------------------------------------------------------------------- + + // ------------------------------------------------------------------------------------------------- @Override protected void customDestroy() { logger.debug("customDestroy started..."); - fiwareService.unregisterAll(); + fiwareService.unregisterAll(); } - + } \ No newline at end of file diff --git a/translator/src/main/java/eu/arrowhead/core/translator/TranslatorController.java b/translator/src/main/java/eu/arrowhead/core/translator/TranslatorController.java index 0b70806ad..85c67ae2a 100644 --- a/translator/src/main/java/eu/arrowhead/core/translator/TranslatorController.java +++ b/translator/src/main/java/eu/arrowhead/core/translator/TranslatorController.java @@ -14,6 +14,7 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import eu.arrowhead.common.CommonConstants; @@ -24,9 +25,8 @@ import eu.arrowhead.core.translator.services.fiware.common.FiwareEntity; import eu.arrowhead.core.translator.services.fiware.common.FiwareUrlServices; import eu.arrowhead.core.translator.services.translator.TranslatorService; -import eu.arrowhead.core.translator.services.translator.common.TranslatorSetup; -import eu.arrowhead.core.translator.services.translator.common.TranslatorHubAccess; - +import eu.arrowhead.core.translator.services.translator.common.TranslatorHubDTO; +import eu.arrowhead.core.translator.services.translator.common.TranslatorSetupDTO; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiResponse; @@ -38,337 +38,314 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestHeader; -@Api(tags = {CoreCommonConstants.SWAGGER_TAG_ALL}) -@CrossOrigin(maxAge = Defaults.CORS_MAX_AGE, allowCredentials = Defaults.CORS_ALLOW_CREDENTIALS, - allowedHeaders = {HttpHeaders.ORIGIN, HttpHeaders.CONTENT_TYPE, HttpHeaders.ACCEPT, HttpHeaders.AUTHORIZATION} -) +@Api(tags = { CoreCommonConstants.SWAGGER_TAG_ALL }) +@CrossOrigin(maxAge = Defaults.CORS_MAX_AGE, allowCredentials = Defaults.CORS_ALLOW_CREDENTIALS, allowedHeaders = { + HttpHeaders.ORIGIN, HttpHeaders.CONTENT_TYPE, HttpHeaders.ACCEPT, HttpHeaders.AUTHORIZATION }) @RestController @RequestMapping(CommonConstants.TRANSLATOR_URI) public class TranslatorController { - //================================================================================================= - // members - private final Logger logger = LogManager.getLogger(TranslatorController.class); + // ================================================================================================= + // members + private final Logger logger = LogManager.getLogger(TranslatorController.class); - private static final String MEDIA_TYPE_APPLICATION_SENML = "application/senml+json"; - - private static final String TRANSLATOR_FIWARE_SERVICES_URL_ENTITIES = "/translator/v2/entities"; - private static final String TRANSLATOR_FIWARE_SERVICES_URL_TYPES = "/translator/v2/types"; + private static final String MEDIA_TYPE_APPLICATION_SENML = "application/senml+json"; - private static final String PATH_VARIABLE_ID = "id"; - private static final String PATH_ENTITY_ID = "entityId"; - private static final String PATH_ENTITY_TYPE = "entityType"; - private static final String PATH_ATTRIBUTE_NAME = "attrName"; - private static final String PATH_SERVICE_NAME = "serviceName"; + private static final String TRANSLATOR_FIWARE_SERVICES_URL_ENTITIES = "/translator/v2/entities"; + private static final String TRANSLATOR_FIWARE_SERVICES_URL_TYPES = "/translator/v2/types"; - private static final String PATH_TRANSLATOR_ROOT = "/"; - private static final String PATH_TRANSLATOR_ALL = "/all"; - private static final String PATH_TRANSLATOR_BY_ID = "/{" + PATH_VARIABLE_ID + "}"; + private static final String PATH_VARIABLE_ID = "id"; + private static final String PATH_ENTITY_ID = "entityId"; + private static final String PATH_ENTITY_TYPE = "entityType"; + private static final String PATH_ATTRIBUTE_NAME = "attrName"; + private static final String PATH_SERVICE_NAME = "serviceName"; - private static final String PATH_TRANSLATOR_PLUGIN_ENTITY_AND_SERVICE = "/plugin/service/{" + PATH_ENTITY_ID + "}/{" + PATH_SERVICE_NAME + "}"; + private static final String PATH_NEW_TRANSLATOR_ROOT = "/"; + private static final String PATH_NEW_TRANSLATOR_BY_ID = PATH_NEW_TRANSLATOR_ROOT + "{" + PATH_VARIABLE_ID + "}"; + private static final String PATH_NEW_TRANSLATOR_ALL = PATH_NEW_TRANSLATOR_ROOT + "all"; - private static final String PATH_TRANSLATOR_FIWARE_ROOT = "/v2"; - private static final String PATH_TRANSLATOR_FIWARE_ENTITIES = PATH_TRANSLATOR_FIWARE_ROOT + "/entities"; - private static final String PATH_TRANSLATOR_FIWARE_ENTITIES_BY_ID = PATH_TRANSLATOR_FIWARE_ROOT + "/entities/{" + PATH_ENTITY_ID + "}"; - private static final String PATH_TRANSLATOR_FIWARE_ENTITIES_ATTRIBUTES_BY_ID = PATH_TRANSLATOR_FIWARE_ENTITIES_BY_ID + "/attrs"; - private static final String PATH_TRANSLATOR_FIWARE_ENTITIES_ATTRIBUTES_BY_ID_AND_ATTRIBUTE = PATH_TRANSLATOR_FIWARE_ENTITIES_ATTRIBUTES_BY_ID + "/{" + PATH_ATTRIBUTE_NAME + "}"; - private static final String PATH_TRANSLATOR_FIWARE_ENTITIES_ATTRIBUTE_VALUE_BY_ID_AND_ATTRIBUTE = PATH_TRANSLATOR_FIWARE_ENTITIES_ATTRIBUTES_BY_ID_AND_ATTRIBUTE + "/value"; - private static final String PATH_TRANSLATOR_FIWARE_TYPES = PATH_TRANSLATOR_FIWARE_ROOT + "/types"; - private static final String PATH_TRANSLATOR_FIWARE_TYPES_BY_TYPE = PATH_TRANSLATOR_FIWARE_TYPES + "/{" + PATH_ENTITY_TYPE + "}"; - private static final String PATH_TRANSLATOR_FIWARE_NOTIFICATION = PATH_TRANSLATOR_FIWARE_ROOT + "/notification"; + private static final String PATH_TRANSLATOR_PLUGIN_ENTITY_AND_SERVICE = "/plugin/service/{" + PATH_ENTITY_ID + + "}/{" + PATH_SERVICE_NAME + "}"; - @Autowired - private FiwareService fiwareService; - @Autowired - private TranslatorService translatorService; + private static final String PATH_TRANSLATOR_FIWARE_ROOT = "/v2"; + private static final String PATH_TRANSLATOR_FIWARE_ENTITIES = PATH_TRANSLATOR_FIWARE_ROOT + "/entities"; + private static final String PATH_TRANSLATOR_FIWARE_ENTITIES_BY_ID = PATH_TRANSLATOR_FIWARE_ROOT + "/entities/{" + + PATH_ENTITY_ID + "}"; + private static final String PATH_TRANSLATOR_FIWARE_ENTITIES_ATTRIBUTES_BY_ID = PATH_TRANSLATOR_FIWARE_ENTITIES_BY_ID + + "/attrs"; + private static final String PATH_TRANSLATOR_FIWARE_ENTITIES_ATTRIBUTES_BY_ID_AND_ATTRIBUTE = PATH_TRANSLATOR_FIWARE_ENTITIES_ATTRIBUTES_BY_ID + + "/{" + PATH_ATTRIBUTE_NAME + "}"; + private static final String PATH_TRANSLATOR_FIWARE_ENTITIES_ATTRIBUTE_VALUE_BY_ID_AND_ATTRIBUTE = PATH_TRANSLATOR_FIWARE_ENTITIES_ATTRIBUTES_BY_ID_AND_ATTRIBUTE + + "/value"; + private static final String PATH_TRANSLATOR_FIWARE_TYPES = PATH_TRANSLATOR_FIWARE_ROOT + "/types"; + private static final String PATH_TRANSLATOR_FIWARE_TYPES_BY_TYPE = PATH_TRANSLATOR_FIWARE_TYPES + "/{" + + PATH_ENTITY_TYPE + "}"; + private static final String PATH_TRANSLATOR_FIWARE_NOTIFICATION = PATH_TRANSLATOR_FIWARE_ROOT + "/notification"; - //================================================================================================= - // methods - //------------------------------------------------------------------------------------------------- - @ApiOperation(value = "Return an echo message with the purpose of testing the core service availability", response = String.class, tags = {CoreCommonConstants.SWAGGER_TAG_CLIENT}) - @ApiResponses(value = { - @ApiResponse(code = HttpStatus.SC_OK, message = CoreCommonConstants.SWAGGER_HTTP_200_MESSAGE), - @ApiResponse(code = HttpStatus.SC_UNAUTHORIZED, message = CoreCommonConstants.SWAGGER_HTTP_401_MESSAGE), - @ApiResponse(code = HttpStatus.SC_INTERNAL_SERVER_ERROR, message = CoreCommonConstants.SWAGGER_HTTP_500_MESSAGE) - }) - @GetMapping(CommonConstants.ECHO_URI) - @ResponseBody - public String echoService() { - return "Got it!"; - } + @Autowired + private FiwareService fiwareService; - //------------------------------------------------------------------------------------------------- - @ApiOperation(value = "Simple test method to see if the http server where this resource is registered works or not", response = String.class, tags = {CoreCommonConstants.SWAGGER_TAG_CLIENT}) - @ApiResponses(value = { - @ApiResponse(code = HttpStatus.SC_OK, message = CoreCommonConstants.SWAGGER_HTTP_200_MESSAGE), - @ApiResponse(code = HttpStatus.SC_UNAUTHORIZED, message = CoreCommonConstants.SWAGGER_HTTP_401_MESSAGE), - @ApiResponse(code = HttpStatus.SC_INTERNAL_SERVER_ERROR, message = CoreCommonConstants.SWAGGER_HTTP_500_MESSAGE) - }) - @GetMapping(path = PATH_TRANSLATOR_ROOT, produces = MediaType.TEXT_PLAIN_VALUE) - @ResponseBody - public String getIt() { - return "This is the Translator Arrowhead Core System"; - } + @Autowired + private TranslatorService translatorService; - //------------------------------------------------------------------------------------------------- - @ApiOperation(value = "This method initiates the creation of a new translation hub, if none exists already, between two systems.", response = TranslatorHubAccess.class, tags = {CoreCommonConstants.SWAGGER_TAG_CLIENT}) - @ApiResponses(value = { - @ApiResponse(code = HttpStatus.SC_OK, message = CoreCommonConstants.SWAGGER_HTTP_200_MESSAGE), - @ApiResponse(code = HttpStatus.SC_BAD_REQUEST, message = CoreCommonConstants.SWAGGER_HTTP_400_MESSAGE), - @ApiResponse(code = HttpStatus.SC_UNAUTHORIZED, message = CoreCommonConstants.SWAGGER_HTTP_401_MESSAGE), - @ApiResponse(code = HttpStatus.SC_INTERNAL_SERVER_ERROR, message = CoreCommonConstants.SWAGGER_HTTP_500_MESSAGE) - }) - @PostMapping(path = PATH_TRANSLATOR_ROOT, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - public TranslatorHubAccess postTranslator(@RequestBody final TranslatorSetup setup, @RequestHeader("host") String hostPort) { - String host = hostPort.contains(":")?hostPort.substring(0, hostPort.indexOf(":")):hostPort; + // ================================================================================================= + // methods + // ------------------------------------------------------------------------------------------------- + @ApiOperation(value = "Return an echo message with the purpose of testing the core service availability", response = String.class, tags = { + CoreCommonConstants.SWAGGER_TAG_CLIENT }) + @ApiResponses(value = { + @ApiResponse(code = HttpStatus.SC_OK, message = CoreCommonConstants.SWAGGER_HTTP_200_MESSAGE), + @ApiResponse(code = HttpStatus.SC_UNAUTHORIZED, message = CoreCommonConstants.SWAGGER_HTTP_401_MESSAGE), + @ApiResponse(code = HttpStatus.SC_INTERNAL_SERVER_ERROR, message = CoreCommonConstants.SWAGGER_HTTP_500_MESSAGE) }) + @GetMapping(CommonConstants.ECHO_URI) + @ResponseBody + public String echoService() { + return "Got it!"; + } - try { - return translatorService.createTranslationHub(setup,host); - } catch (Exception ex) { - logger.warn("Exception: " + ex.getLocalizedMessage()); - throw new ArrowheadException(ex.getLocalizedMessage(), HttpStatus.SC_BAD_REQUEST); + // ------------------------------------------------------------------------------------------------- + @ApiOperation(value = "Simple test method to see if the http server where this resource is registered works or not", response = String.class, tags = { + CoreCommonConstants.SWAGGER_TAG_CLIENT }) + @ApiResponses(value = { + @ApiResponse(code = HttpStatus.SC_OK, message = CoreCommonConstants.SWAGGER_HTTP_200_MESSAGE), + @ApiResponse(code = HttpStatus.SC_UNAUTHORIZED, message = CoreCommonConstants.SWAGGER_HTTP_401_MESSAGE), + @ApiResponse(code = HttpStatus.SC_INTERNAL_SERVER_ERROR, message = CoreCommonConstants.SWAGGER_HTTP_500_MESSAGE) }) + @GetMapping(path = PATH_NEW_TRANSLATOR_ROOT, produces = MediaType.TEXT_PLAIN_VALUE) + @ResponseBody + public String getTranslatorIt() { + return "This is the new Translator Arrowhead Core System"; } - } - //------------------------------------------------------------------------------------------------- - @ApiOperation(value = "Public method to check all active hubs", response = ArrayList.class, tags = {CoreCommonConstants.SWAGGER_TAG_CLIENT}) - @ApiResponses(value = { - @ApiResponse(code = HttpStatus.SC_OK, message = CoreCommonConstants.SWAGGER_HTTP_200_MESSAGE), - @ApiResponse(code = HttpStatus.SC_UNAUTHORIZED, message = CoreCommonConstants.SWAGGER_HTTP_401_MESSAGE), - @ApiResponse(code = HttpStatus.SC_INTERNAL_SERVER_ERROR, message = CoreCommonConstants.SWAGGER_HTTP_500_MESSAGE) - }) - @GetMapping(path = PATH_TRANSLATOR_ALL, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - public ArrayList getTranslatorList(@RequestHeader("host") String hostPort) { - String host = hostPort.contains(":")?hostPort.substring(0, hostPort.indexOf(":")):hostPort; - return translatorService.getAllHubs(host); - } + // ------------------------------------------------------------------------------------------------- + @ApiOperation(value = "This method initiates the creation of a new translation hub, if none exists already, between two systems.", response = TranslatorHubDTO.class, tags = { + CoreCommonConstants.SWAGGER_TAG_MGMT }) + @ApiResponses(value = { + @ApiResponse(code = HttpStatus.SC_OK, message = CoreCommonConstants.SWAGGER_HTTP_200_MESSAGE), + @ApiResponse(code = HttpStatus.SC_BAD_REQUEST, message = CoreCommonConstants.SWAGGER_HTTP_400_MESSAGE), + @ApiResponse(code = HttpStatus.SC_UNAUTHORIZED, message = CoreCommonConstants.SWAGGER_HTTP_401_MESSAGE), + @ApiResponse(code = HttpStatus.SC_CONFLICT, message = CoreCommonConstants.SWAGGER_HTTP_409_MESSAGE), + @ApiResponse(code = HttpStatus.SC_INTERNAL_SERVER_ERROR, message = CoreCommonConstants.SWAGGER_HTTP_500_MESSAGE) }) + @PostMapping(path = PATH_NEW_TRANSLATOR_ROOT, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseStatus(org.springframework.http.HttpStatus.CREATED) + @ResponseBody + public TranslatorHubDTO postTranslator(@RequestBody final TranslatorSetupDTO setup, + HttpServletRequest request) { + return translatorService.createTranslatorHub(setup); + } + + // ------------------------------------------------------------------------------------------------- + @ApiOperation(value = "Public method to check for a specific hub provided his translatorId", response = TranslatorHubDTO.class, tags = { + CoreCommonConstants.SWAGGER_TAG_CLIENT }) + @ApiResponses(value = { + @ApiResponse(code = HttpStatus.SC_OK, message = CoreCommonConstants.SWAGGER_HTTP_200_MESSAGE), + @ApiResponse(code = HttpStatus.SC_BAD_REQUEST, message = CoreCommonConstants.SWAGGER_HTTP_400_MESSAGE), + @ApiResponse(code = HttpStatus.SC_UNAUTHORIZED, message = CoreCommonConstants.SWAGGER_HTTP_401_MESSAGE), + @ApiResponse(code = HttpStatus.SC_CONFLICT, message = CoreCommonConstants.SWAGGER_HTTP_409_MESSAGE), + @ApiResponse(code = HttpStatus.SC_INTERNAL_SERVER_ERROR, message = CoreCommonConstants.SWAGGER_HTTP_500_MESSAGE) }) + @GetMapping(path = PATH_NEW_TRANSLATOR_BY_ID, produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody + public TranslatorHubDTO getTranslatorHub2(@PathVariable(value = PATH_VARIABLE_ID) final int translatorId) { + return translatorService.getTranslatorHubDTO(translatorId); + } + + // ------------------------------------------------------------------------------------------------- + @ApiOperation(value = "Public method to remove a hub", tags = { CoreCommonConstants.SWAGGER_TAG_MGMT }) + @ApiResponses(value = { + @ApiResponse(code = HttpStatus.SC_OK, message = CoreCommonConstants.SWAGGER_HTTP_200_MESSAGE), + @ApiResponse(code = HttpStatus.SC_UNAUTHORIZED, message = CoreCommonConstants.SWAGGER_HTTP_401_MESSAGE), + @ApiResponse(code = HttpStatus.SC_INTERNAL_SERVER_ERROR, message = CoreCommonConstants.SWAGGER_HTTP_500_MESSAGE) }) + @DeleteMapping(path = PATH_NEW_TRANSLATOR_BY_ID) + @ResponseStatus(org.springframework.http.HttpStatus.ACCEPTED) + @ResponseBody + public void removeTrasnlatorHub2(@PathVariable(value = PATH_VARIABLE_ID) final int translatorId) { + translatorService.deleteHub(translatorId); + } + + // ------------------------------------------------------------------------------------------------- + @ApiOperation(value = "Public method to check all active hubs", response = ArrayList.class, tags = { + CoreCommonConstants.SWAGGER_TAG_CLIENT }) + @ApiResponses(value = { + @ApiResponse(code = HttpStatus.SC_OK, message = CoreCommonConstants.SWAGGER_HTTP_200_MESSAGE), + @ApiResponse(code = HttpStatus.SC_BAD_REQUEST, message = CoreCommonConstants.SWAGGER_HTTP_400_MESSAGE), + @ApiResponse(code = HttpStatus.SC_UNAUTHORIZED, message = CoreCommonConstants.SWAGGER_HTTP_401_MESSAGE), + @ApiResponse(code = HttpStatus.SC_INTERNAL_SERVER_ERROR, message = CoreCommonConstants.SWAGGER_HTTP_500_MESSAGE) }) + @GetMapping(path = PATH_NEW_TRANSLATOR_ALL, produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody + public ArrayList getHubsList() { + return translatorService.getHubsList(); + } - //------------------------------------------------------------------------------------------------- - @ApiOperation(value = "Public method to check for a specific hub provided his translatorId", response = TranslatorHubAccess.class, tags = {CoreCommonConstants.SWAGGER_TAG_CLIENT}) - @ApiResponses(value = { - @ApiResponse(code = HttpStatus.SC_OK, message = CoreCommonConstants.SWAGGER_HTTP_200_MESSAGE), - @ApiResponse(code = HttpStatus.SC_BAD_REQUEST, message = CoreCommonConstants.SWAGGER_HTTP_400_MESSAGE), - @ApiResponse(code = HttpStatus.SC_UNAUTHORIZED, message = CoreCommonConstants.SWAGGER_HTTP_401_MESSAGE), - @ApiResponse(code = HttpStatus.SC_INTERNAL_SERVER_ERROR, message = CoreCommonConstants.SWAGGER_HTTP_500_MESSAGE) - }) - @GetMapping(path = PATH_TRANSLATOR_BY_ID, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - public TranslatorHubAccess getTranslator(@RequestHeader("host") String hostPort, @PathVariable(value = PATH_VARIABLE_ID) final int translatorId) { - String host = hostPort.contains(":")?hostPort.substring(0, hostPort.indexOf(":")):hostPort; - try { - return translatorService.getHub(translatorId, host); - } catch (Exception ex) { - logger.warn("Exception: " + ex.getLocalizedMessage()); - throw new ArrowheadException(ex.getLocalizedMessage(), HttpStatus.SC_BAD_REQUEST); + // ------------------------------------------------------------------------------------------------- + @ApiOperation(value = "Public method to get Service from a System @ Translator-Plugin", response = Object.class, tags = { + CoreCommonConstants.SWAGGER_TAG_CLIENT }) + @ApiResponses(value = { + @ApiResponse(code = HttpStatus.SC_OK, message = CoreCommonConstants.SWAGGER_HTTP_200_MESSAGE), + @ApiResponse(code = HttpStatus.SC_BAD_REQUEST, message = CoreCommonConstants.SWAGGER_HTTP_400_MESSAGE), + @ApiResponse(code = HttpStatus.SC_UNAUTHORIZED, message = CoreCommonConstants.SWAGGER_HTTP_401_MESSAGE), + @ApiResponse(code = HttpStatus.SC_NOT_FOUND, message = CoreCommonConstants.SWAGGER_HTTP_404_MESSAGE), + @ApiResponse(code = HttpStatus.SC_UNSUPPORTED_MEDIA_TYPE, message = CoreCommonConstants.SWAGGER_HTTP_415_MESSAGE), + @ApiResponse(code = HttpStatus.SC_INTERNAL_SERVER_ERROR, message = CoreCommonConstants.SWAGGER_HTTP_500_MESSAGE) }) + @GetMapping(path = PATH_TRANSLATOR_PLUGIN_ENTITY_AND_SERVICE, produces = { MediaType.TEXT_PLAIN_VALUE, + MediaType.APPLICATION_JSON_VALUE, MEDIA_TYPE_APPLICATION_SENML }) + @ResponseBody + public Object pluginGetEntityValue(HttpServletRequest request, + @PathVariable(value = PATH_ENTITY_ID) final String entityId, + @PathVariable(value = PATH_SERVICE_NAME) final String serviceName) { + if (request.getHeader(HttpHeaders.ACCEPT) == null) + throw new ArrowheadException("No ACCEPT header", HttpStatus.SC_BAD_REQUEST); + return fiwareService.pluginEntityService(entityId, serviceName, request.getHeader(HttpHeaders.ACCEPT)); } - } - //------------------------------------------------------------------------------------------------- - @ApiOperation(value = "Public method to get Service from a System @ Translator-Plugin", response = Object.class, tags = {CoreCommonConstants.SWAGGER_TAG_CLIENT}) - @ApiResponses(value = { - @ApiResponse(code = HttpStatus.SC_OK, message = CoreCommonConstants.SWAGGER_HTTP_200_MESSAGE), - @ApiResponse(code = HttpStatus.SC_BAD_REQUEST, message = CoreCommonConstants.SWAGGER_HTTP_400_MESSAGE), - @ApiResponse(code = HttpStatus.SC_UNAUTHORIZED, message = CoreCommonConstants.SWAGGER_HTTP_401_MESSAGE), - @ApiResponse(code = HttpStatus.SC_NOT_FOUND, message = CoreCommonConstants.SWAGGER_HTTP_404_MESSAGE), - @ApiResponse(code = HttpStatus.SC_UNSUPPORTED_MEDIA_TYPE, message = CoreCommonConstants.SWAGGER_HTTP_415_MESSAGE), - @ApiResponse(code = HttpStatus.SC_INTERNAL_SERVER_ERROR, message = CoreCommonConstants.SWAGGER_HTTP_500_MESSAGE) - }) - @GetMapping(path = PATH_TRANSLATOR_PLUGIN_ENTITY_AND_SERVICE, produces = {MediaType.TEXT_PLAIN_VALUE, MediaType.APPLICATION_JSON_VALUE, MEDIA_TYPE_APPLICATION_SENML}) - @ResponseBody - public Object pluginGetEntityValue( - HttpServletRequest request, - @PathVariable(value = PATH_ENTITY_ID) final String entityId, - @PathVariable(value = PATH_SERVICE_NAME) final String serviceName - ) { - if (request.getHeader(HttpHeaders.ACCEPT) == null) throw new ArrowheadException("No ACCEPT header", HttpStatus.SC_BAD_REQUEST); - return fiwareService.pluginEntityService(entityId, serviceName, request.getHeader(HttpHeaders.ACCEPT)); - } + // ------------------------------------------------------------------------------------------------- + @ApiOperation(value = "FIWARE endpoints", response = FiwareUrlServices.class, tags = { + CoreCommonConstants.SWAGGER_TAG_CLIENT }) + @ApiResponses(value = { + @ApiResponse(code = HttpStatus.SC_OK, message = CoreCommonConstants.SWAGGER_HTTP_200_MESSAGE), + @ApiResponse(code = HttpStatus.SC_UNAUTHORIZED, message = CoreCommonConstants.SWAGGER_HTTP_401_MESSAGE), + @ApiResponse(code = HttpStatus.SC_INTERNAL_SERVER_ERROR, message = CoreCommonConstants.SWAGGER_HTTP_500_MESSAGE) }) + @GetMapping(path = PATH_TRANSLATOR_FIWARE_ROOT, produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody + public FiwareUrlServices fiwareGetIt() { + return new FiwareUrlServices(TRANSLATOR_FIWARE_SERVICES_URL_ENTITIES, + TRANSLATOR_FIWARE_SERVICES_URL_TYPES, null, null); + } - //------------------------------------------------------------------------------------------------- - @ApiOperation(value = "FIWARE endpoints", response = FiwareUrlServices.class, tags = {CoreCommonConstants.SWAGGER_TAG_CLIENT}) - @ApiResponses(value = { - @ApiResponse(code = HttpStatus.SC_OK, message = CoreCommonConstants.SWAGGER_HTTP_200_MESSAGE), - @ApiResponse(code = HttpStatus.SC_UNAUTHORIZED, message = CoreCommonConstants.SWAGGER_HTTP_401_MESSAGE), - @ApiResponse(code = HttpStatus.SC_INTERNAL_SERVER_ERROR, message = CoreCommonConstants.SWAGGER_HTTP_500_MESSAGE) - }) - @GetMapping(path = PATH_TRANSLATOR_FIWARE_ROOT, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - public FiwareUrlServices fiwareGetIt() { - return new FiwareUrlServices(TRANSLATOR_FIWARE_SERVICES_URL_ENTITIES, TRANSLATOR_FIWARE_SERVICES_URL_TYPES, null, null); - } + // ------------------------------------------------------------------------------------------------- + @ApiOperation(value = "FIWARE list Entities", response = FiwareEntity[].class, tags = { + CoreCommonConstants.SWAGGER_TAG_CLIENT }) + @ApiResponses(value = { + @ApiResponse(code = HttpStatus.SC_OK, message = CoreCommonConstants.SWAGGER_HTTP_200_MESSAGE), + @ApiResponse(code = HttpStatus.SC_UNAUTHORIZED, message = CoreCommonConstants.SWAGGER_HTTP_401_MESSAGE), + @ApiResponse(code = HttpStatus.SC_INTERNAL_SERVER_ERROR, message = CoreCommonConstants.SWAGGER_HTTP_500_MESSAGE) }) + @GetMapping(path = PATH_TRANSLATOR_FIWARE_ENTITIES, produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody + public ArrayList fiwareListEntities(@RequestParam(required = false) String id, + @RequestParam(required = false) String type, @RequestParam(required = false) String idPattern, + @RequestParam(required = false) String typePattern, @RequestParam(required = false) String q, + @RequestParam(required = false) String mq, @RequestParam(required = false) String georel, + @RequestParam(required = false) String geometry, @RequestParam(required = false) String coords, + @RequestParam(required = false) Integer limit, @RequestParam(required = false) Integer offset, + @RequestParam(required = false) String attrs, @RequestParam(required = false) String metadata, + @RequestParam(required = false) String orderBy, @RequestParam(required = false) String options, + @RequestParam(required = false) Map allRequestParams) { + List fiwareResponse = Arrays.asList(fiwareService.listEntities(allRequestParams)); + ArrayList ahResponse = fiwareService.getArrowheadServices(id, type); - //------------------------------------------------------------------------------------------------- - @ApiOperation(value = "FIWARE list Entities", response = FiwareEntity[].class, tags = {CoreCommonConstants.SWAGGER_TAG_CLIENT}) - @ApiResponses(value = { - @ApiResponse(code = HttpStatus.SC_OK, message = CoreCommonConstants.SWAGGER_HTTP_200_MESSAGE), - @ApiResponse(code = HttpStatus.SC_UNAUTHORIZED, message = CoreCommonConstants.SWAGGER_HTTP_401_MESSAGE), - @ApiResponse(code = HttpStatus.SC_INTERNAL_SERVER_ERROR, message = CoreCommonConstants.SWAGGER_HTTP_500_MESSAGE) - }) - @GetMapping(path = PATH_TRANSLATOR_FIWARE_ENTITIES, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - public ArrayList fiwareListEntities( - @RequestParam(required = false) String id, - @RequestParam(required = false) String type, - @RequestParam(required = false) String idPattern, - @RequestParam(required = false) String typePattern, - @RequestParam(required = false) String q, - @RequestParam(required = false) String mq, - @RequestParam(required = false) String georel, - @RequestParam(required = false) String geometry, - @RequestParam(required = false) String coords, - @RequestParam(required = false) Integer limit, - @RequestParam(required = false) Integer offset, - @RequestParam(required = false) String attrs, - @RequestParam(required = false) String metadata, - @RequestParam(required = false) String orderBy, - @RequestParam(required = false) String options, - @RequestParam(required = false) Map allRequestParams - ) { - List fiwareResponse = Arrays.asList(fiwareService.listEntities(allRequestParams)); - ArrayList ahResponse = fiwareService.getArrowheadServices(id, type); + ahResponse.addAll(fiwareResponse); - ahResponse.addAll(fiwareResponse); - - return ahResponse; - } + return ahResponse; + } - //------------------------------------------------------------------------------------------------- - @ApiOperation(value = "FIWARE create Entity", tags = {CoreCommonConstants.SWAGGER_TAG_CLIENT}) - @ApiResponses(value = { - @ApiResponse(code = HttpStatus.SC_CREATED, message = ""), - @ApiResponse(code = HttpStatus.SC_UNAUTHORIZED, message = CoreCommonConstants.SWAGGER_HTTP_401_MESSAGE), - @ApiResponse(code = HttpStatus.SC_UNPROCESSABLE_ENTITY, message = ""), - @ApiResponse(code = HttpStatus.SC_INTERNAL_SERVER_ERROR, message = CoreCommonConstants.SWAGGER_HTTP_500_MESSAGE) - }) - @PostMapping(path = PATH_TRANSLATOR_FIWARE_ENTITIES, consumes = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - public void fiwareCreateEntity( - HttpServletResponse response, - @RequestParam(required = false) String options, - @RequestParam(required = false) Map allRequestParams, - @RequestBody final FiwareEntity entity - ) { - response.setStatus(fiwareService.createEntity(allRequestParams, entity)); - } + // ------------------------------------------------------------------------------------------------- + @ApiOperation(value = "FIWARE create Entity", tags = { CoreCommonConstants.SWAGGER_TAG_CLIENT }) + @ApiResponses(value = { @ApiResponse(code = HttpStatus.SC_CREATED, message = ""), + @ApiResponse(code = HttpStatus.SC_UNAUTHORIZED, message = CoreCommonConstants.SWAGGER_HTTP_401_MESSAGE), + @ApiResponse(code = HttpStatus.SC_UNPROCESSABLE_ENTITY, message = ""), + @ApiResponse(code = HttpStatus.SC_INTERNAL_SERVER_ERROR, message = CoreCommonConstants.SWAGGER_HTTP_500_MESSAGE) }) + @PostMapping(path = PATH_TRANSLATOR_FIWARE_ENTITIES, consumes = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody + public void fiwareCreateEntity(HttpServletResponse response, @RequestParam(required = false) String options, + @RequestParam(required = false) Map allRequestParams, + @RequestBody final FiwareEntity entity) { + response.setStatus(fiwareService.createEntity(allRequestParams, entity)); + } - //------------------------------------------------------------------------------------------------- - @ApiOperation(value = "FIWARE retrieve Entity", response = FiwareEntity.class, tags = {CoreCommonConstants.SWAGGER_TAG_CLIENT}) - @ApiResponses(value = { - @ApiResponse(code = HttpStatus.SC_OK, message = CoreCommonConstants.SWAGGER_HTTP_200_MESSAGE), - @ApiResponse(code = HttpStatus.SC_UNAUTHORIZED, message = CoreCommonConstants.SWAGGER_HTTP_401_MESSAGE), - @ApiResponse(code = HttpStatus.SC_INTERNAL_SERVER_ERROR, message = CoreCommonConstants.SWAGGER_HTTP_500_MESSAGE) - }) - @GetMapping(path = PATH_TRANSLATOR_FIWARE_ENTITIES_BY_ID, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - public FiwareEntity fiwareRetrieveEntity( - @PathVariable(value = PATH_ENTITY_ID) final String entityId, - @RequestParam(required = false) String type, - @RequestParam(required = false) String attrs, - @RequestParam(required = false) String metadata, - @RequestParam(required = false) String options, - @RequestParam(required = false) Map allRequestParams - ) { - return fiwareService.queryEntity(entityId, allRequestParams); - } + // ------------------------------------------------------------------------------------------------- + @ApiOperation(value = "FIWARE retrieve Entity", response = FiwareEntity.class, tags = { + CoreCommonConstants.SWAGGER_TAG_CLIENT }) + @ApiResponses(value = { + @ApiResponse(code = HttpStatus.SC_OK, message = CoreCommonConstants.SWAGGER_HTTP_200_MESSAGE), + @ApiResponse(code = HttpStatus.SC_UNAUTHORIZED, message = CoreCommonConstants.SWAGGER_HTTP_401_MESSAGE), + @ApiResponse(code = HttpStatus.SC_INTERNAL_SERVER_ERROR, message = CoreCommonConstants.SWAGGER_HTTP_500_MESSAGE) }) + @GetMapping(path = PATH_TRANSLATOR_FIWARE_ENTITIES_BY_ID, produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody + public FiwareEntity fiwareRetrieveEntity(@PathVariable(value = PATH_ENTITY_ID) final String entityId, + @RequestParam(required = false) String type, @RequestParam(required = false) String attrs, + @RequestParam(required = false) String metadata, @RequestParam(required = false) String options, + @RequestParam(required = false) Map allRequestParams) { + return fiwareService.queryEntity(entityId, allRequestParams); + } - //------------------------------------------------------------------------------------------------- - @ApiOperation(value = "FIWARE retrieve Entity Attributes", response = Object.class, tags = {CoreCommonConstants.SWAGGER_TAG_CLIENT}) - @ApiResponses(value = { - @ApiResponse(code = HttpStatus.SC_OK, message = CoreCommonConstants.SWAGGER_HTTP_200_MESSAGE), - @ApiResponse(code = HttpStatus.SC_UNAUTHORIZED, message = CoreCommonConstants.SWAGGER_HTTP_401_MESSAGE), - @ApiResponse(code = HttpStatus.SC_INTERNAL_SERVER_ERROR, message = CoreCommonConstants.SWAGGER_HTTP_500_MESSAGE) - }) - @GetMapping(path = PATH_TRANSLATOR_FIWARE_ENTITIES_ATTRIBUTES_BY_ID, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - public Object fiwareRetrieveEntityAttributes( - @PathVariable(value = PATH_ENTITY_ID) final String entityId, - @RequestParam(required = false) String type, - @RequestParam(required = false) String attrs, - @RequestParam(required = false) String metadata, - @RequestParam(required = false) String options, - @RequestParam(required = false) Map allRequestParams - ) { - return fiwareService.retrieveEntityAttributes(entityId, allRequestParams); - } + // ------------------------------------------------------------------------------------------------- + @ApiOperation(value = "FIWARE retrieve Entity Attributes", response = Object.class, tags = { + CoreCommonConstants.SWAGGER_TAG_CLIENT }) + @ApiResponses(value = { + @ApiResponse(code = HttpStatus.SC_OK, message = CoreCommonConstants.SWAGGER_HTTP_200_MESSAGE), + @ApiResponse(code = HttpStatus.SC_UNAUTHORIZED, message = CoreCommonConstants.SWAGGER_HTTP_401_MESSAGE), + @ApiResponse(code = HttpStatus.SC_INTERNAL_SERVER_ERROR, message = CoreCommonConstants.SWAGGER_HTTP_500_MESSAGE) }) + @GetMapping(path = PATH_TRANSLATOR_FIWARE_ENTITIES_ATTRIBUTES_BY_ID, produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody + public Object fiwareRetrieveEntityAttributes(@PathVariable(value = PATH_ENTITY_ID) final String entityId, + @RequestParam(required = false) String type, @RequestParam(required = false) String attrs, + @RequestParam(required = false) String metadata, @RequestParam(required = false) String options, + @RequestParam(required = false) Map allRequestParams) { + return fiwareService.retrieveEntityAttributes(entityId, allRequestParams); + } - //------------------------------------------------------------------------------------------------- - @ApiOperation(value = "FIWARE update/append Entity Attributes", tags = {CoreCommonConstants.SWAGGER_TAG_CLIENT}) - @ApiResponses(value = { - @ApiResponse(code = HttpStatus.SC_OK, message = CoreCommonConstants.SWAGGER_HTTP_200_MESSAGE), - @ApiResponse(code = HttpStatus.SC_UNAUTHORIZED, message = CoreCommonConstants.SWAGGER_HTTP_401_MESSAGE), - @ApiResponse(code = HttpStatus.SC_INTERNAL_SERVER_ERROR, message = CoreCommonConstants.SWAGGER_HTTP_500_MESSAGE) - }) - @PostMapping(path = PATH_TRANSLATOR_FIWARE_ENTITIES_ATTRIBUTES_BY_ID, consumes = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - public void fiwareupdateAppendEntityAttributes( - HttpServletResponse response, - @PathVariable(value = PATH_ENTITY_ID) final String entityId, - @RequestParam(required = false) String type, - @RequestParam(required = false) String attrs, - @RequestParam(required = false) String metadata, - @RequestParam(required = false) String options, - @RequestParam(required = false) Map allRequestParams, - @RequestBody final Object attributes - ) { - response.setStatus(fiwareService.updateOrAppendEntityAttributes(entityId, allRequestParams, attributes)); - } + // ------------------------------------------------------------------------------------------------- + @ApiOperation(value = "FIWARE update/append Entity Attributes", tags = { + CoreCommonConstants.SWAGGER_TAG_CLIENT }) + @ApiResponses(value = { + @ApiResponse(code = HttpStatus.SC_OK, message = CoreCommonConstants.SWAGGER_HTTP_200_MESSAGE), + @ApiResponse(code = HttpStatus.SC_UNAUTHORIZED, message = CoreCommonConstants.SWAGGER_HTTP_401_MESSAGE), + @ApiResponse(code = HttpStatus.SC_INTERNAL_SERVER_ERROR, message = CoreCommonConstants.SWAGGER_HTTP_500_MESSAGE) }) + @PostMapping(path = PATH_TRANSLATOR_FIWARE_ENTITIES_ATTRIBUTES_BY_ID, consumes = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody + public void fiwareupdateAppendEntityAttributes(HttpServletResponse response, + @PathVariable(value = PATH_ENTITY_ID) final String entityId, + @RequestParam(required = false) String type, @RequestParam(required = false) String attrs, + @RequestParam(required = false) String metadata, @RequestParam(required = false) String options, + @RequestParam(required = false) Map allRequestParams, + @RequestBody final Object attributes) { + response.setStatus( + fiwareService.updateOrAppendEntityAttributes(entityId, allRequestParams, attributes)); + } - //------------------------------------------------------------------------------------------------- - @ApiOperation(value = "FIWARE remove Entity", tags = {CoreCommonConstants.SWAGGER_TAG_CLIENT}) - @ApiResponses(value = { - @ApiResponse(code = HttpStatus.SC_OK, message = CoreCommonConstants.SWAGGER_HTTP_200_MESSAGE), - @ApiResponse(code = HttpStatus.SC_UNAUTHORIZED, message = CoreCommonConstants.SWAGGER_HTTP_401_MESSAGE), - @ApiResponse(code = HttpStatus.SC_INTERNAL_SERVER_ERROR, message = CoreCommonConstants.SWAGGER_HTTP_500_MESSAGE) - }) - @DeleteMapping(path = PATH_TRANSLATOR_FIWARE_ENTITIES_BY_ID) - @ResponseBody - public void fiwareRemoveEntity( - HttpServletResponse response, - @PathVariable(value = PATH_ENTITY_ID) final String entityId, - @RequestParam(required = false) String type, - @RequestParam(required = false) Map allRequestParams - ) { - response.setStatus(fiwareService.removeEntity(entityId, allRequestParams)); - } + // ------------------------------------------------------------------------------------------------- + @ApiOperation(value = "FIWARE remove Entity", tags = { CoreCommonConstants.SWAGGER_TAG_CLIENT }) + @ApiResponses(value = { + @ApiResponse(code = HttpStatus.SC_OK, message = CoreCommonConstants.SWAGGER_HTTP_200_MESSAGE), + @ApiResponse(code = HttpStatus.SC_UNAUTHORIZED, message = CoreCommonConstants.SWAGGER_HTTP_401_MESSAGE), + @ApiResponse(code = HttpStatus.SC_INTERNAL_SERVER_ERROR, message = CoreCommonConstants.SWAGGER_HTTP_500_MESSAGE) }) + @DeleteMapping(path = PATH_TRANSLATOR_FIWARE_ENTITIES_BY_ID) + @ResponseBody + public void fiwareRemoveEntity(HttpServletResponse response, + @PathVariable(value = PATH_ENTITY_ID) final String entityId, + @RequestParam(required = false) String type, + @RequestParam(required = false) Map allRequestParams) { + response.setStatus(fiwareService.removeEntity(entityId, allRequestParams)); + } - //------------------------------------------------------------------------------------------------- - @ApiOperation(value = "FIWARE list Entity Types", response = Object[].class, tags = {CoreCommonConstants.SWAGGER_TAG_CLIENT}) - @ApiResponses(value = { - @ApiResponse(code = HttpStatus.SC_OK, message = CoreCommonConstants.SWAGGER_HTTP_200_MESSAGE), - @ApiResponse(code = HttpStatus.SC_UNAUTHORIZED, message = CoreCommonConstants.SWAGGER_HTTP_401_MESSAGE), - @ApiResponse(code = HttpStatus.SC_INTERNAL_SERVER_ERROR, message = CoreCommonConstants.SWAGGER_HTTP_500_MESSAGE) - }) - @GetMapping(path = PATH_TRANSLATOR_FIWARE_TYPES, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - public Object[] fiwareListEntityTypes( - @RequestParam(required = false) Integer limit, - @RequestParam(required = false) Integer offset, - @RequestParam(required = false) String options, - @RequestParam(required = false) Map allRequestParams - ) { - return fiwareService.queryTypesList(allRequestParams); - } + // ------------------------------------------------------------------------------------------------- + @ApiOperation(value = "FIWARE list Entity Types", response = Object[].class, tags = { + CoreCommonConstants.SWAGGER_TAG_CLIENT }) + @ApiResponses(value = { + @ApiResponse(code = HttpStatus.SC_OK, message = CoreCommonConstants.SWAGGER_HTTP_200_MESSAGE), + @ApiResponse(code = HttpStatus.SC_UNAUTHORIZED, message = CoreCommonConstants.SWAGGER_HTTP_401_MESSAGE), + @ApiResponse(code = HttpStatus.SC_INTERNAL_SERVER_ERROR, message = CoreCommonConstants.SWAGGER_HTTP_500_MESSAGE) }) + @GetMapping(path = PATH_TRANSLATOR_FIWARE_TYPES, produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody + public Object[] fiwareListEntityTypes(@RequestParam(required = false) Integer limit, + @RequestParam(required = false) Integer offset, @RequestParam(required = false) String options, + @RequestParam(required = false) Map allRequestParams) { + return fiwareService.queryTypesList(allRequestParams); + } - //------------------------------------------------------------------------------------------------- - @ApiOperation(value = "FIWARE Retrieve Entity Type", response = Object.class, tags = {CoreCommonConstants.SWAGGER_TAG_CLIENT}) - @ApiResponses(value = { - @ApiResponse(code = HttpStatus.SC_OK, message = CoreCommonConstants.SWAGGER_HTTP_200_MESSAGE), - @ApiResponse(code = HttpStatus.SC_UNAUTHORIZED, message = CoreCommonConstants.SWAGGER_HTTP_401_MESSAGE), - @ApiResponse(code = HttpStatus.SC_INTERNAL_SERVER_ERROR, message = CoreCommonConstants.SWAGGER_HTTP_500_MESSAGE) - }) - @GetMapping(path = PATH_TRANSLATOR_FIWARE_TYPES_BY_TYPE, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - public Object fiwareRetrieveEntityType( - @PathVariable(value = PATH_ENTITY_TYPE) final String entityType - ) { - return fiwareService.retrieveEntityType(entityType); - } + // ------------------------------------------------------------------------------------------------- + @ApiOperation(value = "FIWARE Retrieve Entity Type", response = Object.class, tags = { + CoreCommonConstants.SWAGGER_TAG_CLIENT }) + @ApiResponses(value = { + @ApiResponse(code = HttpStatus.SC_OK, message = CoreCommonConstants.SWAGGER_HTTP_200_MESSAGE), + @ApiResponse(code = HttpStatus.SC_UNAUTHORIZED, message = CoreCommonConstants.SWAGGER_HTTP_401_MESSAGE), + @ApiResponse(code = HttpStatus.SC_INTERNAL_SERVER_ERROR, message = CoreCommonConstants.SWAGGER_HTTP_500_MESSAGE) }) + @GetMapping(path = PATH_TRANSLATOR_FIWARE_TYPES_BY_TYPE, produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody + public Object fiwareRetrieveEntityType(@PathVariable(value = PATH_ENTITY_TYPE) final String entityType) { + return fiwareService.retrieveEntityType(entityType); + } - //================================================================================================= - // assistant methods - //------------------------------------------------------------------------------------------------- + // ================================================================================================= + // assistant methods + // ------------------------------------------------------------------------------------------------- } diff --git a/translator/src/main/java/eu/arrowhead/core/translator/services/fiware/ArrowheadDriver.java b/translator/src/main/java/eu/arrowhead/core/translator/services/fiware/ArrowheadDriver.java index 7f25dec72..d898d2242 100644 --- a/translator/src/main/java/eu/arrowhead/core/translator/services/fiware/ArrowheadDriver.java +++ b/translator/src/main/java/eu/arrowhead/core/translator/services/fiware/ArrowheadDriver.java @@ -185,9 +185,6 @@ private SystemRequestDTO getSystemRequestDTO(String id) { //------------------------------------------------------------------------------------------------- private String getServiceUri(String entityId, String serviceName) { return String.format("/translator/plugin/service/%s/%s", - /*sslProperties.isSslEnabled() ? "https" : "http", - myIpAddress, - translatorPort,*/ entityId, serviceName ); diff --git a/translator/src/main/java/eu/arrowhead/core/translator/services/translator/TranslatorHub.java b/translator/src/main/java/eu/arrowhead/core/translator/services/translator/TranslatorHub.java index bb67d60e1..0739a8671 100644 --- a/translator/src/main/java/eu/arrowhead/core/translator/services/translator/TranslatorHub.java +++ b/translator/src/main/java/eu/arrowhead/core/translator/services/translator/TranslatorHub.java @@ -1,128 +1,120 @@ package eu.arrowhead.core.translator.services.translator; -import eu.arrowhead.core.translator.services.translator.common.TranslatorDef.EndPoint; -import eu.arrowhead.core.translator.services.translator.spokes.BaseSpokeConsumer; -import eu.arrowhead.core.translator.services.translator.spokes.CoapConsumerSpoke; -import eu.arrowhead.core.translator.services.translator.spokes.CoapProducerSpoke; -import eu.arrowhead.core.translator.services.translator.spokes.HttpConsumerSpoke; -import eu.arrowhead.core.translator.services.translator.spokes.HttpProducerSpoke; - -import eu.arrowhead.common.exception.ArrowheadException; - -import java.net.URI; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import eu.arrowhead.core.translator.services.translator.spokes.BaseSpokeProducer; import java.io.IOException; -import java.net.URISyntaxException; -import java.net.UnknownHostException; -import org.apache.http.HttpStatus; - -public class TranslatorHub { - - //================================================================================================= - // members - private final Logger logger = LogManager.getLogger(TranslatorHub.class); - private final int id; - private final BaseSpokeProducer pSpoke; - private final BaseSpokeConsumer cSpoke; - private final String hubIp = "0.0.0.0"; - private final int hubPort; - - public TranslatorHub(int id, EndPoint pSpokeConsumer, EndPoint cSpokeProvider) throws ArrowheadException { - this.id = id; - checkEndpoint(pSpokeConsumer); - checkEndpoint(cSpokeProvider); - - try { - - switch (pSpokeConsumer.getProtocol()) { - case coap: - pSpoke = new CoapProducerSpoke(hubIp); - hubPort = new URI(pSpoke.getAddress()).getPort(); - break; - case http: - pSpoke = new HttpProducerSpoke(hubIp, "/*"); - hubPort = new URI(pSpoke.getAddress()).getPort(); - break; - default: - throw new ArrowheadException("Unknown protocol " + pSpokeConsumer.getProtocol(), HttpStatus.SC_BAD_REQUEST); - } - - switch (cSpokeProvider.getProtocol()) { - case coap: - cSpoke = new CoapConsumerSpoke(cSpokeProvider.getHostIpAddress()); - break; - case http: - cSpoke = new HttpConsumerSpoke(cSpokeProvider.getHostIpAddress()); - break; - default: - throw new ArrowheadException("Unknown protocol " + cSpokeProvider.getProtocol(), HttpStatus.SC_BAD_REQUEST); - } - } catch (URISyntaxException ex) { - throw new ArrowheadException("Wrong URI Syntax", HttpStatus.SC_BAD_REQUEST, ex.getLocalizedMessage()); - } catch (IOException | InterruptedException ex) { - throw new ArrowheadException(ex.getMessage(), HttpStatus.SC_INTERNAL_SERVER_ERROR); - } - - // link the spoke connections - pSpoke.setNextSpoke(cSpoke); - cSpoke.setNextSpoke(pSpoke); +import java.net.ServerSocket; +import java.net.URI; +import java.util.Random; - // Activity Monitor - ScheduledExecutorService sesPrintReport = Executors.newSingleThreadScheduledExecutor(); - sesPrintReport.scheduleAtFixedRate(new Runnable() { - @Override - public void run() { - activityMonitor(); - } - }, 1, 1, TimeUnit.MINUTES); - } +import org.apache.http.HttpStatus; - //================================================================================================= - // methods - //------------------------------------------------------------------------------------------------- - public int getTranslatorId() { - return id; - } +import eu.arrowhead.common.exception.ArrowheadException; +import eu.arrowhead.core.translator.services.translator.common.TranslatorHubDTO; +import eu.arrowhead.core.translator.services.translator.common.TranslatorSetupDTO; +import eu.arrowhead.core.translator.services.translator.protocols.CoapIn; +import eu.arrowhead.core.translator.services.translator.protocols.CoapOut; +import eu.arrowhead.core.translator.services.translator.protocols.HttpIn; +import eu.arrowhead.core.translator.services.translator.protocols.HttpOut; +import eu.arrowhead.core.translator.services.translator.protocols.MqttIn; +import eu.arrowhead.core.translator.services.translator.protocols.MqttOut; +import eu.arrowhead.core.translator.services.translator.protocols.ProtocolIn; +import eu.arrowhead.core.translator.services.translator.protocols.ProtocolOut; +import eu.arrowhead.core.translator.services.translator.protocols.WsIn; +import eu.arrowhead.core.translator.services.translator.protocols.WsOut; - //------------------------------------------------------------------------------------------------- - public int getHubPort() { - return hubPort; +public class TranslatorHub { + // ================================================================================================= + // members + private final int id; + private final TranslatorSetupDTO setup; + private ProtocolIn protocolIn; + private ProtocolOut protocolOut; + + public TranslatorHub(int id, TranslatorSetupDTO setup) { + this.setup = setup; + this.id = id; + startHub(); + } + + // ================================================================================================= + // methods + // ------------------------------------------------------------------------------------------------- + public TranslatorHubDTO getDTO() { + return new TranslatorHubDTO(id, setup.getIn(), setup.getOut()); + } + + // ================================================================================================= + // assistant methods + // ------------------------------------------------------------------------------------------------- + private void startHub() { + try { + switch (setup.getIn().getProtocol()) { + case COAP: + setup.getIn().setPort(getAvailablePort()); + protocolIn = new CoapIn(new URI("coap://0.0.0.0:" + setup.getIn().getPort())); + break; + case HTTP: + setup.getIn().setPort(getAvailablePort()); + protocolIn = new HttpIn(new URI("http://0.0.0.0:" + setup.getIn().getPort())); + break; + case MQTT: + setup.getIn().setPort(getRandomNumber()); + protocolIn = new MqttIn(new URI("tcp://127.0.0.1:" + setup.getIn().getPort())); + break; + case WS: + setup.getIn().setPort(getAvailablePort()); + protocolIn = new WsIn(new URI("ws://0.0.0.0:" + setup.getIn().getPort())); + break; + default: + throw new ArrowheadException("Unknown Protocol In: " + setup.getIn().getProtocol(), + HttpStatus.SC_INTERNAL_SERVER_ERROR); + } + + switch (setup.getOut().getProtocol()) { + case COAP: + protocolOut = new CoapOut(new URI("coap://" + setup.getOut().getIp() + ":" + setup.getOut().getPort())); + break; + case HTTP: + protocolOut = new HttpOut(new URI("http://" + setup.getOut().getIp() + ":" + setup.getOut().getPort())); + break; + case MQTT: + protocolOut = new MqttOut(new URI("tcp://" + setup.getOut().getIp() + ":" + setup.getOut().getPort())); + break; + case WS: + protocolOut = new WsOut(new URI("ws://" + setup.getOut().getIp() + ":" + setup.getOut().getPort())); + break; + default: + throw new ArrowheadException("Unknown Protocol Out: " + setup.getOut().getProtocol(), + HttpStatus.SC_INTERNAL_SERVER_ERROR); + } + + protocolIn.setProtocolOut(protocolOut); + protocolIn.setContentType(setup.getIn().getContentType()); + protocolOut.setProtocolIn(protocolIn); + protocolOut.setContentType(setup.getOut().getContentType()); + + } catch (Exception ex) { + throw new ArrowheadException("Problems to start a hub: " + ex.getLocalizedMessage(), + HttpStatus.SC_INTERNAL_SERVER_ERROR); } - //================================================================================================= - // assistant methods - //------------------------------------------------------------------------------------------------- - private void activityMonitor() { - if ((cSpoke.getLastActivity() > 0) || (pSpoke.getLastActivity() > 0)) { - logger.info(String.format("activityMonitor [%d] - Active", id)); - cSpoke.clearActivity(); - pSpoke.clearActivity(); - } else { - logger.info(String.format("activityMonitor [%d] - No Active", id)); - } + } + + // ------------------------------------------------------------------------------------------------- + private int getAvailablePort() { + try { + ServerSocket ss = new ServerSocket(0); + int port = ss.getLocalPort(); + ss.close(); + return port; + } catch (IOException ex) { + throw new ArrowheadException("Problems to start a empty port: " + ex.getLocalizedMessage(), + HttpStatus.SC_INTERNAL_SERVER_ERROR); } - private void checkEndpoint(EndPoint endpoint) { - if (endpoint == null) { - throw new ArrowheadException("Null endpoint", HttpStatus.SC_BAD_REQUEST); - } - if (endpoint.getHostIpAddress() == null) { - throw new ArrowheadException("No Host IpAddress", HttpStatus.SC_BAD_REQUEST); - } - if (endpoint.getName() == null) { - throw new ArrowheadException("No name", HttpStatus.SC_BAD_REQUEST); - } - if (endpoint.getPort() <= 0 || endpoint.getPort() > 65535) { - throw new ArrowheadException("No valid Port", HttpStatus.SC_BAD_REQUEST); - } - if (endpoint.getProtocol() == null) { - throw new ArrowheadException("No Protocol", HttpStatus.SC_BAD_REQUEST); - } - } + } + // ------------------------------------------------------------------------------------------------- + private int getRandomNumber() { + return new Random().nextInt(10000); + } } diff --git a/translator/src/main/java/eu/arrowhead/core/translator/services/translator/TranslatorService.java b/translator/src/main/java/eu/arrowhead/core/translator/services/translator/TranslatorService.java index f276771e2..1855d67d1 100644 --- a/translator/src/main/java/eu/arrowhead/core/translator/services/translator/TranslatorService.java +++ b/translator/src/main/java/eu/arrowhead/core/translator/services/translator/TranslatorService.java @@ -1,75 +1,90 @@ -package eu.arrowhead.core.translator.services.translator; - -import eu.arrowhead.core.translator.services.translator.common.TranslatorSetup; -import com.fasterxml.jackson.databind.ObjectMapper; -import eu.arrowhead.core.translator.services.translator.common.TranslatorHubAccess; -import eu.arrowhead.core.translator.services.translator.common.TranslatorDef.EndPoint; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.springframework.stereotype.Service; - -@Service -public class TranslatorService { - - //================================================================================================= - // members - private final Logger logger = LogManager.getLogger(TranslatorService.class); - private final Map hubs = new HashMap<>(); - - //================================================================================================= - // methods - //------------------------------------------------------------------------------------------------- - public void start() { - logger.debug("Starting TranslatorService"); - } - - //------------------------------------------------------------------------------------------------- - public TranslatorHubAccess createTranslationHub(TranslatorSetup setup, String outgoingIp) throws Exception { - logger.debug("create Translation Hub"); - logger.debug("ip: " + outgoingIp); - logger.debug("setup: " + new ObjectMapper().writeValueAsString(setup)); - - EndPoint consumerEP = new EndPoint(setup.getConsumerName(), setup.getConsumerAddress()); - EndPoint producerEP = new EndPoint(setup.getProducerName(), setup.getProducerAddress()); - - if (consumerEP.isLocal() || producerEP.isLocal()) { - logger.warn("Not valid ip address, we need absolute address, not relative (as 127.0.0.1 or localhost)"); - throw new Exception("Not valid ip address, we need absolute address, not relative (as 127.0.0.1 or localhost)"); - } else { - - int translatorId = (producerEP.getName() + consumerEP.getName()).hashCode(); - if (!hubs.isEmpty() && hubs.containsKey(translatorId)) { - logger.debug("Hub already exists!"); - TranslatorHub existingHub = hubs.get(translatorId); - return new TranslatorHubAccess(existingHub.getTranslatorId(), outgoingIp, existingHub.getHubPort()); - } else { - logger.debug("Creating a new Hub: ClientSpoke type:" + consumerEP.getProtocol() + " ServerSpoke type: " + producerEP.getProtocol()); - TranslatorHub hub = new TranslatorHub(translatorId, consumerEP, producerEP); - hubs.put(translatorId, hub); - return new TranslatorHubAccess(hub.getTranslatorId(), outgoingIp, hub.getHubPort()); - } - } - } - - //------------------------------------------------------------------------------------------------- - public ArrayList getAllHubs(String localOutgoingIp) { - ArrayList response = new ArrayList<>(); - hubs.entrySet().forEach((entry) -> { - response.add(new TranslatorHubAccess(entry.getValue().getTranslatorId(), localOutgoingIp, entry.getValue().getHubPort())); - }); - return response; - } - - //------------------------------------------------------------------------------------------------- - public TranslatorHubAccess getHub(int translatorId, String localOutgoingIp) throws Exception { - TranslatorHub hub = hubs.get(translatorId); - if (hub == null) { - throw new Exception("Not Found"); - } - return new TranslatorHubAccess(translatorId, localOutgoingIp, hub.getHubPort()); - } - -} +package eu.arrowhead.core.translator.services.translator; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import org.apache.http.HttpStatus; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.springframework.stereotype.Service; + +import eu.arrowhead.common.exception.ArrowheadException; +import eu.arrowhead.core.translator.services.translator.common.TranslatorHubDTO; +import eu.arrowhead.core.translator.services.translator.common.TranslatorSetupDTO; +import eu.arrowhead.core.translator.services.translator.common.Translation.Protocol; + +@Service +public class TranslatorService { + // ================================================================================================= + // members + private final Logger logger = LogManager.getLogger(TranslatorService.class); + private final Map hubs = new HashMap<>(); + + // ================================================================================================= + // methods + // ------------------------------------------------------------------------------------------------- + public void start() { + logger.info("Starting Translator Service"); + } + + // ------------------------------------------------------------------------------------------------- + public TranslatorHubDTO createTranslatorHub(TranslatorSetupDTO setup) { + int id = checkSetupDTO(setup); + if (hubs.containsKey(id)) + throw new ArrowheadException("This Translator Hub already exists", HttpStatus.SC_CONFLICT); + TranslatorHub hub = new TranslatorHub(id, setup); + hubs.put(id, hub); + return hub.getDTO(); + } + + // ------------------------------------------------------------------------------------------------- + public TranslatorHubDTO getTranslatorHubDTO(int id) { + if (!hubs.containsKey(id)) + throw new ArrowheadException(String.format("Translator Hub id %d does NOT exists", id), HttpStatus.SC_CONFLICT); + return hubs.get(id).getDTO(); + } + + // ------------------------------------------------------------------------------------------------- + public void deleteHub(int id) { + if (!hubs.containsKey(id)) + throw new ArrowheadException(String.format("Translator Hub id %d does NOT exists", id), HttpStatus.SC_CONFLICT); + // Get hub + TranslatorHub hub = hubs.get(id); + // remove it + hubs.remove(id); + + } + + // ------------------------------------------------------------------------------------------------- + public ArrayList getHubsList() { + ArrayList list = new ArrayList(); + hubs.entrySet().forEach((entry) -> { + list.add(entry.getValue().getDTO()); + }); + return list; + } + + // ================================================================================================= + // assistant methods + // ------------------------------------------------------------------------------------------------- + private int checkSetupDTO(TranslatorSetupDTO setup) { + if (setup == null) + throw new ArrowheadException("Empty TranslatorSetupDTO", HttpStatus.SC_BAD_REQUEST); + if (setup.getIn() == null) + throw new ArrowheadException("Empty In Interface", HttpStatus.SC_BAD_REQUEST); + if (setup.getIn().getProtocol() == null || setup.getOut().getProtocol() == Protocol.UNKOWN) + throw new ArrowheadException("Empty In Interface Protocol", HttpStatus.SC_BAD_REQUEST); + if (setup.getOut() == null) + throw new ArrowheadException("Empty Out Interface", HttpStatus.SC_BAD_REQUEST); + if (setup.getOut().getIp() == null) + throw new ArrowheadException("Empty Out Interface Ip Address", HttpStatus.SC_BAD_REQUEST); + if (setup.getOut().getPort() < 1 || setup.getIn().getPort() > 65535) + throw new ArrowheadException("Wrong Out Interface Port number", HttpStatus.SC_BAD_REQUEST); + if (setup.getOut().getProtocol() == null || setup.getOut().getProtocol() == Protocol.UNKOWN) + throw new ArrowheadException("Empty Out Interface Protocol", HttpStatus.SC_BAD_REQUEST); + return (setup.getIn().getProtocol() + setup.getOut().getIp() + setup.getOut().getPort() + + setup.getOut().getProtocol()).hashCode(); + } +} diff --git a/translator/src/main/java/eu/arrowhead/core/translator/services/translator/common/ContentTranslator.java b/translator/src/main/java/eu/arrowhead/core/translator/services/translator/common/ContentTranslator.java new file mode 100644 index 000000000..98efb1530 --- /dev/null +++ b/translator/src/main/java/eu/arrowhead/core/translator/services/translator/common/ContentTranslator.java @@ -0,0 +1,136 @@ +package eu.arrowhead.core.translator.services.translator.common; + +import com.google.gson.Gson; +import com.moandjiezana.toml.Toml; +import com.moandjiezana.toml.TomlWriter; +import com.upokecenter.cbor.CBORObject; +import org.json.JSONObject; +import org.json.XML; + +import eu.arrowhead.core.translator.services.translator.common.Translation.ContentType; + +public class ContentTranslator { + // CBOR + public static String CBOR2JSON(String cbor) { + int len = cbor.length(); + byte[] bytes = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + bytes[i / 2] = (byte) ((Character.digit(cbor.charAt(i), 16) << 4) + + Character.digit(cbor.charAt(i + 1), 16)); + } + return CBORObject.DecodeSequenceFromBytes(bytes)[0].ToJSONString(); + } + + public static String CBOR2TOML(String cbor) { + return JSON2TOML(CBOR2JSON(cbor)); + } + + public static String CBOR2XML(String cbor) { + return JSON2XML(CBOR2JSON(cbor)); + } + + // JSON + public static String JSON2CBOR(String json) { + byte[] bytes = CBORObject.FromJSONString(new JSONObject(json).toString(10)).EncodeToBytes(); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < bytes.length; i++) { + sb.append(String.format("%02X", bytes[i])); + } + return "" + sb.toString(); + } + + public static String JSON2TOML(String json) { + TomlWriter tomlWriter = new TomlWriter.Builder() + .indentValuesBy(2) + .indentTablesBy(4) + .padArrayDelimitersBy(3) + .build(); + return tomlWriter.write(new JSONObject(json)); + } + + public static String JSON2XML(String json) { + return XML.toString(new JSONObject(json)); + } + + // TOML + public static String TOML2CBOR(String toml) { + return JSON2CBOR(TOML2JSON(toml)); + } + + public static String TOML2JSON(String toml) { + Toml t = new Toml().read(toml); + return new Gson().toJson(new Gson().toJsonTree(t).getAsJsonObject().get("values")); + } + + public static String TOML2XML(String toml) { + return JSON2XML(TOML2JSON(toml)); + } + + // XML + public static String XML2CBOR(String xml) { + return JSON2CBOR(XML2JSON(xml)); + } + + public static String XML2JSON(String xml) { + return XML.toJSONObject(xml, true).toString(); + } + + public static String XML2TOML(String xml) { + return JSON2TOML(XML2JSON(xml)); + } + + public static byte[] translate(ContentType to, ContentType from, byte[] payload) { + try { + String content = new String(payload); + + // CBOR + if (from == ContentType.CBOR && to == ContentType.JSON) { + return CBOR2JSON(content).getBytes(); + } + if (from == ContentType.CBOR && to == ContentType.TEXT) { + return CBOR2TOML(content).getBytes(); + } + if (from == ContentType.CBOR && to == ContentType.XML) { + return CBOR2XML(content).getBytes(); + } + + // JSON + if (from == ContentType.JSON && to == ContentType.CBOR) { + return JSON2CBOR(content).getBytes(); + } + if (from == ContentType.JSON && to == ContentType.TEXT) { + return JSON2TOML(content).getBytes(); + } + if (from == ContentType.JSON && to == ContentType.XML) { + return JSON2XML(content).getBytes(); + } + + // TEXT + if (from == ContentType.TEXT && to == ContentType.CBOR) { + return TOML2CBOR(content).getBytes(); + } + if (from == ContentType.TEXT && to == ContentType.JSON) { + return TOML2JSON(content).getBytes(); + } + if (from == ContentType.TEXT && to == ContentType.XML) { + return TOML2XML(content).getBytes(); + } + + // XML + if (from == ContentType.XML && to == ContentType.CBOR) { + return XML2CBOR(content).getBytes(); + } + if (from == ContentType.XML && to == ContentType.JSON) { + return XML2JSON(content).getBytes(); + } + if (from == ContentType.XML && to == ContentType.TEXT) { + return XML2TOML(content).getBytes(); + } + + } catch (Exception ex) { + // Ignore, it will return default. + } + + return payload; + } +} diff --git a/translator/src/main/java/eu/arrowhead/core/translator/services/translator/common/InterfaceDTO.java b/translator/src/main/java/eu/arrowhead/core/translator/services/translator/common/InterfaceDTO.java new file mode 100644 index 000000000..9a54833b3 --- /dev/null +++ b/translator/src/main/java/eu/arrowhead/core/translator/services/translator/common/InterfaceDTO.java @@ -0,0 +1,65 @@ +package eu.arrowhead.core.translator.services.translator.common; + +import java.io.Serializable; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + +import eu.arrowhead.core.translator.services.translator.common.Translation.ContentType; +import eu.arrowhead.core.translator.services.translator.common.Translation.Protocol; + +@JsonInclude(Include.NON_NULL) +public class InterfaceDTO implements Serializable { + + private static final long serialVersionUID = 3919207845124510215L; + private String ip; + private int port; + private final Protocol protocol; + private final ContentType contentType; + + @JsonCreator + public InterfaceDTO( + @JsonProperty("ip") String ip, + @JsonProperty("port") int port, + @JsonProperty("protocol") Protocol protocol, + @JsonProperty("contentType") ContentType contentType) { + this.ip = ip; + this.port = port; + this.protocol = protocol; + this.contentType = contentType; + } + + public InterfaceDTO(String ip, int port, Protocol protocol) { + this.ip = ip; + this.port = port; + this.protocol = protocol; + contentType = ContentType.ANY; + } + + public String getIp() { + return ip; + } + + public void setIp(String ip) { + this.ip = ip; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public Protocol getProtocol() { + return protocol; + } + + public ContentType getContentType() { + return contentType; + } + +} diff --git a/translator/src/main/java/eu/arrowhead/core/translator/services/translator/common/Translation.java b/translator/src/main/java/eu/arrowhead/core/translator/services/translator/common/Translation.java new file mode 100644 index 000000000..9521d6e4a --- /dev/null +++ b/translator/src/main/java/eu/arrowhead/core/translator/services/translator/common/Translation.java @@ -0,0 +1,208 @@ +package eu.arrowhead.core.translator.services.translator.common; + +import javax.servlet.http.HttpServletResponse; +import org.eclipse.californium.core.coap.CoAP.ResponseCode; + +public class Translation { + + public static enum Protocol { + UNKOWN(0), HTTP(1), COAP(2), MQTT(3), WS(4); + + public final int value; + + Protocol(int value) { + this.value = value; + } + + public static Protocol valueOf(final int value) { + switch (value) { + case 1: + return HTTP; + case 2: + return COAP; + case 3: + return MQTT; + case 4: + return WS; + default: + return UNKOWN; + } + } + } + + public static enum ContentType { + ANY(0), + TEXT(1), + XML(2), + JSON(3), + CBOR(4); + + public final int value; + + ContentType(int value) { + this.value = value; + } + + public static ContentType valueOf(final int value) { + switch (value) { + case 1: + return TEXT; + case 2: + return XML; + case 3: + return JSON; + case 4: + return CBOR; + default: + return ANY; + } + } + } + + public static String contentFormatFromCoap(int cf) { + switch (cf) { + case 0: + return "text/plain;charset=utf-8"; + case 40: + return "application/link-format"; + case 41: + return "application/xml"; + case 42: + return "application/octet-stream"; + case 47: + return "application/exi"; + case 50: + return "application/json"; + default: + return "text/plain"; + } + } + + public static int contentFormatToCoap(String cf) { + switch (cf) { + case "text/plain": + return 0; + case "application/link-format": + return 40; + case "application/xml": + return 41; + case "application/octet-stream": + return 42; + case "application/exi": + return 47; + case "application/json": + return 50; + default: + return 0; + } + } + + public static int statusFromCoap(ResponseCode rc) { + switch (rc) { + + // 2.xx + case CREATED: + return HttpServletResponse.SC_CREATED; + case DELETED: + return HttpServletResponse.SC_NO_CONTENT; + case VALID: + return HttpServletResponse.SC_NOT_MODIFIED; + case CHANGED: + return HttpServletResponse.SC_NO_CONTENT; + case CONTENT: + return HttpServletResponse.SC_OK; + + // 4.xx + case BAD_REQUEST: + return HttpServletResponse.SC_BAD_REQUEST; + case UNAUTHORIZED: + return HttpServletResponse.SC_UNAUTHORIZED; + case BAD_OPTION: + return HttpServletResponse.SC_BAD_REQUEST; + case FORBIDDEN: + return HttpServletResponse.SC_FORBIDDEN; + case NOT_FOUND: + return HttpServletResponse.SC_NOT_FOUND; + case METHOD_NOT_ALLOWED: + return HttpServletResponse.SC_METHOD_NOT_ALLOWED; + case NOT_ACCEPTABLE: + return HttpServletResponse.SC_NOT_ACCEPTABLE; + case PRECONDITION_FAILED: + return HttpServletResponse.SC_PRECONDITION_FAILED; + case REQUEST_ENTITY_TOO_LARGE: + return HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE; + case UNSUPPORTED_CONTENT_FORMAT: + return HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE; + + // 5.xx + case INTERNAL_SERVER_ERROR: + return HttpServletResponse.SC_INTERNAL_SERVER_ERROR; + case NOT_IMPLEMENTED: + return HttpServletResponse.SC_NOT_IMPLEMENTED; + case BAD_GATEWAY: + return HttpServletResponse.SC_BAD_GATEWAY; + case SERVICE_UNAVAILABLE: + return HttpServletResponse.SC_SERVICE_UNAVAILABLE; + case GATEWAY_TIMEOUT: + return HttpServletResponse.SC_GATEWAY_TIMEOUT; + + // Others - No way to match + default: + return HttpServletResponse.SC_INTERNAL_SERVER_ERROR; + } + } + + public static ResponseCode statusToCoap(int status) { + switch (status) { + + // 2xx + case HttpServletResponse.SC_OK: + return ResponseCode.CONTENT; + case HttpServletResponse.SC_CREATED: + return ResponseCode.CREATED; + case HttpServletResponse.SC_NO_CONTENT: + return ResponseCode.CHANGED; + + // 3xx + case HttpServletResponse.SC_NOT_MODIFIED: + return ResponseCode.VALID; + + // 4xx + case HttpServletResponse.SC_BAD_REQUEST: + return ResponseCode.BAD_REQUEST; + case HttpServletResponse.SC_UNAUTHORIZED: + return ResponseCode.UNAUTHORIZED; + case HttpServletResponse.SC_FORBIDDEN: + return ResponseCode.FORBIDDEN; + case HttpServletResponse.SC_NOT_FOUND: + return ResponseCode.NOT_FOUND; + case HttpServletResponse.SC_METHOD_NOT_ALLOWED: + return ResponseCode.METHOD_NOT_ALLOWED; + case HttpServletResponse.SC_NOT_ACCEPTABLE: + return ResponseCode.FORBIDDEN; + case HttpServletResponse.SC_PRECONDITION_FAILED: + return ResponseCode.PRECONDITION_FAILED; + case HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE: + return ResponseCode.REQUEST_ENTITY_TOO_LARGE; + case HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE: + return ResponseCode.UNSUPPORTED_CONTENT_FORMAT; + + // 5xx + case HttpServletResponse.SC_INTERNAL_SERVER_ERROR: + return ResponseCode.INTERNAL_SERVER_ERROR; + case HttpServletResponse.SC_NOT_IMPLEMENTED: + return ResponseCode.NOT_IMPLEMENTED; + case HttpServletResponse.SC_BAD_GATEWAY: + return ResponseCode.BAD_GATEWAY; + case HttpServletResponse.SC_SERVICE_UNAVAILABLE: + return ResponseCode.SERVICE_UNAVAILABLE; + case HttpServletResponse.SC_GATEWAY_TIMEOUT: + return ResponseCode.GATEWAY_TIMEOUT; + + // Others - No way to match + default: + return ResponseCode.INTERNAL_SERVER_ERROR; + } + } + +} diff --git a/translator/src/main/java/eu/arrowhead/core/translator/services/translator/common/TranslatorDef.java b/translator/src/main/java/eu/arrowhead/core/translator/services/translator/common/TranslatorDef.java deleted file mode 100644 index 64fef655b..000000000 --- a/translator/src/main/java/eu/arrowhead/core/translator/services/translator/common/TranslatorDef.java +++ /dev/null @@ -1,87 +0,0 @@ -package eu.arrowhead.core.translator.services.translator.common; - -import java.net.InetAddress; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.UnknownHostException; - -public class TranslatorDef { - - public enum Protocol { - coap (0), - http (1); - - public final int value; - - Protocol(int value) { - this.value = value; - } - - public static Protocol valueOf(final int value) { - switch (value) { - case 0: return coap; - case 1: return http; - default: throw new IllegalArgumentException("Unknown Protocol " + value); - } - } - } - - public static class EndPoint { - private final URI uri; - private final InetAddress ip; - private final String name; - - public EndPoint(String name, String address) throws URISyntaxException, UnknownHostException { - this.name = name; - uri = new URI(address); - ip = InetAddress.getByName(uri.getHost()); - } - - public String getName() { - return name; - } - - public Protocol getProtocol() { - return Protocol.valueOf(uri.getScheme().toLowerCase()); - } - - public String getHostName() { - return uri.getHost(); - } - - public int getPort() { - return uri.getPort(); - } - - public String getHostIpAddress() { - return ip.getHostAddress(); - } - - public boolean isLocal() { - return ip.isAnyLocalAddress() || ip.isLoopbackAddress(); - } - } - - public enum Method { - GET (0), - POST (1), - PUT (2), - DELETE (3); - - public final int value; - - Method(int value) { - this.value = value; - } - - public static Method valueOf(final int value) { - switch (value) { - case 0: return GET; - case 1: return POST; - case 2: return PUT; - case 3: return DELETE; - default: throw new IllegalArgumentException("Unknown Method " + value); - } - } - } -} diff --git a/translator/src/main/java/eu/arrowhead/core/translator/services/translator/common/TranslatorHubAccess.java b/translator/src/main/java/eu/arrowhead/core/translator/services/translator/common/TranslatorHubAccess.java deleted file mode 100644 index 6565dcb3a..000000000 --- a/translator/src/main/java/eu/arrowhead/core/translator/services/translator/common/TranslatorHubAccess.java +++ /dev/null @@ -1,25 +0,0 @@ -package eu.arrowhead.core.translator.services.translator.common; - -public class TranslatorHubAccess { - private final int id; - private final String ip; - private final int port; - - public TranslatorHubAccess(int tranlatorId, String ip, int port) { - id = tranlatorId; - this.ip = ip; - this.port = port; - } - - public int getTranslatorId() { - return id; - } - - public String getIp() { - return ip; - } - - public int getPort() { - return port; - } -} diff --git a/translator/src/main/java/eu/arrowhead/core/translator/services/translator/common/TranslatorHubDTO.java b/translator/src/main/java/eu/arrowhead/core/translator/services/translator/common/TranslatorHubDTO.java new file mode 100644 index 000000000..e5d2f8889 --- /dev/null +++ b/translator/src/main/java/eu/arrowhead/core/translator/services/translator/common/TranslatorHubDTO.java @@ -0,0 +1,32 @@ +package eu.arrowhead.core.translator.services.translator.common; + +import java.io.Serializable; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + +@JsonInclude(Include.NON_NULL) +public class TranslatorHubDTO implements Serializable { + private static final long serialVersionUID = 3919207845123510215L; + private final int id; + private final InterfaceDTO in; + private final InterfaceDTO out; + + public TranslatorHubDTO(int id, InterfaceDTO in, InterfaceDTO out) { + this.id = id; + this.in = in; + this.out = out; + } + + public int getId() { + return id; + } + + public InterfaceDTO getIn() { + return in; + } + + public InterfaceDTO getOut() { + return out; + } +} diff --git a/translator/src/main/java/eu/arrowhead/core/translator/services/translator/common/TranslatorSetup.java b/translator/src/main/java/eu/arrowhead/core/translator/services/translator/common/TranslatorSetup.java deleted file mode 100644 index db73ec47d..000000000 --- a/translator/src/main/java/eu/arrowhead/core/translator/services/translator/common/TranslatorSetup.java +++ /dev/null @@ -1,40 +0,0 @@ -package eu.arrowhead.core.translator.services.translator.common; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; - -public class TranslatorSetup { - - private final String producerName; - private final String producerAddress; - private final String consumerName; - private final String consumerAddress; - - @JsonCreator - public TranslatorSetup( - @JsonProperty("producerName") String producerName, - @JsonProperty("producerAddress") String producerAddress, - @JsonProperty("consumerName") String consumerName, - @JsonProperty("consumerAddress") String consumerAddress) { - this.producerName = producerName; - this.producerAddress = producerAddress; - this.consumerName = consumerName; - this.consumerAddress = consumerAddress; - } - - public String getProducerName() { - return producerName; - } - - public String getProducerAddress() { - return producerAddress; - } - - public String getConsumerName() { - return consumerName; - } - - public String getConsumerAddress() { - return consumerAddress; - } -} diff --git a/translator/src/main/java/eu/arrowhead/core/translator/services/translator/common/TranslatorSetupDTO.java b/translator/src/main/java/eu/arrowhead/core/translator/services/translator/common/TranslatorSetupDTO.java new file mode 100644 index 000000000..fe685db22 --- /dev/null +++ b/translator/src/main/java/eu/arrowhead/core/translator/services/translator/common/TranslatorSetupDTO.java @@ -0,0 +1,25 @@ +package eu.arrowhead.core.translator.services.translator.common; +import java.io.Serializable; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + +@JsonInclude(Include.NON_NULL) +public class TranslatorSetupDTO implements Serializable { + private static final long serialVersionUID = 3919207845125510215L; + private final InterfaceDTO in; + private final InterfaceDTO out; + + public TranslatorSetupDTO(InterfaceDTO in, InterfaceDTO out) { + this.in = in; + this.out = out; + } + + public InterfaceDTO getIn() { + return in; + } + + public InterfaceDTO getOut() { + return out; + } +} diff --git a/translator/src/main/java/eu/arrowhead/core/translator/services/translator/protocols/CoapIn.java b/translator/src/main/java/eu/arrowhead/core/translator/services/translator/protocols/CoapIn.java new file mode 100644 index 000000000..6f04ce202 --- /dev/null +++ b/translator/src/main/java/eu/arrowhead/core/translator/services/translator/protocols/CoapIn.java @@ -0,0 +1,153 @@ +package eu.arrowhead.core.translator.services.translator.protocols; + +import java.net.URI; +import java.net.URISyntaxException; +import javax.servlet.http.HttpServletResponse; +import org.eclipse.californium.core.CoapResource; +import org.eclipse.californium.core.CoapServer; +import org.eclipse.californium.core.coap.CoAP; +import org.eclipse.californium.core.coap.Request; +import org.eclipse.californium.core.coap.Response; +import org.eclipse.californium.core.network.config.NetworkConfig; +import org.eclipse.californium.core.server.resources.CoapExchange; +import org.eclipse.californium.core.server.resources.Resource; +import org.eclipse.jetty.http.MimeTypes; + +import eu.arrowhead.core.translator.services.translator.common.ContentTranslator; +import eu.arrowhead.core.translator.services.translator.common.Translation; + +public class CoapIn extends ProtocolIn { + + private final CoapServer coapServer; + // private final Resource resource; + + public CoapIn(URI uri) { + super(uri); + coapServer = new RootCoapServer(uri.getPort()); + coapServer.start(); + } + + private class RootCoapServer extends CoapServer { + + public RootCoapServer(final int... ports) { + super(NetworkConfig.getStandard(), ports); + } + + @Override + protected Resource createRoot() { + return new CustomRootResource(); + } + } + + private class CustomRootResource extends CoapResource { + + private byte[] payload = "Loading...".getBytes(); + private int rc = HttpServletResponse.SC_OK; + private String ct = MimeTypes.Type.TEXT_PLAIN.asString(); + + public CustomRootResource() { + super(""); + setObservable(true); + setObserveType(CoAP.Type.CON); + getAttributes().setObservable(); + } + + @Override + public Resource getChild(String name) { + return this; + } + + public void notifyObservers(InterProtocolResponse response) { + + // Translation + response.setContent( + ContentTranslator.translate(getContentType(), protocolOut.getContentType(), response.getContent())); + + payload = response.getContent(); + rc = response.getStatusCode(); + ct = response.getContentType(); + new Thread(() -> { + changed(); + }).start(); + } + + @Override + public void handleGET(CoapExchange exchange) { + + try { + Request request = exchange.advanced().getRequest(); + Response response = exchange.advanced().getResponse(); + URI uri = new URI(request.getURI()); + + if (request.isObserve()) { + if (response == null) { + new Thread(() -> { + protocolOut.observe(new InterProtocolRequest(uri.getPath(), uri.getQuery(), + Translation.contentFormatFromCoap(request.getOptions().getContentFormat()), null)); + }).start(); + } + sendResponse(exchange, new InterProtocolResponse(ct, rc, payload)); + + } else { + sendResponse(exchange, protocolOut.get(new InterProtocolRequest(uri.getPath(), uri.getQuery(), + Translation.contentFormatFromCoap(request.getOptions().getContentFormat()), null))); + } + + } catch (URISyntaxException ex) { + exchange.respond(CoAP.ResponseCode.INTERNAL_SERVER_ERROR, ex.getLocalizedMessage()); + } + } + + @Override + public void handlePOST(CoapExchange exchange) { + try { + Request request = exchange.advanced().getRequest(); + URI uri = new URI(request.getURI()); + + sendResponse(exchange, protocolOut.post(new InterProtocolRequest(uri.getPath(), uri.getQuery(), + Translation.contentFormatFromCoap(request.getOptions().getContentFormat()), request.getPayload()))); + } catch (URISyntaxException ex) { + exchange.respond(CoAP.ResponseCode.INTERNAL_SERVER_ERROR, ex.getLocalizedMessage()); + } + } + + @Override + public void handlePUT(CoapExchange exchange) { + try { + Request request = exchange.advanced().getRequest(); + URI uri = new URI(request.getURI()); + + sendResponse(exchange, protocolOut.put(new InterProtocolRequest(uri.getPath(), uri.getQuery(), + Translation.contentFormatFromCoap(request.getOptions().getContentFormat()), request.getPayload()))); + } catch (URISyntaxException ex) { + exchange.respond(CoAP.ResponseCode.INTERNAL_SERVER_ERROR, ex.getLocalizedMessage()); + } + } + + @Override + public void handleDELETE(CoapExchange exchange) { + try { + Request request = exchange.advanced().getRequest(); + URI uri = new URI(request.getURI()); + + sendResponse(exchange, protocolOut.delete(new InterProtocolRequest(uri.getPath(), uri.getQuery(), + Translation.contentFormatFromCoap(request.getOptions().getContentFormat()), null))); + } catch (URISyntaxException ex) { + exchange.respond(CoAP.ResponseCode.INTERNAL_SERVER_ERROR, ex.getLocalizedMessage()); + } + } + + // Private + private void sendResponse(CoapExchange exchange, InterProtocolResponse response) { + exchange.respond(Translation.statusToCoap(response.getStatusCode()), response.getContent(), + Translation.contentFormatToCoap(response.getContentType())); + } + + } + + @Override + synchronized void notifyObservers(InterProtocolResponse response) { + CustomRootResource rr = (CustomRootResource) coapServer.getRoot(); + rr.notifyObservers(response); + } +} diff --git a/translator/src/main/java/eu/arrowhead/core/translator/services/translator/protocols/CoapOut.java b/translator/src/main/java/eu/arrowhead/core/translator/services/translator/protocols/CoapOut.java new file mode 100644 index 000000000..45876200f --- /dev/null +++ b/translator/src/main/java/eu/arrowhead/core/translator/services/translator/protocols/CoapOut.java @@ -0,0 +1,149 @@ +package eu.arrowhead.core.translator.services.translator.protocols; + +import java.io.IOException; +import java.net.URI; +import javax.servlet.http.HttpServletResponse; +import org.eclipse.californium.core.CoapClient; +import org.eclipse.californium.core.CoapHandler; +import org.eclipse.californium.core.CoapResponse; +import org.eclipse.californium.elements.exception.ConnectorException; +import org.eclipse.jetty.http.MimeTypes; + +import eu.arrowhead.core.translator.services.translator.common.Translation; + +public class CoapOut extends ProtocolOut { + + public CoapOut(URI uri) throws Exception { + super(uri); + } + + @Override + public InterProtocolResponse get(InterProtocolRequest request) { + String remoteRequest = uri.toString() + request.getPath() + + (request.getQueries() == null ? "" : "?" + request.getQueries()); + try { + CoapClient coapClient = new CoapClient(remoteRequest); + + CoapResponse response = coapClient.get(); + + return new InterProtocolResponse( + Translation.contentFormatFromCoap(response.getOptions().getContentFormat()), + Translation.statusFromCoap(response.getCode()), response.getPayload()); + + } catch (ConnectorException ex) { + return new InterProtocolResponse(MimeTypes.Type.TEXT_PLAIN, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, + ex.getLocalizedMessage().getBytes()); + } catch (IOException ex) { + return new InterProtocolResponse(MimeTypes.Type.TEXT_PLAIN, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, + ex.getLocalizedMessage().getBytes()); + } + } + + @Override + public InterProtocolResponse post(InterProtocolRequest request) { + String remoteRequest = uri.toString() + request.getPath() + "?" + request.getQueries(); + try { + CoapClient coapClient = new CoapClient(remoteRequest); + + CoapResponse response = coapClient.post(request.getContent(), + Translation.contentFormatToCoap(request.getContentType())); + + return new InterProtocolResponse( + Translation.contentFormatFromCoap(response.getOptions().getContentFormat()), + Translation.statusFromCoap(response.getCode()), response.getPayload()); + + } catch (ConnectorException ex) { + return new InterProtocolResponse(MimeTypes.Type.TEXT_PLAIN, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, + ex.getLocalizedMessage().getBytes()); + } catch (IOException ex) { + return new InterProtocolResponse(MimeTypes.Type.TEXT_PLAIN, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, + ex.getLocalizedMessage().getBytes()); + } + } + + @Override + public InterProtocolResponse put(InterProtocolRequest request) { + String remoteRequest = uri.toString() + request.getPath() + "?" + request.getQueries(); + try { + CoapClient coapClient = new CoapClient(remoteRequest); + + CoapResponse response = coapClient.put(request.getContent(), + Translation.contentFormatToCoap(request.getContentType())); + + return new InterProtocolResponse( + Translation.contentFormatFromCoap(response.getOptions().getContentFormat()), + Translation.statusFromCoap(response.getCode()), response.getPayload()); + + } catch (ConnectorException ex) { + return new InterProtocolResponse(MimeTypes.Type.TEXT_PLAIN, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, + ex.getLocalizedMessage().getBytes()); + } catch (IOException ex) { + return new InterProtocolResponse(MimeTypes.Type.TEXT_PLAIN, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, + ex.getLocalizedMessage().getBytes()); + } + } + + @Override + public InterProtocolResponse delete(InterProtocolRequest request) { + String remoteRequest = uri.toString() + request.getPath() + "?" + request.getQueries(); + try { + CoapClient coapClient = new CoapClient(remoteRequest); + + CoapResponse response = coapClient.delete(); + + return new InterProtocolResponse( + Translation.contentFormatFromCoap(response.getOptions().getContentFormat()), + Translation.statusFromCoap(response.getCode()), response.getPayload()); + + } catch (ConnectorException ex) { + return new InterProtocolResponse(MimeTypes.Type.TEXT_PLAIN, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, + ex.getLocalizedMessage().getBytes()); + } catch (IOException ex) { + return new InterProtocolResponse(MimeTypes.Type.TEXT_PLAIN, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, + ex.getLocalizedMessage().getBytes()); + } + } + + @Override + public InterProtocolResponse observe(InterProtocolRequest request) { + String remoteRequest = uri.toString() + request.getPath() + "?" + request.getQueries(); + try { + CoapClient coapClient = new CoapClient(remoteRequest); + + CoapResponse response = coapClient.get(); + + coapClient.observe(new CoapHandler() { + @Override + public void onLoad(CoapResponse response) { + + new Thread(() -> { + protocolIn.notifyObservers(new InterProtocolResponse( + Translation.contentFormatFromCoap(response.getOptions().getContentFormat()), + Translation.statusFromCoap(response.getCode()), response.getPayload())); + + }).start(); + } + + @Override + public void onError() { + new Thread(() -> { + protocolIn.notifyObservers(new InterProtocolResponse(MimeTypes.Type.TEXT_PLAIN, + HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "CoAP observe Error".getBytes())); + }).start(); + } + }); + + return new InterProtocolResponse( + Translation.contentFormatFromCoap(response.getOptions().getContentFormat()), + response.getCode().value, response.getPayload()); + + } catch (ConnectorException ex) { + return new InterProtocolResponse(MimeTypes.Type.TEXT_PLAIN, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, + ex.getLocalizedMessage().getBytes()); + } catch (IOException ex) { + return new InterProtocolResponse(MimeTypes.Type.TEXT_PLAIN, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, + ex.getLocalizedMessage().getBytes()); + } + } + +} diff --git a/translator/src/main/java/eu/arrowhead/core/translator/services/translator/protocols/HttpIn.java b/translator/src/main/java/eu/arrowhead/core/translator/services/translator/protocols/HttpIn.java new file mode 100644 index 000000000..80f5f5c5d --- /dev/null +++ b/translator/src/main/java/eu/arrowhead/core/translator/services/translator/protocols/HttpIn.java @@ -0,0 +1,200 @@ +package eu.arrowhead.core.translator.services.translator.protocols; + +import java.io.IOException; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.apache.commons.io.IOUtils; +import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.server.AsyncContextState; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.servlet.ServletHandler; +import org.eclipse.jetty.servlet.ServletHolder; + +import eu.arrowhead.core.translator.services.translator.common.ContentTranslator; + +public class HttpIn extends ProtocolIn { + + private final Server httpServer = new Server(); + private final HttpService httpService = new HttpService(); + + public HttpIn(URI uri) throws Exception { + super(uri); + HttpConfiguration config = new HttpConfiguration(); + HttpConnectionFactory httpConnectionFactory = new HttpConnectionFactory(config); + ServerConnector httpConnector = new ServerConnector(httpServer, httpConnectionFactory); + httpConnector.setPort(uri.getPort()); + httpServer.addConnector(httpConnector); + ServletHandler handler = new ServletHandler(); + handler.addServletWithMapping(new ServletHolder(httpService), "/*"); + httpServer.setHandler(handler); + httpServer.start(); + } + + private class HttpService extends HttpServlet { + + private ArrayList contexts = new ArrayList(); + + @Override + public void service(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + if (request.getMethod().equalsIgnoreCase("PATCH")) { + doPatch(request, response); + } else { + super.service(request, response); + } + } + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { + + if (req.getHeader("Connection") == null || !req.getHeader("Connection").equals("keep-alive")) { + + InterProtocolResponse ipr = protocolOut.get(new InterProtocolRequest( + req.getPathInfo(), + req.getQueryString(), + req.getContentType(), + null)); + + sendResponse(resp, ipr); + return; + } + + resp.setStatus(HttpServletResponse.SC_OK); + resp.setContentType("text/event-stream"); + resp.setCharacterEncoding(StandardCharsets.UTF_8.name()); + resp.flushBuffer(); + AsyncContextState async = (AsyncContextState) req.startAsync(); + contexts.add(async); + protocolOut.observe( + new InterProtocolRequest( + req.getPathInfo(), + req.getQueryString(), + req.getContentType(), + null)); + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { + byte[] content = new byte[] {}; + try { + content = getContentAsByteArray(req); + } catch (IOException ex) { + sendResponse(resp, new InterProtocolResponse( + MimeTypes.Type.TEXT_PLAIN, + HttpServletResponse.SC_INTERNAL_SERVER_ERROR, + ex.getLocalizedMessage().getBytes())); + } + sendResponse(resp, protocolOut.post( + new InterProtocolRequest( + req.getPathInfo(), + req.getQueryString(), + req.getContentType(), + content))); + } + + // @Override + protected void doPatch(HttpServletRequest req, HttpServletResponse resp) throws IOException { + byte[] content = new byte[] {}; + try { + content = getContentAsByteArray(req); + } catch (IOException ex) { + sendResponse(resp, new InterProtocolResponse( + MimeTypes.Type.TEXT_PLAIN, + HttpServletResponse.SC_INTERNAL_SERVER_ERROR, + ex.getLocalizedMessage().getBytes())); + } + sendResponse(resp, protocolOut.patch( + new InterProtocolRequest( + req.getPathInfo(), + req.getQueryString(), + req.getContentType(), + content))); + } + + @Override + protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IOException { + byte[] content = new byte[] {}; + try { + content = getContentAsByteArray(req); + } catch (IOException ex) { + sendResponse(resp, new InterProtocolResponse( + MimeTypes.Type.TEXT_PLAIN, + HttpServletResponse.SC_INTERNAL_SERVER_ERROR, + ex.getLocalizedMessage().getBytes())); + } + sendResponse(resp, protocolOut.put( + new InterProtocolRequest( + req.getPathInfo(), + req.getQueryString(), + req.getContentType(), + content))); + } + + @Override + protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws IOException { + sendResponse(resp, protocolOut.delete( + new InterProtocolRequest( + req.getPathInfo(), + req.getQueryString(), + req.getContentType(), + null))); + } + + // PRIVATE + private void sendResponse(HttpServletResponse resp, InterProtocolResponse ipr) throws IOException { + resp.setContentType(ipr.getContentType()); + if (ipr.getContent() != null) { + resp.getOutputStream().write(ipr.getContent()); + } + resp.setStatus(ipr.getStatusCode()); + } + + private byte[] getContentAsByteArray(HttpServletRequest req) throws IOException { + if (req.getContentLength() <= 0) { + return null; + } + byte[] b = IOUtils.toByteArray(req.getInputStream()); + return b; + } + + public void notifyObservers(InterProtocolResponse response) { + + // Translation + response.setContent( + ContentTranslator.translate(getContentType(), protocolOut.getContentType(), response.getContent())); + + contexts.forEach(cont -> { + try { + ServletOutputStream outputStream = cont.getResponse().getOutputStream(); + if (outputStream.isReady()) { + if (response.getContent() == null) { + outputStream.write("".getBytes(), 0, "".getBytes().length); + } else { + outputStream.write(response.getContent(), 0, response.getContent().length); + } + outputStream.flush(); + cont.getResponse().flushBuffer(); + } else { + // Not ready, ignore + } + } catch (IOException ex) { // Ignore + } + + }); + } + } + + @Override + synchronized void notifyObservers(InterProtocolResponse response) { + httpService.notifyObservers(response); + } +} diff --git a/translator/src/main/java/eu/arrowhead/core/translator/services/translator/protocols/HttpOut.java b/translator/src/main/java/eu/arrowhead/core/translator/services/translator/protocols/HttpOut.java new file mode 100644 index 000000000..1cfce0dda --- /dev/null +++ b/translator/src/main/java/eu/arrowhead/core/translator/services/translator/protocols/HttpOut.java @@ -0,0 +1,259 @@ +package eu.arrowhead.core.translator.services.translator.protocols; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import javax.servlet.http.HttpServletResponse; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.client.api.Response; +import org.eclipse.jetty.client.util.BytesContentProvider; +import org.eclipse.jetty.client.util.InputStreamResponseListener; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.MimeTypes; + +public class HttpOut extends ProtocolOut { + + private final static String GET = "GET"; + private final static String POST = "POST"; + private final static String PUT = "PUT"; + private final static String PATCH = "PATCH"; + private final static String DELETE = "DELETE"; + + private final HttpClient client; + + public HttpOut(URI uri) throws Exception { + super(uri); + client = new HttpClient(); + client.start(); + } + + @Override + public InterProtocolResponse get(InterProtocolRequest request) { + String remoteRequest; + try { + remoteRequest = new URI(uri.getScheme(), uri.getHost() + ":" + uri.getPort() + uri.getPath(), + request.getPath(), request.getQueries()).toString(); + } catch (URISyntaxException ex) { + remoteRequest = uri.toString(); + } + try { + Request httpReq = client.POST(remoteRequest); + httpReq.method(GET); + httpReq.header(HttpHeader.CONNECTION, ""); + httpReq.header(HttpHeader.CONTENT_TYPE, request.getContentType()); + ContentResponse res = httpReq.send(); + + return new InterProtocolResponse( + res.getMediaType(), + res.getStatus(), + res.getContent()); + + } catch (InterruptedException | ExecutionException ex) { + return new InterProtocolResponse( + MimeTypes.Type.TEXT_PLAIN, + HttpServletResponse.SC_INTERNAL_SERVER_ERROR, + ex.getLocalizedMessage().getBytes()); + } catch (TimeoutException ex) { + return new InterProtocolResponse( + MimeTypes.Type.TEXT_PLAIN, + HttpServletResponse.SC_REQUEST_TIMEOUT, + ex.getLocalizedMessage().getBytes()); + } + + } + + @Override + public InterProtocolResponse post(InterProtocolRequest request) { + String remoteRequest; + try { + remoteRequest = new URI(uri.getScheme(), uri.getHost() + ":" + uri.getPort() + uri.getPath(), + request.getPath(), request.getQueries()).toString(); + } catch (URISyntaxException ex) { + remoteRequest = uri.toString(); + } + try { + Request httpReq = client.POST(remoteRequest); + httpReq.method(POST); + httpReq.header(HttpHeader.CONNECTION, ""); + httpReq.header(HttpHeader.CONTENT_TYPE, request.getContentType()); + httpReq.content(new BytesContentProvider(request.getContent())); + ContentResponse res = httpReq.send(); + return new InterProtocolResponse( + res.getMediaType(), + res.getStatus(), + res.getContent()); + } catch (InterruptedException | ExecutionException ex) { + return new InterProtocolResponse( + MimeTypes.Type.TEXT_PLAIN, + HttpServletResponse.SC_INTERNAL_SERVER_ERROR, + ex.getLocalizedMessage().getBytes()); + } catch (TimeoutException ex) { + return new InterProtocolResponse( + MimeTypes.Type.TEXT_PLAIN, + HttpServletResponse.SC_REQUEST_TIMEOUT, + ex.getLocalizedMessage().getBytes()); + } + } + + @Override + public InterProtocolResponse put(InterProtocolRequest request) { + String remoteRequest; + try { + remoteRequest = new URI(uri.getScheme(), uri.getHost() + ":" + uri.getPort() + uri.getPath(), + request.getPath(), request.getQueries()).toString(); + } catch (URISyntaxException ex) { + remoteRequest = uri.toString(); + } + try { + Request httpReq = client.POST(remoteRequest); + httpReq.method(PUT); + httpReq.header(HttpHeader.CONNECTION, ""); + httpReq.header(HttpHeader.CONTENT_TYPE, request.getContentType()); + httpReq.content(new BytesContentProvider(request.getContent())); + ContentResponse res = httpReq.send(); + return new InterProtocolResponse( + res.getMediaType(), + res.getStatus(), + res.getContent()); + } catch (InterruptedException | ExecutionException ex) { + return new InterProtocolResponse( + MimeTypes.Type.TEXT_PLAIN, + HttpServletResponse.SC_INTERNAL_SERVER_ERROR, + ex.getLocalizedMessage().getBytes()); + } catch (TimeoutException ex) { + return new InterProtocolResponse( + MimeTypes.Type.TEXT_PLAIN, + HttpServletResponse.SC_REQUEST_TIMEOUT, + ex.getLocalizedMessage().getBytes()); + } + } + + @Override + public InterProtocolResponse patch(InterProtocolRequest request) { + String remoteRequest; + try { + remoteRequest = new URI(uri.getScheme(), uri.getHost() + ":" + uri.getPort() + uri.getPath(), + request.getPath(), request.getQueries()).toString(); + } catch (URISyntaxException ex) { + remoteRequest = uri.toString(); + } + try { + Request httpReq = client.POST(remoteRequest); + httpReq.method(PATCH); + httpReq.header(HttpHeader.CONNECTION, ""); + httpReq.header(HttpHeader.CONTENT_TYPE, request.getContentType()); + httpReq.content(new BytesContentProvider(request.getContent())); + ContentResponse res = httpReq.send(); + return new InterProtocolResponse( + res.getMediaType(), + res.getStatus(), + res.getContent()); + } catch (InterruptedException | ExecutionException ex) { + return new InterProtocolResponse( + MimeTypes.Type.TEXT_PLAIN, + HttpServletResponse.SC_INTERNAL_SERVER_ERROR, + ex.getLocalizedMessage().getBytes()); + } catch (TimeoutException ex) { + return new InterProtocolResponse( + MimeTypes.Type.TEXT_PLAIN, + HttpServletResponse.SC_REQUEST_TIMEOUT, + ex.getLocalizedMessage().getBytes()); + } + } + + @Override + public InterProtocolResponse delete(InterProtocolRequest request) { + String remoteRequest; + try { + remoteRequest = new URI(uri.getScheme(), uri.getHost() + ":" + uri.getPort() + uri.getPath(), + request.getPath(), request.getQueries()).toString(); + } catch (URISyntaxException ex) { + remoteRequest = uri.toString(); + } + try { + Request httpReq = client.POST(remoteRequest); + httpReq.method(DELETE); + httpReq.header(HttpHeader.CONNECTION, ""); + httpReq.header(HttpHeader.CONTENT_TYPE, request.getContentType()); + ContentResponse res = httpReq.send(); + return new InterProtocolResponse( + res.getMediaType(), + res.getStatus(), + res.getContent()); + } catch (InterruptedException | ExecutionException ex) { + return new InterProtocolResponse( + MimeTypes.Type.TEXT_PLAIN, + HttpServletResponse.SC_INTERNAL_SERVER_ERROR, + ex.getLocalizedMessage().getBytes()); + } catch (TimeoutException ex) { + return new InterProtocolResponse( + MimeTypes.Type.TEXT_PLAIN, + HttpServletResponse.SC_REQUEST_TIMEOUT, + ex.getLocalizedMessage().getBytes()); + } + } + + @Override + public InterProtocolResponse observe(InterProtocolRequest request) { + String remoteRequest; + try { + remoteRequest = new URI(uri.getScheme(), uri.getHost() + ":" + uri.getPort() + uri.getPath(), + request.getPath(), request.getQueries()).toString(); + } catch (URISyntaxException ex) { + remoteRequest = uri.toString(); + } + try { + Request httpReq = client.POST(remoteRequest); + httpReq.method(GET); + httpReq.header(HttpHeader.CONNECTION, "keep-alive"); + httpReq.header(HttpHeader.CONTENT_TYPE, request.getContentType()); + InputStreamResponseListener listener = new InputStreamResponseListener(); + httpReq.send(listener); + Response response = listener.get(10, TimeUnit.SECONDS); + if (response.getStatus() == 200) { + new Thread(() -> { + try (InputStream responseContent = listener.getInputStream()) { + + ByteArrayOutputStream result = new ByteArrayOutputStream(); + byte[] buffer = new byte[2048]; + int length; + while ((length = responseContent.read(buffer)) != -1) { + result.write(buffer, 0, length); + + protocolIn.notifyObservers(new InterProtocolResponse( + MimeTypes.Type.TEXT_PLAIN, + response.getStatus(), + buffer)); + buffer = new byte[2048]; + } + } catch (IOException ex) { + // Ignore + } + }).start(); + } + return new InterProtocolResponse( + MimeTypes.Type.TEXT_PLAIN, + response.getStatus(), + "Stop Observing".getBytes()); + + } catch (InterruptedException | ExecutionException ex) { + return new InterProtocolResponse( + MimeTypes.Type.TEXT_PLAIN, + HttpServletResponse.SC_INTERNAL_SERVER_ERROR, + ex.getLocalizedMessage().getBytes()); + } catch (TimeoutException ex) { + return new InterProtocolResponse( + MimeTypes.Type.TEXT_PLAIN, + HttpServletResponse.SC_REQUEST_TIMEOUT, + ex.getLocalizedMessage() != null ? ex.getLocalizedMessage().getBytes() : "Timeout".getBytes()); + } + + } +} diff --git a/translator/src/main/java/eu/arrowhead/core/translator/services/translator/protocols/InterProtocolRequest.java b/translator/src/main/java/eu/arrowhead/core/translator/services/translator/protocols/InterProtocolRequest.java new file mode 100644 index 000000000..fac63c286 --- /dev/null +++ b/translator/src/main/java/eu/arrowhead/core/translator/services/translator/protocols/InterProtocolRequest.java @@ -0,0 +1,40 @@ +package eu.arrowhead.core.translator.services.translator.protocols; + +import org.eclipse.jetty.http.MimeTypes; + +public class InterProtocolRequest { + private final String path; + private final String queries; + private final String contentType; + private final byte[] content; + + public InterProtocolRequest(String path, String queries, MimeTypes.Type contentType, byte[] content) { + this.path = path; + this.queries = queries; + this.contentType = contentType.asString(); + this.content = content; + } + + public InterProtocolRequest(String path, String queries, String contentType, byte[] content) { + this.path = path; + this.queries = queries; + this.contentType = (contentType == null) ? MimeTypes.Type.TEXT_PLAIN.asString() : contentType; + this.content = content; + } + + public String getPath() { + return path; + } + + public String getQueries() { + return queries; + } + + public String getContentType() { + return contentType; + } + + public byte[] getContent() { + return content; + } +} diff --git a/translator/src/main/java/eu/arrowhead/core/translator/services/translator/protocols/InterProtocolResponse.java b/translator/src/main/java/eu/arrowhead/core/translator/services/translator/protocols/InterProtocolResponse.java new file mode 100644 index 000000000..9197e902b --- /dev/null +++ b/translator/src/main/java/eu/arrowhead/core/translator/services/translator/protocols/InterProtocolResponse.java @@ -0,0 +1,50 @@ +package eu.arrowhead.core.translator.services.translator.protocols; + +import javax.servlet.http.HttpServletResponse; +import org.eclipse.jetty.http.MimeTypes; + +public class InterProtocolResponse { + + private final String contentType; + private final int statusCode; + private byte[] content; + + public InterProtocolResponse() { + contentType = MimeTypes.Type.TEXT_PLAIN.asString(); + statusCode = HttpServletResponse.SC_NOT_IMPLEMENTED; + content = "Not Implemented".getBytes(); + } + + public InterProtocolResponse(String contentType, int statusCode, byte[] content) { + this.contentType = contentType; + this.statusCode = statusCode; + this.content = content; + } + + public InterProtocolResponse(MimeTypes.Type contentType, int statusCode, byte[] content) { + this.contentType = contentType.asString(); + this.statusCode = statusCode; + this.content = content; + } + + public String getContentType() { + return contentType; + } + + public int getStatusCode() { + return statusCode; + } + + public void setContent(byte[] content) { + this.content = content; + } + + public byte[] getContent() { + return content; + } + + public String getContentAsString() { + return new String(content); + } + +} diff --git a/translator/src/main/java/eu/arrowhead/core/translator/services/translator/protocols/MqttIn.java b/translator/src/main/java/eu/arrowhead/core/translator/services/translator/protocols/MqttIn.java new file mode 100644 index 000000000..500805d67 --- /dev/null +++ b/translator/src/main/java/eu/arrowhead/core/translator/services/translator/protocols/MqttIn.java @@ -0,0 +1,54 @@ +package eu.arrowhead.core.translator.services.translator.protocols; + +import java.net.URI; + +import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.paho.client.mqttv3.MqttClient; +import org.eclipse.paho.client.mqttv3.MqttException; +import org.eclipse.paho.client.mqttv3.MqttMessage; +import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; + +import eu.arrowhead.core.translator.services.translator.common.ContentTranslator; + +public class MqttIn extends ProtocolIn { + private MqttClient client; + private final MemoryPersistence persistence = new MemoryPersistence(); + private String topicId; + private final String broker = "tcp://127.0.0.1:1883"; + + public MqttIn(URI uri) throws Exception { + super(uri); + topicId = "" + uri.getPort(); + client = new MqttClient(broker, topicId, persistence); + new Thread(() -> { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + } + protocolOut.observe(new InterProtocolRequest("", "", MimeTypes.Type.TEXT_PLAIN, null)); + }).start(); + + } + + private void publish(String topic, String msg) { + try { + if (!client.isConnected()) { + client.connect(); + } + client.publish(topic, new MqttMessage(msg.getBytes())); + } catch (MqttException ex) { + // Ignore + } + } + + @Override + synchronized void notifyObservers(InterProtocolResponse response) { + + // Translation + response.setContent( + ContentTranslator.translate(getContentType(), protocolOut.getContentType(), response.getContent())); + + publish(topicId + "/out", response.getContentAsString()); + } + +} diff --git a/translator/src/main/java/eu/arrowhead/core/translator/services/translator/protocols/MqttOut.java b/translator/src/main/java/eu/arrowhead/core/translator/services/translator/protocols/MqttOut.java new file mode 100644 index 000000000..75ea074bc --- /dev/null +++ b/translator/src/main/java/eu/arrowhead/core/translator/services/translator/protocols/MqttOut.java @@ -0,0 +1,86 @@ +package eu.arrowhead.core.translator.services.translator.protocols; + +import java.net.URI; +import java.util.Random; + +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.http.MimeTypes.Type; +import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; +import org.eclipse.paho.client.mqttv3.MqttCallback; +import org.eclipse.paho.client.mqttv3.MqttClient; +import org.eclipse.paho.client.mqttv3.MqttException; +import org.eclipse.paho.client.mqttv3.MqttMessage; +import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; + +public class MqttOut extends ProtocolOut { + private MqttClient client; + private final MemoryPersistence persistence = new MemoryPersistence(); + private String topicId; + private final String broker = "tcp://127.0.0.1:1883"; + private byte[] payloadBuffer = "Web Socket Connected".getBytes(); + + public MqttOut(URI uri) throws Exception { + super(uri); + topicId = "" + uri.getPort(); + client = new MqttClient(broker, getRandomString(), persistence); + + client.setCallback(new MqttCallback() { + + @Override + public void connectionLost(Throwable thrwbl) { + } + + @Override + public void messageArrived(String topic, MqttMessage message) throws Exception { + + protocolIn.notifyObservers( + new InterProtocolResponse(Type.TEXT_PLAIN, + HttpServletResponse.SC_OK, message.getPayload())); + + } + + @Override + public void deliveryComplete(IMqttDeliveryToken imdt) { + } + }); + + subscribe(topicId + "/out"); + + } + + private void subscribe(String topic) { + try { + if (!client.isConnected()) { + client.connect(); + } + client.subscribe(topic, 0); + } catch (MqttException ex) { + // Ignore + } + } + + @Override + public InterProtocolResponse observe(InterProtocolRequest request) { + + return new InterProtocolResponse( + MimeTypes.Type.TEXT_PLAIN, + HttpServletResponse.SC_OK, + payloadBuffer); + } + + private String getRandomString() { + int leftLimit = 97; + int rightLimit = 122; + int targetStringLength = 10; + Random random = new Random(); + StringBuilder buffer = new StringBuilder(targetStringLength); + for (int i = 0; i < targetStringLength; i++) { + int randomLimitedInt = leftLimit + (int) (random.nextFloat() * (rightLimit - leftLimit + 1)); + buffer.append((char) randomLimitedInt); + } + String generatedString = buffer.toString().toUpperCase(); + return generatedString; + } +} diff --git a/translator/src/main/java/eu/arrowhead/core/translator/services/translator/protocols/ProtocolIn.java b/translator/src/main/java/eu/arrowhead/core/translator/services/translator/protocols/ProtocolIn.java new file mode 100644 index 000000000..3c14f4659 --- /dev/null +++ b/translator/src/main/java/eu/arrowhead/core/translator/services/translator/protocols/ProtocolIn.java @@ -0,0 +1,33 @@ +package eu.arrowhead.core.translator.services.translator.protocols; + +import java.net.URI; + +import eu.arrowhead.core.translator.services.translator.common.Translation.ContentType; + +public class ProtocolIn { + + final URI uri; + ProtocolOut protocolOut; + ContentType contentType = ContentType.ANY; + + public ProtocolIn(URI uri) { + this.uri = uri; + } + + public void setProtocolOut(ProtocolOut protocolOut) { + this.protocolOut = protocolOut; + } + + public void setContentType(ContentType contentType) { + this.contentType = contentType; + } + + public ContentType getContentType() { + return contentType; + } + + synchronized void notifyObservers(InterProtocolResponse response) { + throw new UnsupportedOperationException("Not supported yet."); + } + +} diff --git a/translator/src/main/java/eu/arrowhead/core/translator/services/translator/protocols/ProtocolOut.java b/translator/src/main/java/eu/arrowhead/core/translator/services/translator/protocols/ProtocolOut.java new file mode 100644 index 000000000..7da1416a9 --- /dev/null +++ b/translator/src/main/java/eu/arrowhead/core/translator/services/translator/protocols/ProtocolOut.java @@ -0,0 +1,53 @@ +package eu.arrowhead.core.translator.services.translator.protocols; + +import java.net.URI; + +import eu.arrowhead.core.translator.services.translator.common.Translation.ContentType; + +public class ProtocolOut { + + final URI uri; + ProtocolIn protocolIn; + ContentType contentType = ContentType.ANY; + + public ProtocolOut(URI uri) { + this.uri = uri; + } + + public void setProtocolIn(ProtocolIn protocolIn) { + this.protocolIn = protocolIn; + } + + public void setContentType(ContentType contentType) { + this.contentType = contentType; + } + + public ContentType getContentType() { + return contentType; + } + + public InterProtocolResponse get(InterProtocolRequest request) { + return new InterProtocolResponse(); + } + + public InterProtocolResponse post(InterProtocolRequest request) { + return new InterProtocolResponse(); + } + + public InterProtocolResponse put(InterProtocolRequest request) { + return new InterProtocolResponse(); + } + + public InterProtocolResponse patch(InterProtocolRequest request) { + return new InterProtocolResponse(); + } + + public InterProtocolResponse delete(InterProtocolRequest request) { + return new InterProtocolResponse(); + } + + public InterProtocolResponse observe(InterProtocolRequest request) { + return new InterProtocolResponse(); + } + +} diff --git a/translator/src/main/java/eu/arrowhead/core/translator/services/translator/protocols/WsIn.java b/translator/src/main/java/eu/arrowhead/core/translator/services/translator/protocols/WsIn.java new file mode 100644 index 000000000..f04eb5416 --- /dev/null +++ b/translator/src/main/java/eu/arrowhead/core/translator/services/translator/protocols/WsIn.java @@ -0,0 +1,159 @@ +package eu.arrowhead.core.translator.services.translator.protocols; + +import java.io.IOException; +import java.net.URI; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.servlet.ServletHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.api.annotations.WebSocket; +import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest; +import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse; +import org.eclipse.jetty.websocket.servlet.WebSocketCreator; +import org.eclipse.jetty.websocket.servlet.WebSocketServlet; +import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; + +import eu.arrowhead.core.translator.services.translator.common.ContentTranslator; + +public class WsIn extends ProtocolIn { + + private final Server wsServer = new Server(); + private final WsServlet wsServlet = new WsServlet(); + + public WsIn(URI uri) throws Exception { + super(uri); + HttpConfiguration config = new HttpConfiguration(); + HttpConnectionFactory httpConnectionFactory = new HttpConnectionFactory(config); + ServerConnector httpConnector = new ServerConnector(wsServer, httpConnectionFactory); + httpConnector.setPort(uri.getPort()); + wsServer.addConnector(httpConnector); + ServletHandler handler = new ServletHandler(); + handler.addServletWithMapping(new ServletHolder("ws", wsServlet), "/*"); + wsServer.setHandler(handler); + wsServer.start(); + } + + @Override + synchronized void notifyObservers(InterProtocolResponse response) { + + // Translation + response.setContent( + ContentTranslator.translate(getContentType(), protocolOut.getContentType(), response.getContent())); + + wsServlet.notifyAllSessions(response); + } + + private class WsServlet extends WebSocketServlet { + + private final ArrayList sessions; + + WsServlet() { + sessions = new ArrayList(); + } + + void addSession(Session session) { + sessions.add(session); + } + + void removeSession(Session session) { + sessions.remove(session); + } + + void notifyAllSessions(InterProtocolResponse response) { + sessions.forEach(session -> { + try { + if (session.isOpen()) { + session.getRemote().sendBytes(ByteBuffer.wrap(response.getContent())); + } + } catch (IOException ex) { + // Ignore + } + }); + } + + @Override + public void configure(WebSocketServletFactory wssf) { + wssf.setCreator(new WsCreator(this)); + } + } + + private class WsCreator implements WebSocketCreator { + + private final WsServlet wsServlet; + + WsCreator(WsServlet wsServlet) { + this.wsServlet = wsServlet; + } + + @Override + public Object createWebSocket(ServletUpgradeRequest sur, ServletUpgradeResponse sur1) { + return new Ws(wsServlet); + } + + @WebSocket(maxTextMessageSize = 100 * 1024 * 1024, maxBinaryMessageSize = 64 * 1024) + public class Ws { + + private final WsServlet wsServlet; + private String path; + private String query; + + public Ws(WsServlet wsServlet) { + this.wsServlet = wsServlet; + } + + @OnWebSocketConnect + public void onWebSocketConnect(Session session) { + path = session.getUpgradeRequest().getRequestURI().getPath(); + query = session.getUpgradeRequest().getRequestURI().getQuery(); + wsServlet.addSession(session); + + protocolOut.observe(new InterProtocolRequest( + path, + query, + MimeTypes.Type.TEXT_PLAIN, + null)); + } + + @OnWebSocketClose + public void onWebSocketClose(Session session, int statusCode, String reason) { + wsServlet.removeSession(session); + } + + @OnWebSocketError + public void onWebSocketError(Throwable cause) { + // Ignore + } + + @OnWebSocketMessage + public void onBinary(Session session, byte buf[], int offset, int length) throws IOException { + wsServlet.notifyAllSessions(protocolOut.post( + new InterProtocolRequest( + path, + query, + MimeTypes.Type.MULTIPART_BYTERANGES, + buf))); + } + + @OnWebSocketMessage + public void onText(Session session, String message) { + wsServlet.notifyAllSessions(protocolOut.post( + new InterProtocolRequest( + path, + query, + MimeTypes.Type.TEXT_PLAIN, + message.getBytes()))); + } + } + + } +} diff --git a/translator/src/main/java/eu/arrowhead/core/translator/services/translator/protocols/WsOut.java b/translator/src/main/java/eu/arrowhead/core/translator/services/translator/protocols/WsOut.java new file mode 100644 index 000000000..a3c65817d --- /dev/null +++ b/translator/src/main/java/eu/arrowhead/core/translator/services/translator/protocols/WsOut.java @@ -0,0 +1,132 @@ +package eu.arrowhead.core.translator.services.translator.protocols; + +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.ByteBuffer; +import javax.servlet.http.HttpServletResponse; +import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.websocket.api.WebSocketAdapter; +import org.eclipse.jetty.websocket.client.WebSocketClient; + +public class WsOut extends ProtocolOut { + + private final WebSocketClient wsClient; + private final SocketAdapter socket; + private byte[] payloadBuffer = "Web Socket Connected".getBytes(); + + public WsOut(URI uri) throws Exception { + super(uri); + socket = new SocketAdapter(); + wsClient = new WebSocketClient(); + } + + @Override + public InterProtocolResponse get(InterProtocolRequest request) { + return observe(request); + } + + @Override + public InterProtocolResponse post(InterProtocolRequest request) { + String remoteRequest; + try { + remoteRequest = new URI(uri.getScheme(), uri.getHost() + ":" + uri.getPort() + uri.getPath(), + request.getPath(), request.getQueries()).toString(); + } catch (URISyntaxException ex) { + remoteRequest = uri.toString(); + } + try { + if (!wsClient.isRunning()) { + wsClient.start(); + } + + if (socket.isNotConnected()) { + wsClient.connect(socket, URI.create(remoteRequest)); + Thread.sleep(500); // Give time to connect + } + + if (socket.isConnected()) { + socket.getSession().getRemote().sendBytes(ByteBuffer.wrap(request.getContent())); + return new InterProtocolResponse( + MimeTypes.Type.TEXT_PLAIN, + HttpServletResponse.SC_OK, + "Websocket Sent!".getBytes()); + } else { + return new InterProtocolResponse( + MimeTypes.Type.TEXT_PLAIN, + HttpServletResponse.SC_INTERNAL_SERVER_ERROR, + "Impossible to connect to the Websocket Server".getBytes()); + } + + } catch (Exception ex) { + return new InterProtocolResponse( + MimeTypes.Type.TEXT_PLAIN, + HttpServletResponse.SC_INTERNAL_SERVER_ERROR, + ex.getLocalizedMessage().getBytes()); + } + + } + + @Override + public InterProtocolResponse observe(InterProtocolRequest request) { + String remoteRequest; + try { + remoteRequest = new URI(uri.getScheme(), uri.getHost() + ":" + uri.getPort() + uri.getPath(), + request.getPath(), request.getQueries()).toString(); + } catch (URISyntaxException ex) { + remoteRequest = uri.toString(); + } + + try { + if (!wsClient.isRunning()) { + wsClient.start(); + } + + if (socket.isNotConnected()) { + wsClient.connect(socket, URI.create(remoteRequest)); + Thread.sleep(500); // Give time to connect + } + + if (socket.isConnected()) { + return new InterProtocolResponse( + MimeTypes.Type.TEXT_PLAIN, + HttpServletResponse.SC_OK, + payloadBuffer); + } else { + return new InterProtocolResponse( + MimeTypes.Type.TEXT_PLAIN, + HttpServletResponse.SC_INTERNAL_SERVER_ERROR, + "Impossible to connect to the Websocket Server".getBytes()); + } + + } catch (Exception ex) { + return new InterProtocolResponse( + MimeTypes.Type.TEXT_PLAIN, + HttpServletResponse.SC_INTERNAL_SERVER_ERROR, + ex.getLocalizedMessage().getBytes()); + } + } + + private class SocketAdapter extends WebSocketAdapter { + + public SocketAdapter() { + } + + @Override + public void onWebSocketBinary(byte[] payload, int offset, int len) { + payloadBuffer = payload; + protocolIn.notifyObservers(new InterProtocolResponse( + MimeTypes.Type.TEXT_PLAIN, + HttpServletResponse.SC_OK, + payload)); + } + + @Override + public void onWebSocketText(String message) { + payloadBuffer = message.getBytes(); + protocolIn.notifyObservers(new InterProtocolResponse( + MimeTypes.Type.TEXT_PLAIN, + HttpServletResponse.SC_OK, + message.getBytes())); + } + } +} diff --git a/translator/src/main/java/eu/arrowhead/core/translator/services/translator/spokes/BaseContext.java b/translator/src/main/java/eu/arrowhead/core/translator/services/translator/spokes/BaseContext.java deleted file mode 100644 index 5d4bc2c09..000000000 --- a/translator/src/main/java/eu/arrowhead/core/translator/services/translator/spokes/BaseContext.java +++ /dev/null @@ -1,71 +0,0 @@ -package eu.arrowhead.core.translator.services.translator.spokes; - -import eu.arrowhead.core.translator.services.translator.common.TranslatorDef.Method; - -public class BaseContext { - - //================================================================================================= - // members - private int key = 0; - private String content; - private String contentType; - private Method method; - private String path; - int cacheTimeout; - - //================================================================================================= - // methods - //------------------------------------------------------------------------------------------------- - public int getKey() { - if (key == 0) { - this.key = (int) (Math.random() * 1000000); - } - return key; - } - - //------------------------------------------------------------------------------------------------- - @SuppressWarnings("unused") - private void setKey(int key) { - this.key = key; - } - - //------------------------------------------------------------------------------------------------- - public String getContent() { - return content; - } - - //------------------------------------------------------------------------------------------------- - public void setContent(String content) { - this.content = content; - } - - //------------------------------------------------------------------------------------------------- - public String getContentType() { - return contentType; - } - - //------------------------------------------------------------------------------------------------- - public void setContentType(String contentType) { - this.contentType = contentType; - } - - //------------------------------------------------------------------------------------------------- - public Method getMethod() { - return method; - } - - //------------------------------------------------------------------------------------------------- - public void setMethod(Method method) { - this.method = method; - } - - //------------------------------------------------------------------------------------------------- - public String getPath() { - return path; - } - - //------------------------------------------------------------------------------------------------- - public void setPath(String path) { - this.path = path; - } -} diff --git a/translator/src/main/java/eu/arrowhead/core/translator/services/translator/spokes/BaseSpoke.java b/translator/src/main/java/eu/arrowhead/core/translator/services/translator/spokes/BaseSpoke.java deleted file mode 100644 index e8a2f0ea1..000000000 --- a/translator/src/main/java/eu/arrowhead/core/translator/services/translator/spokes/BaseSpoke.java +++ /dev/null @@ -1,12 +0,0 @@ -package eu.arrowhead.core.translator.services.translator.spokes; - -public interface BaseSpoke { - - public void in(BaseContext context); - - public void setNextSpoke(Object nextSpoke); - - public int getLastActivity(); - - public void clearActivity(); -} diff --git a/translator/src/main/java/eu/arrowhead/core/translator/services/translator/spokes/BaseSpokeConsumer.java b/translator/src/main/java/eu/arrowhead/core/translator/services/translator/spokes/BaseSpokeConsumer.java deleted file mode 100644 index eed877502..000000000 --- a/translator/src/main/java/eu/arrowhead/core/translator/services/translator/spokes/BaseSpokeConsumer.java +++ /dev/null @@ -1,6 +0,0 @@ -package eu.arrowhead.core.translator.services.translator.spokes; - -public interface BaseSpokeConsumer extends BaseSpoke { - - public void close(); -} diff --git a/translator/src/main/java/eu/arrowhead/core/translator/services/translator/spokes/BaseSpokeProducer.java b/translator/src/main/java/eu/arrowhead/core/translator/services/translator/spokes/BaseSpokeProducer.java deleted file mode 100644 index cb7cf0b8b..000000000 --- a/translator/src/main/java/eu/arrowhead/core/translator/services/translator/spokes/BaseSpokeProducer.java +++ /dev/null @@ -1,8 +0,0 @@ -package eu.arrowhead.core.translator.services.translator.spokes; - -public interface BaseSpokeProducer extends BaseSpoke { - - public void close(); - - public String getAddress(); -} diff --git a/translator/src/main/java/eu/arrowhead/core/translator/services/translator/spokes/CoapConsumerSpoke.java b/translator/src/main/java/eu/arrowhead/core/translator/services/translator/spokes/CoapConsumerSpoke.java deleted file mode 100644 index e8e7c3edd..000000000 --- a/translator/src/main/java/eu/arrowhead/core/translator/services/translator/spokes/CoapConsumerSpoke.java +++ /dev/null @@ -1,111 +0,0 @@ -package eu.arrowhead.core.translator.services.translator.spokes; - -import java.net.UnknownHostException; -import org.eclipse.californium.core.CoapClient; -import org.eclipse.californium.core.CoapResponse; -import org.eclipse.californium.core.coap.MediaTypeRegistry; - -public class CoapConsumerSpoke implements BaseSpokeConsumer { - - //================================================================================================= - // members - BaseSpoke nextSpoke; - String serviceAddress = ""; - public int activity = 0; - - public CoapConsumerSpoke(String serviceAddress) throws UnknownHostException { - if (serviceAddress.startsWith("coap")) { - this.serviceAddress = serviceAddress; - } else { - this.serviceAddress = "coap://" + serviceAddress; - } - - CoapClient pingClient = new CoapClient(serviceAddress); - pingClient.ping(); - - } - - //================================================================================================= - // methods - //------------------------------------------------------------------------------------------------- - @Override - public void close() { - } - - //------------------------------------------------------------------------------------------------- - @Override - public void in(BaseContext context) { - //if the context has no error then - //start a coap client worker - new Thread(new Worker(context), serviceAddress).start(); - } - - //------------------------------------------------------------------------------------------------- - @Override - public void setNextSpoke(Object nextSpoke) { - this.nextSpoke = (BaseSpoke) nextSpoke; - } - - //------------------------------------------------------------------------------------------------- - @Override - public int getLastActivity() { - return activity; - } - - //------------------------------------------------------------------------------------------------- - @Override - public void clearActivity() { - activity = 0; - } - - class Worker implements Runnable { - - //================================================================================================= - // members - BaseContext context = null; - - public Worker(BaseContext paramContext) { - this.context = paramContext; - } - - //================================================================================================= - // methods - //------------------------------------------------------------------------------------------------- - @Override - public void run() { - if (serviceAddress.endsWith("/") && context.getPath().startsWith("/")) { - context.setPath(context.getPath().substring(1)); - } - - CoapClient client = new CoapClient(serviceAddress + context.getPath()); - - CoapResponse response = null; - long lStartTime = System.nanoTime(); - - switch (context.getMethod()) { - case GET: - response = client.get(); - break; - case POST: - response = client.post(context.getContent(), MediaTypeRegistry.parse(context.getContentType())); - break; - case PUT: - response = client.put(context.getContent(), MediaTypeRegistry.parse(context.getContentType())); - break; - case DELETE: - response = client.delete(); - break; - default: - break; - } - - lStartTime = System.nanoTime(); - - if (response != null) { - context.setContent(response.getResponseText()); - } - activity++; - nextSpoke.in(context); - } - } -} diff --git a/translator/src/main/java/eu/arrowhead/core/translator/services/translator/spokes/CoapProducerSpoke.java b/translator/src/main/java/eu/arrowhead/core/translator/services/translator/spokes/CoapProducerSpoke.java deleted file mode 100644 index 4cbe9381e..000000000 --- a/translator/src/main/java/eu/arrowhead/core/translator/services/translator/spokes/CoapProducerSpoke.java +++ /dev/null @@ -1,147 +0,0 @@ -package eu.arrowhead.core.translator.services.translator.spokes; - -import eu.arrowhead.core.translator.services.translator.common.TranslatorDef.Method; -import java.net.InetSocketAddress; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.eclipse.californium.core.CoapResource; -import org.eclipse.californium.core.CoapServer; -import org.eclipse.californium.core.coap.CoAP.Code; -import org.eclipse.californium.core.coap.CoAP.ResponseCode; -import org.eclipse.californium.core.coap.Response; -import org.eclipse.californium.core.network.CoapEndpoint; -import org.eclipse.californium.core.network.CoapEndpoint.CoapEndpointBuilder; -import org.eclipse.californium.core.network.Endpoint; -import org.eclipse.californium.core.network.Exchange; -import org.eclipse.californium.core.server.resources.Resource; - -public class CoapProducerSpoke extends CoapServer implements BaseSpokeProducer { - - //================================================================================================= - // members - BaseSpoke nextSpoke; - Map cachedCoapExchangeMap = new HashMap<>(); - String interfaceAddress = ""; - private int activity = 0; - - //================================================================================================= - // methods - //------------------------------------------------------------------------------------------------- - public CoapProducerSpoke(String ipaddress, int port) { - super(port); - this.interfaceAddress = ipaddress; - this.start(); - } - - //------------------------------------------------------------------------------------------------- - public CoapProducerSpoke(String ipaddress) { - super(); - InetSocketAddress socketAddress = new InetSocketAddress(ipaddress, 0); - this.interfaceAddress = ipaddress; - CoapEndpointBuilder builderCoap = new CoapEndpoint.CoapEndpointBuilder(); - builderCoap.setInetSocketAddress(socketAddress); - this.addEndpoint(builderCoap.build()); - this.start(); - } - - //------------------------------------------------------------------------------------------------- - @Override - public void close() { - this.destroy(); - } - - //------------------------------------------------------------------------------------------------- - @Override - public String getAddress() { - return "coap://" + this.interfaceAddress + ":" + Integer.toString(this.getEndpoints().get(0).getAddress().getPort()) + "/"; - - } - - //------------------------------------------------------------------------------------------------- - @Override - public void in(BaseContext context) { - Exchange exchange = cachedCoapExchangeMap.get(context.getKey()); - Response response = new Response(ResponseCode.CONTENT); - response.setPayload(context.getContent()); - exchange.sendResponse(response); - } - - //------------------------------------------------------------------------------------------------- - @Override - public void setNextSpoke(Object nextSpoke) { - this.nextSpoke = (BaseSpoke) nextSpoke; - } - - //------------------------------------------------------------------------------------------------- - @Override - public int getLastActivity() { - return activity; - } - - //------------------------------------------------------------------------------------------------- - @Override - public void clearActivity() { - activity = 0; - } - - //------------------------------------------------------------------------------------------------- - @Override - protected Resource createRoot() { - return new RootResource(); - } - - private class RootResource extends CoapResource { - - public RootResource() { - super(""); - } - - //================================================================================================= - // methods - //------------------------------------------------------------------------------------------------- - @Override - public List getEndpoints() { - return CoapProducerSpoke.this.getEndpoints(); - } - - //------------------------------------------------------------------------------------------------- - @Override - public void handleRequest(Exchange exchange) { - exchange.sendAccept(); - activity++; - BaseContext context = new BaseContext(); - cachedCoapExchangeMap.put(context.getKey(), exchange); - context.setContent(exchange.getRequest().getPayloadString()); - context.setPath(exchange.getRequest().getOptions().getUriPathString()); - Code code = exchange.getRequest().getCode(); - Method method = null; - switch (code) { - case GET: - method = Method.GET; - break; - case POST: - method = Method.POST; - break; - case PUT: - method = Method.PUT; - break; - case DELETE: - method = Method.DELETE; - break; - default: - method = Method.GET; - break; - } - context.setMethod(method); - nextSpoke.in(context); - } - - //------------------------------------------------------------------------------------------------- - @Override - public Resource getChild(String name) { - //all sub-resources come back to this root resource - return this; - } - } -} diff --git a/translator/src/main/java/eu/arrowhead/core/translator/services/translator/spokes/HttpConsumerSpoke.java b/translator/src/main/java/eu/arrowhead/core/translator/services/translator/spokes/HttpConsumerSpoke.java deleted file mode 100644 index 2411a23b2..000000000 --- a/translator/src/main/java/eu/arrowhead/core/translator/services/translator/spokes/HttpConsumerSpoke.java +++ /dev/null @@ -1,137 +0,0 @@ -package eu.arrowhead.core.translator.services.translator.spokes; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.net.HttpURLConnection; -import java.net.URL; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.springframework.http.MediaType; - -public class HttpConsumerSpoke implements BaseSpokeConsumer { - - //================================================================================================= - // members - private final Logger logger = LogManager.getLogger(HttpConsumerSpoke.class); - BaseSpoke nextSpoke; - String serviceAddress = ""; - public int activity = 0; - - public HttpConsumerSpoke(String serviceAddress) { - this.serviceAddress = serviceAddress; - - } - - //================================================================================================= - // methods - //------------------------------------------------------------------------------------------------- - @Override - public void close() { - } - - //------------------------------------------------------------------------------------------------- - @Override - public void in(BaseContext context) { - new Thread(new Worker(context)).start(); - } - - //------------------------------------------------------------------------------------------------- - @Override - public void setNextSpoke(Object nextSpoke) { - this.nextSpoke = (BaseSpoke) nextSpoke; - } - - //------------------------------------------------------------------------------------------------- - @Override - public int getLastActivity() { - return activity; - } - - //------------------------------------------------------------------------------------------------- - @Override - public void clearActivity() { - activity = 0; - } - - class Worker implements Runnable { - - //================================================================================================= - // members - BaseContext context = null; - - public Worker(BaseContext paramContext) { - this.context = paramContext; - } - - //================================================================================================= - // methods - //------------------------------------------------------------------------------------------------- - @Override - public void run() { - - // get the requested host, if the port is not specified, the constructor - // sets it to -1 - - //Added path to access resources, not just root "/" - String myurl = HttpConsumerSpoke.this.serviceAddress + "/" + this.context.getPath(); - StringBuilder result = new StringBuilder(); - URL url; - HttpURLConnection conn; - boolean payloadExpected = false; - try { - url = new URL(myurl); - - // create the requestLine - conn = (HttpURLConnection) url.openConnection(); - - switch (this.context.getMethod()) { - case GET: - conn.setRequestMethod("GET"); - payloadExpected = false; - break; - case POST: - conn.setRequestMethod("POST"); - payloadExpected = true; - break; - case PUT: - conn.setRequestMethod("PUT"); - payloadExpected = true; - break; - case DELETE: - conn.setRequestMethod("DELETE"); - payloadExpected = true; - break; - default: - break; - } - //if there is a payload then add that to the request - if (payloadExpected) { - // create the content - conn.setDoOutput(true); - conn.setRequestProperty("content-type", MediaType.APPLICATION_JSON_VALUE); - OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream()); - writer.write(this.context.getContent()); - writer.flush(); - - } - - BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream())); - String line; - while ((line = rd.readLine()) != null) { - result.append(line); - } - rd.close(); - context.setContent(result.toString()); - } catch (IOException ex) { - logger.debug(ex.getMessage()); - } - - // get the mapping to http for the incoming request - activity++; - nextSpoke.in(context); - } - } - -} diff --git a/translator/src/main/java/eu/arrowhead/core/translator/services/translator/spokes/HttpProducerSpoke.java b/translator/src/main/java/eu/arrowhead/core/translator/services/translator/spokes/HttpProducerSpoke.java deleted file mode 100644 index 510fa9ed7..000000000 --- a/translator/src/main/java/eu/arrowhead/core/translator/services/translator/spokes/HttpProducerSpoke.java +++ /dev/null @@ -1,242 +0,0 @@ -package eu.arrowhead.core.translator.services.translator.spokes; - - -import eu.arrowhead.common.exception.ArrowheadException; -import eu.arrowhead.core.translator.services.translator.common.TranslatorDef.Method; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.net.InetSocketAddress; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.Map; -import org.apache.http.HttpEntity; -import org.apache.http.HttpEntityEnclosingRequest; -import org.apache.http.HttpException; -import org.apache.http.HttpRequest; -import org.apache.http.HttpRequestInterceptor; -import org.apache.http.HttpResponse; -import org.apache.http.HttpResponseInterceptor; -import org.apache.http.client.protocol.RequestAcceptEncoding; -import org.apache.http.client.protocol.ResponseContentEncoding; -import org.apache.http.entity.ContentType; -import org.apache.http.entity.StringEntity; -import org.apache.http.impl.DefaultConnectionReuseStrategy; -import org.apache.http.impl.nio.DefaultHttpServerIODispatch; -import org.apache.http.impl.nio.DefaultNHttpServerConnection; -import org.apache.http.impl.nio.DefaultNHttpServerConnectionFactory; -import org.apache.http.impl.nio.reactor.DefaultListeningIOReactor; -import org.apache.http.nio.NHttpConnectionFactory; -import org.apache.http.nio.protocol.BasicAsyncRequestConsumer; -import org.apache.http.nio.protocol.HttpAsyncExchange; -import org.apache.http.nio.protocol.HttpAsyncRequestConsumer; -import org.apache.http.nio.protocol.HttpAsyncRequestHandler; -import org.apache.http.nio.protocol.HttpAsyncRequestHandlerRegistry; -import org.apache.http.nio.protocol.HttpAsyncService; -import org.apache.http.nio.reactor.IOEventDispatch; -import org.apache.http.nio.reactor.ListenerEndpoint; -import org.apache.http.nio.reactor.ListeningIOReactor; -import org.apache.http.params.CoreConnectionPNames; -import org.apache.http.params.CoreProtocolPNames; -import org.apache.http.params.HttpParams; -import org.apache.http.params.SyncBasicHttpParams; -import org.apache.http.protocol.HttpContext; -import org.apache.http.protocol.HttpProcessor; -import org.apache.http.protocol.ImmutableHttpProcessor; -import org.apache.http.protocol.ResponseConnControl; -import org.apache.http.protocol.ResponseContent; -import org.apache.http.protocol.ResponseDate; -import org.apache.http.protocol.ResponseServer; -import org.apache.http.util.EntityUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.eclipse.californium.core.network.config.NetworkConfig; - -public class HttpProducerSpoke implements BaseSpokeProducer { - - //================================================================================================= - // members - private final Logger logger = LogManager.getLogger(HttpProducerSpoke.class); - private static final Charset UTF_8 = StandardCharsets.UTF_8; - private static final int SOCKET_TIMEOUT = NetworkConfig.getStandard().getInt(NetworkConfig.Keys.HTTP_SERVER_SOCKET_TIMEOUT); - private static final int SOCKET_BUFFER_SIZE = NetworkConfig.getStandard().getInt(NetworkConfig.Keys.HTTP_SERVER_SOCKET_BUFFER_SIZE); - private static final String SERVER_NAME = "Californium Http Proxy"; - - Map cachedHttpExchangeMap = new HashMap<>(); - private String address = ""; - ListeningIOReactor ioReactor; - private int activity = 0; - - public HttpProducerSpoke(String ipaddress, String path) throws IOException, InterruptedException { - address = ipaddress; - // HTTP parameters for the server - HttpParams params = new SyncBasicHttpParams(); - params.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, SOCKET_TIMEOUT).setIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, SOCKET_BUFFER_SIZE).setBooleanParameter(CoreConnectionPNames.TCP_NODELAY, true).setParameter(CoreProtocolPNames.ORIGIN_SERVER, SERVER_NAME); - - // Create HTTP protocol processing chain - // Use standard server-side protocol interceptors - HttpRequestInterceptor[] requestInterceptors = new HttpRequestInterceptor[]{new RequestAcceptEncoding()}; - HttpResponseInterceptor[] responseInterceptors = new HttpResponseInterceptor[]{new ResponseAllowCORS(), new ResponseContentEncoding(), new ResponseDate(), new ResponseServer(), new ResponseContent(), new ResponseConnControl()}; - HttpProcessor httpProcessor = new ImmutableHttpProcessor(requestInterceptors, responseInterceptors); - - // Create request handler registry - HttpAsyncRequestHandlerRegistry registry = new HttpAsyncRequestHandlerRegistry(); - - // register the handler that will reply to the proxy requests - registry.register(path, new RequestHandler("", true)); - - // Create server-side HTTP protocol handler - HttpAsyncService protocolHandler = new HttpAsyncService(httpProcessor, new DefaultConnectionReuseStrategy(), registry, params); - - // Create HTTP connection factory - NHttpConnectionFactory connFactory = new DefaultNHttpServerConnectionFactory(params); - - // Create server-side I/O event dispatch - final IOEventDispatch ioEventDispatch = new DefaultHttpServerIODispatch(protocolHandler, connFactory); - - try { - // Create server-side I/O reactor - ioReactor = new DefaultListeningIOReactor(); - // Listen of the given port - - InetSocketAddress socketAddress = new InetSocketAddress(ipaddress, 0); - ListenerEndpoint endpoint1 = ioReactor.listen(socketAddress); - - // create the listener thread - Thread listener = new Thread("Http listener") { - - @Override - public void run() { - try { - ioReactor.execute(ioEventDispatch); - } catch (IOException ex) { - throw new ArrowheadException(ex.getLocalizedMessage()); - } - - } - }; - - listener.setDaemon(false); - listener.start(); - endpoint1.waitFor(); - - if (address.contains(":")) { - if (!address.startsWith("[")) { - address = "[" + address + "]"; - } - } - - address = address + ":" + Integer.toString(((InetSocketAddress) endpoint1.getAddress()).getPort()) + "/"; - - } catch (IOException ex) { - logger.debug(ex.getMessage()); - } - } - - //================================================================================================= - // methods - //------------------------------------------------------------------------------------------------- - @Override - public void close() { - try { - ioReactor.shutdown(); - } catch (IOException ex) { - logger.debug(ex.getMessage()); - } - } - - //------------------------------------------------------------------------------------------------- - @Override - public String getAddress() { - throw new UnsupportedOperationException("Not supported yet."); - } - - //------------------------------------------------------------------------------------------------- - @Override - public void in(BaseContext context) { - // get the sample http response - HttpResponse httpResponse = cachedHttpExchangeMap.get(context.getKey()).getResponse(); - - try { - constructHttpResponse(context.getContent(), httpResponse); - } catch (UnsupportedEncodingException ex) { - logger.debug(ex.getMessage()); - } - - long lStartTime = System.nanoTime(); - // send the response - cachedHttpExchangeMap.get(context.getKey()).submitResponse(); - - cachedHttpExchangeMap.remove(context.getKey()); - } - - //------------------------------------------------------------------------------------------------- - @Override - public void setNextSpoke(Object nextSpoke) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - - //------------------------------------------------------------------------------------------------- - @Override - public int getLastActivity() { - return activity; - } - - //------------------------------------------------------------------------------------------------- - @Override - public void clearActivity() { - activity = 0; - } - - //================================================================================================= - // assistant methods - //------------------------------------------------------------------------------------------------- - private static void constructHttpResponse(String msg, HttpResponse httpResponse) throws UnsupportedEncodingException { - if (msg != null) { - HttpEntity httpEntity; - // create the entity - httpEntity = new StringEntity(msg); - httpResponse.setEntity(httpEntity); - } - } - - private class RequestHandler implements HttpAsyncRequestHandler { - - public RequestHandler(String localResource, boolean proxyingEnabled) { - super(); - } - - //================================================================================================= - // methods - //------------------------------------------------------------------------------------------------- - @Override - public void handle(HttpRequest httpRequest, HttpAsyncExchange httpExchange, HttpContext httpContext) throws HttpException, IOException { - - long lStartTime = System.nanoTime(); - activity++; - BaseContext context = new BaseContext(); - - //store the http context for generating the response - cachedHttpExchangeMap.put(context.getKey(), httpExchange); - - context.setMethod(Method.valueOf(httpRequest.getRequestLine().getMethod().toUpperCase())); - context.setPath(httpRequest.getRequestLine().getUri()); - // set the payload if the http entity is present - if (httpRequest instanceof HttpEntityEnclosingRequest) { - HttpEntity httpEntity = ((HttpEntityEnclosingRequest) httpRequest).getEntity(); - - // get the bytes from the entity - String payload = EntityUtils.toString(httpEntity); - - context.setContent(payload); - } - } - - //------------------------------------------------------------------------------------------------- - @Override - public HttpAsyncRequestConsumer processRequest(HttpRequest httpRequest, HttpContext httpContext) throws HttpException, IOException { - // Buffer request content in memory for simplicity - return new BasicAsyncRequestConsumer(); - } - } -} diff --git a/translator/src/main/java/eu/arrowhead/core/translator/services/translator/spokes/ResponseAllowCORS.java b/translator/src/main/java/eu/arrowhead/core/translator/services/translator/spokes/ResponseAllowCORS.java deleted file mode 100644 index fb712d40f..000000000 --- a/translator/src/main/java/eu/arrowhead/core/translator/services/translator/spokes/ResponseAllowCORS.java +++ /dev/null @@ -1,28 +0,0 @@ -package eu.arrowhead.core.translator.services.translator.spokes; - -import java.io.IOException; -import org.apache.http.HttpException; -import org.apache.http.HttpResponse; -import org.apache.http.HttpResponseInterceptor; -import org.apache.http.protocol.HttpContext; - -public class ResponseAllowCORS implements HttpResponseInterceptor { - - //================================================================================================= - // members - - public ResponseAllowCORS() { - super(); - } - - //================================================================================================= - // methods - //------------------------------------------------------------------------------------------------- - @Override - public void process(final HttpResponse response, final HttpContext context) throws HttpException, IOException { - if (response == null) { - throw new IllegalArgumentException("HTTP response may not be null."); - } - response.setHeader("Access-Control-Allow-Origin", "*"); - } -} diff --git a/translator/src/main/resources/log4j2.xml b/translator/src/main/resources/log4j2.xml index 9e2356c4f..40ce3f368 100644 --- a/translator/src/main/resources/log4j2.xml +++ b/translator/src/main/resources/log4j2.xml @@ -41,6 +41,8 @@ - + + + \ No newline at end of file