diff options
author | Fiete Ostkamp <Fiete.Ostkamp@telekom.de> | 2024-05-14 13:38:17 +0200 |
---|---|---|
committer | Fiete Ostkamp <Fiete.Ostkamp@telekom.de> | 2024-05-16 14:07:00 +0200 |
commit | 9ad020e36d7dba6e9e2fdd2e5b5276e728de4bd3 (patch) | |
tree | d35910bf26cd5d91e09d0431d5e6dd88d35c1682 | |
parent | f5a7f7daf927ee345cc60abd212771812dfae685 (diff) |
Make rbac excluded endpoints configurablenewdelhi
- introduce bff.rbac.endpoints-excluded config
- add some performance improvements for role checking
- resolve compilation warning related to missing swagger dependency
Issue-ID: PORTALNG-100
Change-Id: I38ac942f0731a3297a797a09402f20aa6efc3b58
Signed-off-by: Fiete Ostkamp <Fiete.Ostkamp@telekom.de>
-rw-r--r-- | app/build.gradle | 1 | ||||
-rw-r--r-- | app/src/main/resources/application-access-control.yml | 42 | ||||
-rw-r--r-- | app/src/main/resources/application.yml | 4 | ||||
-rw-r--r-- | app/src/test/java/org/onap/portalng/bff/BaseIntegrationTest.java | 2 | ||||
-rw-r--r-- | app/src/test/java/org/onap/portalng/bff/idtoken/IdTokenExchangeFilterFunctionTest.java | 7 | ||||
-rw-r--r-- | app/src/test/resources/application-access-control.yml | 21 | ||||
-rw-r--r-- | app/src/test/resources/application.yml | 15 | ||||
-rw-r--r-- | app/src/test/resources/logback-spring.xml | 18 | ||||
-rw-r--r-- | build.gradle | 1 | ||||
-rw-r--r-- | lib/build.gradle | 1 | ||||
-rw-r--r-- | lib/src/main/java/org/onap/portalng/bff/config/BeansConfig.java | 5 | ||||
-rw-r--r-- | lib/src/main/java/org/onap/portalng/bff/config/BffConfig.java | 8 | ||||
-rw-r--r-- | lib/src/main/java/org/onap/portalng/bff/config/IdTokenExchangeFilterFunction.java | 35 | ||||
-rw-r--r-- | lib/src/main/java/org/onap/portalng/bff/config/SecurityConfig.java | 10 |
14 files changed, 70 insertions, 100 deletions
diff --git a/app/build.gradle b/app/build.gradle index 4305de0..ed30630 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -41,6 +41,7 @@ dependencies { implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml' implementation "org.zalando:problem-spring-webflux:$problemSpringVersion" implementation "org.zalando:jackson-datatype-problem:$problemVersion" + implementation "io.swagger.core.v3:swagger-annotations:$swaggerV3Version" implementation "org.mapstruct:mapstruct:$mapStructVersion" annotationProcessor "org.mapstruct:mapstruct-processor:$mapStructVersion" diff --git a/app/src/main/resources/application-access-control.yml b/app/src/main/resources/application-access-control.yml index 4da29f1..6fda781 100644 --- a/app/src/main/resources/application-access-control.yml +++ b/app/src/main/resources/application-access-control.yml @@ -1,21 +1,21 @@ -bff.access-control: - ACTIONS_CREATE: [ portal_admin, portal_designer, portal_operator ] - ACTIONS_GET: [ portal_admin, portal_designer, portal_operator ] - ACTIONS_LIST: [ portal_admin, portal_designer, portal_operator ] - ACTIVE_ALARM_LIST: [portal_admin, portal_designer, portal_operator] - KEY_ENCRYPT_BY_USER: [portal_admin, portal_designer, portal_operator] - KEY_ENCRYPT_BY_VALUE: [portal_admin, portal_designer, portal_operator] - PREFERENCES_CREATE: [portal_admin, portal_designer, portal_operator] - PREFERENCES_GET: [portal_admin, portal_designer, portal_operator] - PREFERENCES_UPDATE: [portal_admin, portal_designer, portal_operator] - ROLE_LIST: ["*"] - USER_CREATE: [portal_admin, portal_designer, portal_operator] - USER_DELETE: [portal_admin, portal_designer, portal_operator] - USER_GET: [portal_admin, portal_designer, portal_operator] - USER_LIST_AVAILABLE_ROLES: [portal_admin, portal_designer, portal_operator] - USER_LIST_ROLES: [portal_admin, portal_designer, portal_operator] - USER_LIST: [portal_admin, portal_designer, portal_operator] - USER_UPDATE_PASSWORD: [portal_admin, portal_designer, portal_operator] - USER_UPDATE_ROLES: [portal_admin, portal_designer, portal_operator] - USER_UPDATE: [portal_admin, portal_designer, portal_operator] - +bff: + access-control: + ACTIONS_CREATE: [ portal_admin, portal_designer, portal_operator ] + ACTIONS_GET: [ portal_admin, portal_designer, portal_operator ] + ACTIONS_LIST: [ portal_admin, portal_designer, portal_operator ] + ACTIVE_ALARM_LIST: [portal_admin, portal_designer, portal_operator] + KEY_ENCRYPT_BY_USER: [portal_admin, portal_designer, portal_operator] + KEY_ENCRYPT_BY_VALUE: [portal_admin, portal_designer, portal_operator] + PREFERENCES_CREATE: [portal_admin, portal_designer, portal_operator] + PREFERENCES_GET: [portal_admin, portal_designer, portal_operator] + PREFERENCES_UPDATE: [portal_admin, portal_designer, portal_operator] + ROLE_LIST: ["*"] + USER_CREATE: [portal_admin, portal_designer, portal_operator] + USER_DELETE: [portal_admin, portal_designer, portal_operator] + USER_GET: [portal_admin, portal_designer, portal_operator] + USER_LIST_AVAILABLE_ROLES: [portal_admin, portal_designer, portal_operator] + USER_LIST_ROLES: [portal_admin, portal_designer, portal_operator] + USER_LIST: [portal_admin, portal_designer, portal_operator] + USER_UPDATE_PASSWORD: [portal_admin, portal_designer, portal_operator] + USER_UPDATE_ROLES: [portal_admin, portal_designer, portal_operator] + USER_UPDATE: [portal_admin, portal_designer, portal_operator] diff --git a/app/src/main/resources/application.yml b/app/src/main/resources/application.yml index 367b33c..a99ff0b 100644 --- a/app/src/main/resources/application.yml +++ b/app/src/main/resources/application.yml @@ -52,4 +52,8 @@ bff: preferences-url: ${PREFERENCES_URL} history-url: ${HISTORY_URL} keycloak-url: ${KEYCLOAK_URL} + endpoints: + unauthenticated: /api-docs.html, /api.yaml, /webjars/**, /actuator/** + rbac: + endpoints-excluded: /actuator/**, **/actuator/**, */actuator/**, /**/actuator/**, /*/actuator/** diff --git a/app/src/test/java/org/onap/portalng/bff/BaseIntegrationTest.java b/app/src/test/java/org/onap/portalng/bff/BaseIntegrationTest.java index 1311ac7..528568d 100644 --- a/app/src/test/java/org/onap/portalng/bff/BaseIntegrationTest.java +++ b/app/src/test/java/org/onap/portalng/bff/BaseIntegrationTest.java @@ -52,8 +52,8 @@ import org.springframework.context.annotation.Bean; import org.springframework.http.MediaType; /** Base class for all tests that has the common config including port, realm, logging and auth. */ -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @AutoConfigureWireMock(port = 0) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public abstract class BaseIntegrationTest { @TestConfiguration diff --git a/app/src/test/java/org/onap/portalng/bff/idtoken/IdTokenExchangeFilterFunctionTest.java b/app/src/test/java/org/onap/portalng/bff/idtoken/IdTokenExchangeFilterFunctionTest.java index cb6694a..b7491f2 100644 --- a/app/src/test/java/org/onap/portalng/bff/idtoken/IdTokenExchangeFilterFunctionTest.java +++ b/app/src/test/java/org/onap/portalng/bff/idtoken/IdTokenExchangeFilterFunctionTest.java @@ -30,6 +30,7 @@ import java.util.UUID; import org.junit.jupiter.api.Test; import org.onap.portalng.bff.BaseIntegrationTest; import org.onap.portalng.bff.config.IdTokenExchangeFilterFunction; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpMethod; import org.springframework.mock.http.server.reactive.MockServerHttpRequest; import org.springframework.mock.web.server.MockServerWebExchange; @@ -41,10 +42,10 @@ import reactor.core.publisher.Mono; class IdTokenExchangeFilterFunctionTest extends BaseIntegrationTest { + @Autowired IdTokenExchangeFilterFunction filterFunction; + @Test void idTokenIsCorrectlyPropagated() { - final IdTokenExchangeFilterFunction filterFunction = new IdTokenExchangeFilterFunction(); - final String idToken = UUID.randomUUID().toString(); final ServerWebExchange serverWebExchange = MockServerWebExchange.builder( @@ -72,8 +73,6 @@ class IdTokenExchangeFilterFunctionTest extends BaseIntegrationTest { @Test void exceptionIsThrownWhenIdTokenIsMissingInRequest() { - final IdTokenExchangeFilterFunction filterFunction = new IdTokenExchangeFilterFunction(); - final ServerWebExchange serverWebExchange = MockServerWebExchange.builder(MockServerHttpRequest.get("http://localhost:8000")).build(); diff --git a/app/src/test/resources/application-access-control.yml b/app/src/test/resources/application-access-control.yml deleted file mode 100644 index 6fda781..0000000 --- a/app/src/test/resources/application-access-control.yml +++ /dev/null @@ -1,21 +0,0 @@ -bff: - access-control: - ACTIONS_CREATE: [ portal_admin, portal_designer, portal_operator ] - ACTIONS_GET: [ portal_admin, portal_designer, portal_operator ] - ACTIONS_LIST: [ portal_admin, portal_designer, portal_operator ] - ACTIVE_ALARM_LIST: [portal_admin, portal_designer, portal_operator] - KEY_ENCRYPT_BY_USER: [portal_admin, portal_designer, portal_operator] - KEY_ENCRYPT_BY_VALUE: [portal_admin, portal_designer, portal_operator] - PREFERENCES_CREATE: [portal_admin, portal_designer, portal_operator] - PREFERENCES_GET: [portal_admin, portal_designer, portal_operator] - PREFERENCES_UPDATE: [portal_admin, portal_designer, portal_operator] - ROLE_LIST: ["*"] - USER_CREATE: [portal_admin, portal_designer, portal_operator] - USER_DELETE: [portal_admin, portal_designer, portal_operator] - USER_GET: [portal_admin, portal_designer, portal_operator] - USER_LIST_AVAILABLE_ROLES: [portal_admin, portal_designer, portal_operator] - USER_LIST_ROLES: [portal_admin, portal_designer, portal_operator] - USER_LIST: [portal_admin, portal_designer, portal_operator] - USER_UPDATE_PASSWORD: [portal_admin, portal_designer, portal_operator] - USER_UPDATE_ROLES: [portal_admin, portal_designer, portal_operator] - USER_UPDATE: [portal_admin, portal_designer, portal_operator] diff --git a/app/src/test/resources/application.yml b/app/src/test/resources/application.yml index 3e423e4..04e6a57 100644 --- a/app/src/test/resources/application.yml +++ b/app/src/test/resources/application.yml @@ -1,7 +1,6 @@ -logging: - level: - org.springframework.web: TRACE - +management: + tracing: + enabled: false spring: profiles: include: @@ -22,12 +21,14 @@ spring: resourceserver: jwt: jwk-set-uri: http://localhost:${wiremock.server.port}/realms/ONAP/protocol/openid-connect/certs - jackson: - serialization: - FAIL_ON_EMPTY_BEANS: false bff: realm: ONAP preferences-url: http://localhost:${wiremock.server.port} history-url: http://localhost:${wiremock.server.port} keycloak-url: http://localhost:${wiremock.server.port} + endpoints: + unauthenticated: /api-docs.html, /api.yaml, /webjars/**, /actuator/** + rbac: + endpoints-excluded: /actuator/**, **/actuator/**, */actuator/**, /**/actuator/**, /*/actuator/** + diff --git a/app/src/test/resources/logback-spring.xml b/app/src/test/resources/logback-spring.xml deleted file mode 100644 index 45bd7e2..0000000 --- a/app/src/test/resources/logback-spring.xml +++ /dev/null @@ -1,18 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<configuration scan="true"> - <include resource="org/springframework/boot/logging/logback/defaults.xml"/> - - <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender"> - <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> - <level>${LOGBACK_LEVEL:-info}</level> - </filter> - <encoder> - <pattern>${CONSOLE_LOG_PATTERN}</pattern> - <charset>utf8</charset> - </encoder> - </appender> - - <root level="all"> - <appender-ref ref="stdout"/> - </root> -</configuration>
\ No newline at end of file diff --git a/build.gradle b/build.gradle index bb57241..3e3b47d 100644 --- a/build.gradle +++ b/build.gradle @@ -13,6 +13,7 @@ ext { logbackVersion = '7.4' lombokVersion = '1.18.28' micrometerVersion = '1.1.4' + swaggerV3Version = '2.2.21' // app wiremockVersion = '4.0.4' diff --git a/lib/build.gradle b/lib/build.gradle index adb82ea..44b2920 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -29,6 +29,7 @@ dependencies { implementation "org.mapstruct:mapstruct:$mapStructVersion" implementation "org.mapstruct.extensions.spring:mapstruct-spring-annotations:$mapStructExtensionsVersion" implementation "org.mapstruct.extensions.spring:mapstruct-spring-extensions:$mapStructExtensionsVersion" + implementation "io.swagger.core.v3:swagger-annotations:$swaggerV3Version" implementation(platform("io.micrometer:micrometer-tracing-bom:$micrometerVersion")) implementation("io.micrometer:micrometer-tracing") diff --git a/lib/src/main/java/org/onap/portalng/bff/config/BeansConfig.java b/lib/src/main/java/org/onap/portalng/bff/config/BeansConfig.java index a23ac0c..f64da12 100644 --- a/lib/src/main/java/org/onap/portalng/bff/config/BeansConfig.java +++ b/lib/src/main/java/org/onap/portalng/bff/config/BeansConfig.java @@ -74,11 +74,6 @@ public class BeansConfig { return oauth2Filter; } - @Bean(name = ID_TOKEN_EXCHANGE_FILTER_FUNCTION) - ExchangeFilterFunction idTokenExchangeFilterFunction() { - return new IdTokenExchangeFilterFunction(); - } - @Bean(name = ERROR_HANDLING_EXCHANGE_FILTER_FUNCTION) ExchangeFilterFunction errorHandlingExchangeFilterFunction() { return ExchangeFilterFunction.ofResponseProcessor( diff --git a/lib/src/main/java/org/onap/portalng/bff/config/BffConfig.java b/lib/src/main/java/org/onap/portalng/bff/config/BffConfig.java index 5bc618c..3fada84 100644 --- a/lib/src/main/java/org/onap/portalng/bff/config/BffConfig.java +++ b/lib/src/main/java/org/onap/portalng/bff/config/BffConfig.java @@ -24,8 +24,8 @@ package org.onap.portalng.bff.config; import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; -import java.util.List; import java.util.Map; +import java.util.Set; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.zalando.problem.Problem; @@ -37,8 +37,8 @@ import reactor.core.publisher.Mono; * urls. */ @Valid -@ConfigurationProperties("bff") @Data +@ConfigurationProperties("bff") public class BffConfig { @NotBlank private final String realm; @@ -46,9 +46,9 @@ public class BffConfig { @NotBlank private final String historyUrl; @NotBlank private final String keycloakUrl; - @NotNull private final Map<String, List<String>> accessControl; + @NotNull private final Map<String, Set<String>> accessControl; - public Mono<List<String>> getRoles(String method) { + public Mono<Set<String>> getRoles(String method) { return Mono.just(accessControl) .map(control -> control.get(method)) .onErrorResume( diff --git a/lib/src/main/java/org/onap/portalng/bff/config/IdTokenExchangeFilterFunction.java b/lib/src/main/java/org/onap/portalng/bff/config/IdTokenExchangeFilterFunction.java index d747f3a..26db78d 100644 --- a/lib/src/main/java/org/onap/portalng/bff/config/IdTokenExchangeFilterFunction.java +++ b/lib/src/main/java/org/onap/portalng/bff/config/IdTokenExchangeFilterFunction.java @@ -23,9 +23,10 @@ package org.onap.portalng.bff.config; import com.nimbusds.jwt.JWTParser; import java.text.ParseException; -import java.util.Collections; import java.util.List; -import java.util.Optional; +import java.util.Set; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; import org.springframework.util.AntPathMatcher; import org.springframework.web.reactive.function.client.ClientRequest; import org.springframework.web.reactive.function.client.ClientResponse; @@ -37,27 +38,32 @@ import org.zalando.problem.Status; import reactor.core.Exceptions; import reactor.core.publisher.Mono; +@Component public class IdTokenExchangeFilterFunction implements ExchangeFilterFunction { public static final String X_AUTH_IDENTITY_HEADER = "X-Auth-Identity"; public static final String CLAIM_NAME_ROLES = "roles"; - private static final List<String> EXCLUDED_PATHS_PATTERNS = - List.of( - "/actuator/**", "**/actuator/**", "*/actuator/**", "/**/actuator/**", "/*/actuator/**"); + private final List<String> rbacExcludedPatterns; private static final Mono<ServerWebExchange> serverWebExchangeFromContext = Mono.deferContextual(Mono::just) .filter(context -> context.hasKey(ServerWebExchange.class)) .map(context -> context.get(ServerWebExchange.class)); + private final AntPathMatcher antPathMatcher = new AntPathMatcher(); + + public IdTokenExchangeFilterFunction( + @Value("${bff.rbac.endpoints-excluded}") List<String> rbacExcludedPatterns) { + this.rbacExcludedPatterns = rbacExcludedPatterns; + } + @Override public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) { boolean shouldNotFilter = - EXCLUDED_PATHS_PATTERNS.stream() + rbacExcludedPatterns.stream() .anyMatch( - excludedPath -> - new AntPathMatcher().match(excludedPath, request.url().getRawPath())); + excludedPath -> antPathMatcher.match(excludedPath, request.url().getRawPath())); if (shouldNotFilter) { return next.exchange(request).switchIfEmpty(Mono.defer(() -> next.exchange(request))); } @@ -86,7 +92,7 @@ public class IdTokenExchangeFilterFunction implements ExchangeFilterFunction { } public static Mono<Void> validateAccess( - ServerWebExchange exchange, List<String> rolesListForMethod) { + ServerWebExchange exchange, Set<String> rolesListForMethod) { return extractRoles(exchange) .map(roles -> roles.stream().anyMatch(rolesListForMethod::contains)) @@ -110,16 +116,13 @@ public class IdTokenExchangeFilterFunction implements ExchangeFilterFunction { .map( jwt -> { try { - return Optional.of(jwt.getJWTClaimsSet()); + return jwt.getJWTClaimsSet().getClaim(CLAIM_NAME_ROLES); } catch (ParseException e) { throw Exceptions.propagate(e); } }) - .map( - optionalClaimsSet -> - optionalClaimsSet - .map(claimsSet -> claimsSet.getClaim(CLAIM_NAME_ROLES)) - .map(obj -> (List<String>) obj)) - .map(roles -> roles.orElse(Collections.emptyList())); + .filter(List.class::isInstance) + .map(roles -> (List<String>) roles) + .switchIfEmpty(Mono.just(List.<String>of())); } } diff --git a/lib/src/main/java/org/onap/portalng/bff/config/SecurityConfig.java b/lib/src/main/java/org/onap/portalng/bff/config/SecurityConfig.java index 2a0f701..94c87bd 100644 --- a/lib/src/main/java/org/onap/portalng/bff/config/SecurityConfig.java +++ b/lib/src/main/java/org/onap/portalng/bff/config/SecurityConfig.java @@ -21,9 +21,9 @@ package org.onap.portalng.bff.config; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; import org.springframework.security.config.web.server.ServerHttpSecurity; import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager; @@ -34,9 +34,13 @@ import org.springframework.security.oauth2.client.web.DefaultReactiveOAuth2Autho import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository; import org.springframework.security.web.server.SecurityWebFilterChain; -@EnableWebFluxSecurity @Configuration +@EnableWebFluxSecurity public class SecurityConfig { + + @Value("${bff.endpoints.unauthenticated}") + private String[] unauthenticatedEndpoints; + @Bean public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { return http.httpBasic() @@ -48,7 +52,7 @@ public class SecurityConfig { .cors() .and() .authorizeExchange() - .pathMatchers(HttpMethod.GET, "/api-docs.html", "/api.yaml", "/webjars/**", "/actuator/**") + .pathMatchers(unauthenticatedEndpoints) .permitAll() .anyExchange() .authenticated() |