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:
Avgustin Marinov
2024-08-09 14:27:07 +03:00
committed by GitHub
parent bcafdbdb86
commit c8321fdb44
5 changed files with 189 additions and 33 deletions

View File

@@ -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);
}
}
}
}

View File

@@ -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() {

View File

@@ -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;

View File

@@ -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());
}
}
}

View File

@@ -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
}
}
}
}