Move Mgmt artifacts into hawkbit-mgmt (#2003)

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2024-11-11 15:57:56 +02:00
committed by GitHub
parent 05d8d6cc7e
commit baab2fcf95
200 changed files with 167 additions and 114 deletions

View File

@@ -0,0 +1,68 @@
/**
* Copyright (c) 2015 Bosch Software Innovations 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 https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.app.mgmt;
import java.util.Map;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* Error page controller that ensures that ocet stream does not return text in
* case of an error.
*/
@Controller
// Exception squid:S3752 - errors need handling for all methods
@SuppressWarnings("squid:S3752")
public class ErrorController extends BasicErrorController {
private static final String PATH = "path";
/**
* A new {@link ErrorController}.
*
* @param errorAttributes the error attributes
* @param serverProperties configuration properties
*/
public ErrorController(final ErrorAttributes errorAttributes, final ServerProperties serverProperties) {
super(errorAttributes, serverProperties.getError());
}
@RequestMapping(produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public ResponseEntity<Void> errorStream(final HttpServletRequest request, final HttpServletResponse response) {
final HttpStatus status = getStatus(request);
return new ResponseEntity<>(status);
}
@Override
@RequestMapping
public ResponseEntity<Map<String, Object>> error(final HttpServletRequest request) {
final HttpStatus status = getStatus(request);
final Map<String, Object> body = getErrorAttributesWithoutPath(request);
return new ResponseEntity<>(body, status);
}
private Map<String, Object> getErrorAttributesWithoutPath(final HttpServletRequest request) {
final Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
if (body != null && body.containsKey(PATH)) {
body.remove(PATH);
}
return body;
}
}

View File

@@ -0,0 +1,56 @@
/**
* Copyright (c) 2015 Bosch Software Innovations 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 https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.app.mgmt;
import org.eclipse.hawkbit.autoconfigure.security.EnableHawkbitManagedSecurityConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.springframework.web.servlet.view.RedirectView;
/**
* A {@link SpringBootApplication} annotated class with a main method to start.
* The minimal configuration for the stand alone hawkBit server.
*/
@SpringBootApplication(scanBasePackages = "org.eclipse.hawkbit")
@EnableHawkbitManagedSecurityConfiguration
public class MgmtServerStart {
/**
* Main method to start the spring-boot application.
*
* @param args the VM arguments.
*/
public static void main(final String[] args) {
SpringApplication.run(MgmtServerStart.class, args);
}
@Controller
public static class RedirectController {
@GetMapping("/")
public RedirectView redirectToSwagger(
RedirectAttributes attributes) {
attributes.addFlashAttribute("flashAttribute", "redirectWithRedirectView");
attributes.addAttribute("attribute", "redirectWithRedirectView");
return new RedirectView("swagger-ui/index.html");
}
}
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, proxyTargetClass = true)
public static class MethodSecurityConfig {
}
}

View File

@@ -0,0 +1,19 @@
#
# Copyright (c) 2019 Bosch Software Innovations 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 https://www.eclipse.org/legal/epl-2.0/
#
# SPDX-License-Identifier: EPL-2.0
#
# This profile adds basic configurations for a DB2 DB usage.
# Keep in mind that you need the DB2 driver in your classpath on compile.
# see https://www.eclipse.org/hawkbit/guides/runhawkbit/
spring.jpa.database=DB2
spring.datasource.url=jdbc:db2://localhost:50000/hawkbit
spring.datasource.username=db2inst1
spring.datasource.password=db2inst1-pwd
spring.datasource.driverClassName=com.ibm.db2.jcc.DB2Driver

View File

@@ -0,0 +1,19 @@
#
# Copyright (c) 2018 Bosch Software Innovations 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 https://www.eclipse.org/legal/epl-2.0/
#
# SPDX-License-Identifier: EPL-2.0
#
# This profile adds basic configurations for a Microsoft SQL Server DB usage.
# Keep in mind that you need the SQL server driver in your classpath on compile.
# see https://www.eclipse.org/hawkbit/guides/runhawkbit/
spring.jpa.database=SQL_SERVER
spring.datasource.url=jdbc:sqlserver://localhost:1433;database=hawkbit
spring.datasource.username=SA
spring.datasource.password=
spring.datasource.driverClassName=com.microsoft.sqlserver.jdbc.SQLServerDriver

View File

@@ -0,0 +1,19 @@
#
# Copyright (c) 2015 Bosch Software Innovations 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 https://www.eclipse.org/legal/epl-2.0/
#
# SPDX-License-Identifier: EPL-2.0
#
# This profile adds basic configurations for a MySQL DB usage.
# Keep in mind that you need the MariaDB driver in your classpath on compile.
# see https://www.eclipse.org/hawkbit/guides/runhawkbit/
spring.jpa.database=MYSQL
spring.datasource.url=jdbc:mariadb://localhost:3306/hawkbit
spring.datasource.username=root
spring.datasource.password=
spring.datasource.driverClassName=org.mariadb.jdbc.Driver

View File

@@ -0,0 +1,19 @@
#
# Copyright (c) 2020 Enapter Co.,Ltd
#
# This program and the accompanying materials are made
# available under the terms of the Eclipse Public License 2.0
# which is available at https://www.eclipse.org/legal/epl-2.0/
#
# SPDX-License-Identifier: EPL-2.0
#
# This profile adds basic configurations for a PostgreSQL usage.
# Keep in mind that you need the PostgreSQL driver in your classpath on compile.
# see https://www.eclipse.org/hawkbit/guides/runhawkbit/
spring.jpa.database=POSTGRESQL
spring.datasource.url=jdbc:postgresql://localhost:5432/hawkbit
spring.datasource.username=postgres
spring.datasource.password=admin
spring.datasource.driverClassName=org.postgresql.Driver

View File

@@ -0,0 +1,75 @@
#
# Copyright (c) 2015 Bosch Software Innovations 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 https://www.eclipse.org/legal/epl-2.0/
#
# SPDX-License-Identifier: EPL-2.0
#
# Spring config
spring.application.name=mgmt-server
spring.main.allow-bean-definition-overriding=true
spring.port=8080
# Logging configuration
logging.level.org.eclipse.hawkbit.eventbus.DeadEventListener=WARN
logging.level.org.springframework.boot.actuate.audit.listener.AuditListener=WARN
logging.level.org.hibernate.validator.internal.util.Version=WARN
# security Log with hints on potential attacks
logging.level.server-security=INFO
# logging pattern
logging.pattern.console=%clr(%d{${logging.pattern.dateformat:yyyy-MM-dd'T'HH:mm:ss.SSSXXX}}){faint} %clr(${logging.pattern.level:%5p}) %clr(${PID:}){magenta} %clr(---){faint} %clr([${spring.application.name}] [%X{tenant}:%X{user}] [%15.15t]){faint} %clr(${logging.pattern.correlation:}){faint}%clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${logging.exception-conversion-word:%wEx}
# Define DEFAULT tenant admin user admin/admin
hawkbit.security.user.admin.tenant=DEFAULT
hawkbit.security.user.admin.password={noop}admin
hawkbit.security.user.admin.roles=TENANT_ADMIN
# allow to auto/implicit create DEFAULT tenant (on mgmt api call)
hawkbit.server.repository.implicitTenantCreateAllowed=true
# Http Encoding
server.servlet.encoding.charset=UTF-8
server.servlet.encoding.enabled=true
server.servlet.encoding.force=true
# Optional events
hawkbit.server.repository.publish-target-poll-event=false
## Configuration for DMF/RabbitMQ integration
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.virtual-host=/
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
# Enable CORS and specify the allowed origins:
#hawkbit.server.security.cors.enabled=true
#hawkbit.server.security.cors.allowedOrigins=http://localhost
hawkbit.server.swagger.mgmt.api.group.enabled=true
hawkbit.server.swagger.ddi.api.group.enabled=false
# Swagger Configuration
springdoc.api-docs.version=openapi_3_0
springdoc.show-oauth2-endpoints=true
springdoc.show-login-endpoint=true
springdoc.packages-to-scan=org.eclipse.hawkbit.mgmt
springdoc.paths-to-exclude=/system/**
# Disable discovery client of spring-cloud-commons
spring.cloud.discovery.enabled=false
# Enable communication between services
spring.cloud.bus.enabled=true
spring.cloud.bus.ack.enabled=false
spring.cloud.bus.refresh.enabled=false
spring.cloud.bus.env.enabled=false
endpoints.spring.cloud.bus.refresh.enabled=false
endpoints.spring.cloud.bus.env.enabled=false
spring.cloud.stream.bindings.springCloudBusInput.group=mgmt-server
# To use protostuff (for instance fot improved performance) you shall uncomment
# the following two lines and add io.protostuff:protostuff-core and io.protostuff:protostuff-runtime to dependencies
#spring.cloud.stream.bindings.springCloudBusInput.content-type=application/binary+protostuff
#spring.cloud.stream.bindings.springCloudBusOutput.content-type=application/binary+protostuff

View File

@@ -0,0 +1,14 @@
______ _ _ _ _ ____ _ _ __ __ _
| ____| | (_) | | | | | _ \(_) | | \/ | | |
| |__ ___| |_ _ __ ___ ___ | |__ __ ___ _| | _| |_) |_| |_ | \ / | __ _ _ __ ___ | |_
| __| / __| | | '_ \/ __|/ _ \ | '_ \ / _` \ \ /\ / / |/ / _ <| | __| | |\/| |/ _` | '_ ` _ \| __|
| |___| (__| | | |_) \__ \ __/ | | | | (_| |\ V V /| <| |_) | | |_ | | | | (_| | | | | | | |_
|______\___|_|_| .__/|___/\___| |_| |_|\__,_| \_/\_/ |_|\_\____/|_|\__| |_| |_|\__, |_| |_| |_|\__|
| | __/ |
|_| |___/
Eclipse hawkBit Management Server ${application.formatted-version}
using Spring Boot ${spring-boot.formatted-version}
Go to https://www.eclipse.org/hawkbit for more information.

View File

@@ -0,0 +1,38 @@
/**
* 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 https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.app.mgmt;
import org.eclipse.hawkbit.repository.test.util.SharedSqlTestDatabaseExtension;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
@SpringBootTest(properties = { "hawkbit.dmf.rabbitmq.enabled=false" })
@ExtendWith(SharedSqlTestDatabaseExtension.class)
public abstract class AbstractSecurityTest {
protected MockMvc mvc;
@Autowired
private WebApplicationContext context;
@BeforeEach
public void setup() {
final DefaultMockMvcBuilder builder = MockMvcBuilders.webAppContextSetup(context)
.apply(SecurityMockMvcConfigurers.springSecurity()).dispatchOptions(true);
mvc = builder.build();
}
}

View File

@@ -0,0 +1,46 @@
/**
* Copyright (c) 2019 Bosch Software Innovations 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 https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.app.mgmt;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.qameta.allure.Story;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.test.context.TestPropertySource;
@TestPropertySource(properties = { "hawkbit.server.security.allowedHostNames=localhost",
"hawkbit.server.security.httpFirewallIgnoredPaths=/index.html" })
@Feature("Integration Test - Security")
@Story("Allowed Host Names")
public class AllowedHostNamesTest extends AbstractSecurityTest {
@Test
@Description("Tests whether a RequestRejectedException is thrown when not allowed host is used")
public void allowedHostNameWithNotAllowedHost() throws Exception {
mvc.perform(get("/").header(HttpHeaders.HOST, "www.google.com")).andExpect(status().isBadRequest());
}
@Test
@Description("Tests whether request is redirected when allowed host is used")
public void allowedHostNameWithAllowedHost() throws Exception {
mvc.perform(get("/").header(HttpHeaders.HOST, "localhost")).andExpect(status().is3xxRedirection());
}
@Test
@Description("Tests whether request without allowed host name and with ignored path end up with a client error")
public void notAllowedHostnameWithIgnoredPath() throws Exception {
mvc.perform(get("/index.html").header(HttpHeaders.HOST, "www.google.com"))
.andExpect(status().is4xxClientError());
}
}

View File

@@ -0,0 +1,76 @@
/**
* Copyright (c) 2019 Bosch Software Innovations 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 https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.app.mgmt;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.options;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.qameta.allure.Story;
import org.eclipse.hawkbit.im.authentication.SpRole;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.repository.test.util.WithUser;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpHeaders;
import org.springframework.test.web.servlet.ResultActions;
@SpringBootTest(
properties = {
"hawkbit.dmf.rabbitmq.enabled=false",
"hawkbit.server.security.cors.enabled=true",
"hawkbit.server.security.cors.allowedOrigins=" +
CorsTest.ALLOWED_ORIGIN_FIRST + "," +
CorsTest.ALLOWED_ORIGIN_SECOND,
"hawkbit.server.security.cors.exposedHeaders=" +
HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN })
@Feature("Integration Test - Security")
@Story("CORS")
public class CorsTest extends AbstractSecurityTest {
final static String ALLOWED_ORIGIN_FIRST = "http://test.first.origin";
final static String ALLOWED_ORIGIN_SECOND = "http://test.second.origin";
private final static String INVALID_ORIGIN = "http://test.invalid.origin";
private final static String INVALID_CORS_REQUEST = "Invalid CORS request";
@Test
@Description("Ensures that Cors is working.")
@WithUser(authorities = SpRole.TENANT_ADMIN)
public void validateCorsRequest() throws Exception {
performOptionsRequestToRestWithOrigin(ALLOWED_ORIGIN_FIRST).andExpect(status().isOk())
.andExpect(header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, ALLOWED_ORIGIN_FIRST));
performOptionsRequestToRestWithOrigin(ALLOWED_ORIGIN_SECOND).andExpect(status().isOk())
.andExpect(header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, ALLOWED_ORIGIN_SECOND));
final String invalidOriginResponseBody = performOptionsRequestToRestWithOrigin(INVALID_ORIGIN)
.andExpect(status().isForbidden())
.andExpect(header().doesNotExist(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)).andReturn().getResponse()
.getContentAsString();
assertThat(invalidOriginResponseBody).isEqualTo(INVALID_CORS_REQUEST);
final String invalidCorsUrlResponseBody = performOptionsRequestToUrlWithOrigin(
MgmtRestConstants.BASE_SYSTEM_MAPPING, ALLOWED_ORIGIN_FIRST).andExpect(status().isForbidden())
.andExpect(header().doesNotExist(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)).andReturn()
.getResponse().getContentAsString();
assertThat(invalidCorsUrlResponseBody).isEqualTo(INVALID_CORS_REQUEST);
}
private ResultActions performOptionsRequestToRestWithOrigin(final String origin) throws Exception {
return performOptionsRequestToUrlWithOrigin(MgmtRestConstants.BASE_V1_REQUEST_MAPPING, origin);
}
private ResultActions performOptionsRequestToUrlWithOrigin(final String url, final String origin) throws Exception {
return mvc.perform(options(url).header("Access-Control-Request-Method", "GET").header("Origin", origin));
}
}

View File

@@ -0,0 +1,80 @@
/**
* Copyright (c) 2023 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 https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.app.mgmt;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import java.util.HashMap;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.qameta.allure.Story;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.im.authentication.SpRole;
import org.eclipse.hawkbit.repository.test.util.WithUser;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpStatus;
@Feature("Integration Test - Security")
@Story("PreAuthorized enabled")
public class PreAuthorizeEnabledTest extends AbstractSecurityTest {
@Test
@Description("Tests whether request fail if a role is forbidden for the user")
@WithUser(authorities = { SpPermission.READ_TARGET })
public void failIfNoRole() throws Exception {
mvc.perform(get("/rest/v1/distributionsets")).andExpect(result ->
assertThat(result.getResponse().getStatus()).isEqualTo(HttpStatus.FORBIDDEN.value()));
}
@Test
@Description("Tests whether request succeed if a role is granted for the user")
@WithUser(authorities = { SpPermission.READ_REPOSITORY })
public void successIfHasRole() throws Exception {
mvc.perform(get("/rest/v1/distributionsets")).andExpect(result -> {
assertThat(result.getResponse().getStatus()).isEqualTo(HttpStatus.OK.value());
});
}
@Test
@Description("Tests whether request succeed if a role is granted for the user")
@WithUser(authorities = { SpRole.TENANT_ADMIN })
public void successIfHasTenantAdminRole() throws Exception {
mvc.perform(get("/rest/v1/distributionsets")).andExpect(result -> {
assertThat(result.getResponse().getStatus()).isEqualTo(HttpStatus.OK.value());
});
}
@Test
@Description("Tests whether read tenant config request fail if a tenant config (or read read) is not " +
"granted for the user")
@WithUser(authorities = { SpPermission.READ_TARGET })
public void onlyDSIfNoTenantConfig() throws Exception {
mvc.perform(get("/rest/v1/system/configs")).andExpect(result -> {
// returns default DS type because of READ_TARGET
assertThat(result.getResponse().getStatus()).isEqualTo(HttpStatus.OK.value());
assertThat(
new ObjectMapper().reader().readValue(result.getResponse().getContentAsString(), HashMap.class)
.size())
.isEqualTo(1);
});
}
@Test
@Description("Tests whether read tenant config request succeed if a tenant config (not read explicitly) is " +
"granted for the user")
@WithUser(authorities = { SpPermission.TENANT_CONFIGURATION })
public void successIfHasTenantConfig() throws Exception {
mvc.perform(get("/rest/v1/system/configs")).andExpect(result ->
assertThat(result.getResponse().getStatus()).isEqualTo(HttpStatus.OK.value()));
}
}