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
|
* Utility class to extract authorities out of the jwt. It interprets the user's role as their authorities.
|
||||||
* role as their authorities.
|
|
||||||
*/
|
*/
|
||||||
private record DefaultJwtAuthoritiesExtractor(GrantedAuthoritiesMapper authoritiesMapper) implements JwtAuthoritiesExtractor {
|
private record DefaultJwtAuthoritiesExtractor(GrantedAuthoritiesMapper authoritiesMapper) implements JwtAuthoritiesExtractor {
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import org.eclipse.hawkbit.im.authentication.TenantAwareUserProperties.User;
|
|||||||
import org.eclipse.hawkbit.security.DdiSecurityProperties;
|
import org.eclipse.hawkbit.security.DdiSecurityProperties;
|
||||||
import org.eclipse.hawkbit.security.InMemoryUserAuthoritiesResolver;
|
import org.eclipse.hawkbit.security.InMemoryUserAuthoritiesResolver;
|
||||||
import org.eclipse.hawkbit.security.HawkbitSecurityProperties;
|
import org.eclipse.hawkbit.security.HawkbitSecurityProperties;
|
||||||
|
import org.eclipse.hawkbit.security.MDCHandler;
|
||||||
import org.eclipse.hawkbit.security.SecurityContextSerializer;
|
import org.eclipse.hawkbit.security.SecurityContextSerializer;
|
||||||
import org.eclipse.hawkbit.security.SecurityContextTenantAware;
|
import org.eclipse.hawkbit.security.SecurityContextTenantAware;
|
||||||
import org.eclipse.hawkbit.security.SecurityTokenGenerator;
|
import org.eclipse.hawkbit.security.SecurityTokenGenerator;
|
||||||
@@ -121,36 +122,30 @@ public class SecurityAutoConfiguration {
|
|||||||
return new SystemSecurityContext(tenantAware, roleHierarchy);
|
return new SystemSecurityContext(tenantAware, roleHierarchy);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Bean
|
||||||
* @return {@link SecurityTokenGenerator} bean
|
@ConditionalOnMissingBean
|
||||||
*/
|
public MDCHandler mdcHandler() {
|
||||||
|
return MDCHandler.getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnMissingBean
|
@ConditionalOnMissingBean
|
||||||
public SecurityTokenGenerator securityTokenGenerator() {
|
public SecurityTokenGenerator securityTokenGenerator() {
|
||||||
return new SecurityTokenGenerator();
|
return new SecurityTokenGenerator();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {@link AuthenticationSuccessHandler} bean
|
|
||||||
*/
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnMissingBean
|
@ConditionalOnMissingBean
|
||||||
public AuthenticationSuccessHandler authenticationSuccessHandler() {
|
public AuthenticationSuccessHandler authenticationSuccessHandler() {
|
||||||
return new SimpleUrlAuthenticationSuccessHandler();
|
return new SimpleUrlAuthenticationSuccessHandler();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {@link LogoutHandler} bean
|
|
||||||
*/
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnMissingBean
|
@ConditionalOnMissingBean
|
||||||
public LogoutHandler logoutHandler() {
|
public LogoutHandler logoutHandler() {
|
||||||
return new SecurityContextLogoutHandler();
|
return new SecurityContextLogoutHandler();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {@link LogoutSuccessHandler} bean
|
|
||||||
*/
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnMissingBean
|
@ConditionalOnMissingBean
|
||||||
public LogoutSuccessHandler logoutSuccessHandler() {
|
public LogoutSuccessHandler logoutSuccessHandler() {
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import org.eclipse.hawkbit.security.HttpControllerPreAuthenticateSecurityTokenFi
|
|||||||
import org.eclipse.hawkbit.security.HttpControllerPreAuthenticatedGatewaySecurityTokenFilter;
|
import org.eclipse.hawkbit.security.HttpControllerPreAuthenticatedGatewaySecurityTokenFilter;
|
||||||
import org.eclipse.hawkbit.security.HttpControllerPreAuthenticatedSecurityHeaderFilter;
|
import org.eclipse.hawkbit.security.HttpControllerPreAuthenticatedSecurityHeaderFilter;
|
||||||
import org.eclipse.hawkbit.security.HttpDownloadAuthenticationFilter;
|
import org.eclipse.hawkbit.security.HttpDownloadAuthenticationFilter;
|
||||||
|
import org.eclipse.hawkbit.security.MDCHandler;
|
||||||
import org.eclipse.hawkbit.security.PreAuthTokenSourceTrustAuthenticationProvider;
|
import org.eclipse.hawkbit.security.PreAuthTokenSourceTrustAuthenticationProvider;
|
||||||
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
||||||
import org.eclipse.hawkbit.tenancy.TenantAware;
|
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.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
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.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.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
|
||||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
@@ -206,6 +206,8 @@ public class SecurityManagedConfiguration {
|
|||||||
.sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
|
.sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MDCHandler.getInstance().addLoggingFilter(http);
|
||||||
|
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -321,6 +323,8 @@ public class SecurityManagedConfiguration {
|
|||||||
.sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
|
.sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MDCHandler.getInstance().addLoggingFilter(http);
|
||||||
|
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -383,6 +387,8 @@ public class SecurityManagedConfiguration {
|
|||||||
.addFilterBefore(downloadIdAuthenticationFilter, AuthorizationFilter.class)
|
.addFilterBefore(downloadIdAuthenticationFilter, AuthorizationFilter.class)
|
||||||
.sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
|
.sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
|
||||||
|
|
||||||
|
MDCHandler.getInstance().addLoggingFilter(http);
|
||||||
|
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -485,6 +491,8 @@ public class SecurityManagedConfiguration {
|
|||||||
httpSecurityCustomizer.customize(http);
|
httpSecurityCustomizer.customize(http);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MDCHandler.getInstance().addLoggingFilter(http);
|
||||||
|
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -521,7 +529,6 @@ public class SecurityManagedConfiguration {
|
|||||||
return firewall;
|
return firewall;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static class IgnorePathsStrictHttpFirewall extends StrictHttpFirewall {
|
private static class IgnorePathsStrictHttpFirewall extends StrictHttpFirewall {
|
||||||
|
|
||||||
private final Collection<String> pathsToIgnore;
|
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, () -> {
|
return tenantAware.runAsTenant(tenant, () -> {
|
||||||
try {
|
try {
|
||||||
setSystemContext(SecurityContextHolder.getContext());
|
setSystemContext(SecurityContextHolder.getContext());
|
||||||
return callable.call();
|
return MDCHandler.getInstance().withLogging(callable);
|
||||||
|
|
||||||
} catch (final RuntimeException e) {
|
} catch (final RuntimeException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
SecurityContextHolder.setContext(oldContext);
|
SecurityContextHolder.setContext(oldContext);
|
||||||
log.debug("leaving system code execution");
|
log.debug("leaving system code execution");
|
||||||
@@ -154,21 +152,18 @@ public class SystemSecurityContext {
|
|||||||
return tenantAware.runAsTenant(tenant, () -> {
|
return tenantAware.runAsTenant(tenant, () -> {
|
||||||
try {
|
try {
|
||||||
setCustomSecurityContext(tenant, oldContext.getAuthentication().getPrincipal(), authorities);
|
setCustomSecurityContext(tenant, oldContext.getAuthentication().getPrincipal(), authorities);
|
||||||
return callable.call();
|
return MDCHandler.getInstance().withLogging(callable);
|
||||||
|
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
SecurityContextHolder.setContext(oldContext);
|
SecurityContextHolder.setContext(oldContext);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {@code true} if the current running code is running as system
|
* @return {@code true} if the current running code is running as system code block.
|
||||||
* code block.
|
|
||||||
*/
|
*/
|
||||||
public boolean isCurrentThreadSystemCode() {
|
public boolean isCurrentThreadSystemCode() {
|
||||||
return SecurityContextHolder.getContext().getAuthentication() instanceof SystemCodeAuthentication;
|
return SecurityContextHolder.getContext().getAuthentication() instanceof SystemCodeAuthentication;
|
||||||
@@ -195,6 +190,13 @@ public class SystemSecurityContext {
|
|||||||
return false;
|
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,
|
private void setCustomSecurityContext(final String tenantId, final Object principal,
|
||||||
final Collection<? extends GrantedAuthority> authorities) {
|
final Collection<? extends GrantedAuthority> authorities) {
|
||||||
final AnonymousAuthenticationToken authenticationToken = new AnonymousAuthenticationToken(
|
final AnonymousAuthenticationToken authenticationToken = new AnonymousAuthenticationToken(
|
||||||
@@ -205,13 +207,6 @@ public class SystemSecurityContext {
|
|||||||
SecurityContextHolder.setContext(securityContextImpl);
|
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
|
* An implementation of the Spring's {@link Authentication} object which is
|
||||||
* used within a system security code block and wraps the original
|
* used within a system security code block and wraps the original
|
||||||
|
|||||||
Reference in New Issue
Block a user