Feature/add tenant and user into mdc (#1806)
* Add MDC * Add tenant/user into MDC in order to be possible to be used in logging Enabled by default. Could be disabled via hawkbit.logging.mdchandler.enable=false Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com> --------- Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com>
This commit is contained in:
@@ -178,8 +178,7 @@ public class OidcUserManagementAutoConfiguration {
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility class to extract authorities out of the jwt. It interprets the user's
|
||||
* role as their authorities.
|
||||
* Utility class to extract authorities out of the jwt. It interprets the user's role as their authorities.
|
||||
*/
|
||||
private record DefaultJwtAuthoritiesExtractor(GrantedAuthoritiesMapper authoritiesMapper) implements JwtAuthoritiesExtractor {
|
||||
|
||||
@@ -220,4 +219,4 @@ public class OidcUserManagementAutoConfiguration {
|
||||
return new LinkedHashSet<>(authorities);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import org.eclipse.hawkbit.im.authentication.TenantAwareUserProperties.User;
|
||||
import org.eclipse.hawkbit.security.DdiSecurityProperties;
|
||||
import org.eclipse.hawkbit.security.InMemoryUserAuthoritiesResolver;
|
||||
import org.eclipse.hawkbit.security.HawkbitSecurityProperties;
|
||||
import org.eclipse.hawkbit.security.MDCHandler;
|
||||
import org.eclipse.hawkbit.security.SecurityContextSerializer;
|
||||
import org.eclipse.hawkbit.security.SecurityContextTenantAware;
|
||||
import org.eclipse.hawkbit.security.SecurityTokenGenerator;
|
||||
@@ -121,36 +122,30 @@ public class SecurityAutoConfiguration {
|
||||
return new SystemSecurityContext(tenantAware, roleHierarchy);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@link SecurityTokenGenerator} bean
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public MDCHandler mdcHandler() {
|
||||
return MDCHandler.getInstance();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public SecurityTokenGenerator securityTokenGenerator() {
|
||||
return new SecurityTokenGenerator();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@link AuthenticationSuccessHandler} bean
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public AuthenticationSuccessHandler authenticationSuccessHandler() {
|
||||
return new SimpleUrlAuthenticationSuccessHandler();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@link LogoutHandler} bean
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public LogoutHandler logoutHandler() {
|
||||
return new SecurityContextLogoutHandler();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@link LogoutSuccessHandler} bean
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public LogoutSuccessHandler logoutSuccessHandler() {
|
||||
|
||||
@@ -35,6 +35,7 @@ import org.eclipse.hawkbit.security.HttpControllerPreAuthenticateSecurityTokenFi
|
||||
import org.eclipse.hawkbit.security.HttpControllerPreAuthenticatedGatewaySecurityTokenFilter;
|
||||
import org.eclipse.hawkbit.security.HttpControllerPreAuthenticatedSecurityHeaderFilter;
|
||||
import org.eclipse.hawkbit.security.HttpDownloadAuthenticationFilter;
|
||||
import org.eclipse.hawkbit.security.MDCHandler;
|
||||
import org.eclipse.hawkbit.security.PreAuthTokenSourceTrustAuthenticationProvider;
|
||||
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
||||
import org.eclipse.hawkbit.tenancy.TenantAware;
|
||||
@@ -57,7 +58,6 @@ import org.springframework.security.config.annotation.method.configuration.Enabl
|
||||
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.configurers.AbstractHttpConfigurer;
|
||||
import org.springframework.security.config.annotation.web.configurers.HttpBasicConfigurer;
|
||||
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.core.Authentication;
|
||||
@@ -206,6 +206,8 @@ public class SecurityManagedConfiguration {
|
||||
.sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
|
||||
}
|
||||
|
||||
MDCHandler.getInstance().addLoggingFilter(http);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
}
|
||||
@@ -321,6 +323,8 @@ public class SecurityManagedConfiguration {
|
||||
.sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
|
||||
}
|
||||
|
||||
MDCHandler.getInstance().addLoggingFilter(http);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
}
|
||||
@@ -383,6 +387,8 @@ public class SecurityManagedConfiguration {
|
||||
.addFilterBefore(downloadIdAuthenticationFilter, AuthorizationFilter.class)
|
||||
.sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
|
||||
|
||||
MDCHandler.getInstance().addLoggingFilter(http);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
}
|
||||
@@ -485,6 +491,8 @@ public class SecurityManagedConfiguration {
|
||||
httpSecurityCustomizer.customize(http);
|
||||
}
|
||||
|
||||
MDCHandler.getInstance().addLoggingFilter(http);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@@ -521,7 +529,6 @@ public class SecurityManagedConfiguration {
|
||||
return firewall;
|
||||
}
|
||||
|
||||
|
||||
private static class IgnorePathsStrictHttpFirewall extends StrictHttpFirewall {
|
||||
|
||||
private final Collection<String> pathsToIgnore;
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
/**
|
||||
* Copyright (c) 2024 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* 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.security;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails;
|
||||
import org.slf4j.MDC;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.web.access.intercept.AuthorizationFilter;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
@NoArgsConstructor
|
||||
public class MDCHandler {
|
||||
|
||||
public static String MDC_KEY_TENANT = "tenant";
|
||||
public static String MDC_KEY_USER = "user";
|
||||
|
||||
private static final MDCHandler SINGLETON = new MDCHandler();
|
||||
|
||||
@Value("${hawkbit.logging.mdchandler.enable:true}")
|
||||
private boolean mdcEnabled;
|
||||
@Autowired
|
||||
private SpringSecurityAuditorAware springSecurityAuditorAware;
|
||||
@Autowired
|
||||
private SystemSecurityContext securityContext;
|
||||
|
||||
/**
|
||||
* @return The holder singleton instance.
|
||||
*/
|
||||
public static MDCHandler getInstance() {
|
||||
return SINGLETON;
|
||||
}
|
||||
|
||||
public <T> T withLogging(final Callable<T> callable) throws Exception {
|
||||
if (!mdcEnabled) {
|
||||
return callable.call();
|
||||
}
|
||||
|
||||
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
final String tenant;
|
||||
if (authentication.getDetails() instanceof TenantAwareAuthenticationDetails tenantAwareAuthenticationDetails) {
|
||||
tenant = tenantAwareAuthenticationDetails.getTenant();
|
||||
} else {
|
||||
tenant = null;
|
||||
}
|
||||
|
||||
final String currentTenant = MDC.get(MDC_KEY_TENANT);
|
||||
if (Objects.equals(currentTenant, tenant)) {
|
||||
return putUser(callable);
|
||||
} else {
|
||||
set(MDC_KEY_TENANT, tenant);
|
||||
try {
|
||||
return putUser(callable);
|
||||
} finally {
|
||||
set(MDC_KEY_TENANT, currentTenant);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addLoggingFilter(final HttpSecurity httpSecurity) {
|
||||
if (!mdcEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
httpSecurity.addFilterBefore(new OncePerRequestFilter() {
|
||||
@Override
|
||||
protected void doFilterInternal(
|
||||
final HttpServletRequest request, final HttpServletResponse response, final FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
try {
|
||||
withLogging(() -> {
|
||||
filterChain.doFilter(request, response);
|
||||
return null;
|
||||
});
|
||||
} catch (final RuntimeException re) {
|
||||
throw re;
|
||||
} catch (final WrappedException we) {
|
||||
final Throwable cause = we.getCause();
|
||||
if (cause instanceof ServletException se) {
|
||||
throw se;
|
||||
} else if (cause instanceof IOException ioe) {
|
||||
throw ioe;
|
||||
} else {
|
||||
throw we.toRuntimeException();
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
// should never be here - if mdc is handler is enabled non runtime exceptions are
|
||||
// always wrapped
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}, AuthorizationFilter.class);
|
||||
}
|
||||
|
||||
private <T> T putUser(final Callable<T> callable) throws WrappedException {
|
||||
final String user = springSecurityAuditorAware
|
||||
.getCurrentAuditor()
|
||||
.map(username -> (securityContext.isCurrentThreadSystemCode() ? "as " : "") + username)
|
||||
.orElse(null);
|
||||
|
||||
final String currentUser = MDC.get(MDC_KEY_USER);
|
||||
try {
|
||||
if (Objects.equals(currentUser, user)) {
|
||||
return callable.call();
|
||||
} else {
|
||||
set(MDC_KEY_USER, user);
|
||||
try {
|
||||
return callable.call();
|
||||
} finally {
|
||||
set(MDC_KEY_USER, currentUser);
|
||||
}
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
if (e instanceof RuntimeException) {
|
||||
throw (RuntimeException) e;
|
||||
} else {
|
||||
throw new WrappedException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void set(final String key, final String value) {
|
||||
if (value == null) {
|
||||
MDC.remove(key);
|
||||
} else {
|
||||
MDC.put(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
// Wraps catchable exceptions to rethrow
|
||||
public static class WrappedException extends Exception {
|
||||
|
||||
public WrappedException(final Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public RuntimeException toRuntimeException() {
|
||||
return new RuntimeException(getCause() == null ? this : getCause());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -115,15 +115,13 @@ public class SystemSecurityContext {
|
||||
return tenantAware.runAsTenant(tenant, () -> {
|
||||
try {
|
||||
setSystemContext(SecurityContextHolder.getContext());
|
||||
return callable.call();
|
||||
|
||||
return MDCHandler.getInstance().withLogging(callable);
|
||||
} catch (final RuntimeException e) {
|
||||
throw e;
|
||||
} catch (final Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
|
||||
} finally {
|
||||
SecurityContextHolder.setContext(oldContext);
|
||||
log.debug("leaving system code execution");
|
||||
@@ -154,21 +152,18 @@ public class SystemSecurityContext {
|
||||
return tenantAware.runAsTenant(tenant, () -> {
|
||||
try {
|
||||
setCustomSecurityContext(tenant, oldContext.getAuthentication().getPrincipal(), authorities);
|
||||
return callable.call();
|
||||
|
||||
return MDCHandler.getInstance().withLogging(callable);
|
||||
} catch (final Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
|
||||
} finally {
|
||||
SecurityContextHolder.setContext(oldContext);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@code true} if the current running code is running as system
|
||||
* code block.
|
||||
* @return {@code true} if the current running code is running as system code block.
|
||||
*/
|
||||
public boolean isCurrentThreadSystemCode() {
|
||||
return SecurityContextHolder.getContext().getAuthentication() instanceof SystemCodeAuthentication;
|
||||
@@ -195,6 +190,13 @@ public class SystemSecurityContext {
|
||||
return false;
|
||||
}
|
||||
|
||||
static void setSystemContext(final SecurityContext oldContext) {
|
||||
final Authentication oldAuthentication = oldContext.getAuthentication();
|
||||
final SecurityContextImpl securityContextImpl = new SecurityContextImpl();
|
||||
securityContextImpl.setAuthentication(new SystemCodeAuthentication(oldAuthentication));
|
||||
SecurityContextHolder.setContext(securityContextImpl);
|
||||
}
|
||||
|
||||
private void setCustomSecurityContext(final String tenantId, final Object principal,
|
||||
final Collection<? extends GrantedAuthority> authorities) {
|
||||
final AnonymousAuthenticationToken authenticationToken = new AnonymousAuthenticationToken(
|
||||
@@ -205,13 +207,6 @@ public class SystemSecurityContext {
|
||||
SecurityContextHolder.setContext(securityContextImpl);
|
||||
}
|
||||
|
||||
static void setSystemContext(final SecurityContext oldContext) {
|
||||
final Authentication oldAuthentication = oldContext.getAuthentication();
|
||||
final SecurityContextImpl securityContextImpl = new SecurityContextImpl();
|
||||
securityContextImpl.setAuthentication(new SystemCodeAuthentication(oldAuthentication));
|
||||
SecurityContextHolder.setContext(securityContextImpl);
|
||||
}
|
||||
|
||||
/**
|
||||
* An implementation of the Spring's {@link Authentication} object which is
|
||||
* used within a system security code block and wraps the original
|
||||
@@ -265,4 +260,4 @@ public class SystemSecurityContext {
|
||||
// not needed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user