Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

W-17849656 | [add] code changes for FIPS compliance #266

Merged
merged 15 commits into from
Mar 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions module/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,16 @@
<munit.output.directory>${basedir}/target/test-mule/munit</munit.output.directory>

<munit.extensions.maven.plugin.version>1.4.0</munit.extensions.maven.plugin.version>
<munit.version>3.3.2</munit.version>
<munit.version>3.4.0</munit.version>
<mtf.tools.version>1.2.0</mtf.tools.version>

<mavenResourcesVersion>3.0.2</mavenResourcesVersion>

<jacoco.version>0.8.10</jacoco.version>
<jacoco.version>0.8.12</jacoco.version>
<sdk.api.version>0.7.0</sdk.api.version>
<http.connector.version>1.8.1</http.connector.version>
<mule.java.module.version>1.2.12</mule.java.module.version>
<mule.sockets.connector.version>1.2.3</mule.sockets.connector.version>
<http.connector.version>1.9.3</http.connector.version>
<mule.java.module.version>1.2.13</mule.java.module.version>
<mule.sockets.connector.version>1.2.5</mule.sockets.connector.version>
</properties>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ public <T> Map<String, T> getObjectsByType(Class<T> type) {
@Override
public void dispose() {
if (applicationContext != null) {
applicationContext.destroy();
applicationContext.close();
applicationContext = null;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@
*/
package org.mule.extension.spring.internal.beanfactory;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;

import org.apache.commons.logging.LogFactory;
import org.mule.extension.spring.internal.util.CustomPostAuthenticationChecks;
import org.mule.extension.spring.internal.util.CustomPreAuthenticationChecks;
import org.mule.runtime.api.exception.MuleRuntimeException;
import org.mule.runtime.api.ioc.ObjectProvider;
import org.mule.runtime.api.lifecycle.Disposable;
import org.mule.runtime.api.lifecycle.Initialisable;
Expand All @@ -19,13 +29,24 @@
import java.util.Set;

import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.TypeConverter;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.cache.NullUserCache;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder;

/**
* {@link DefaultListableBeanFactory} implementation that takes into account the objects provided by the mule artifact for
Expand Down Expand Up @@ -84,6 +105,11 @@ public Object doResolveDependency(DependencyDescriptor descriptor, String beanNa
*/
@Override
protected <T> T doGetBean(String name, Class<T> requiredType, Object[] args, boolean typeCheckOnly) throws BeansException {
if ("fips140-2".equals(System.getProperty("mule.security.model"))) {
if (name.contains(DaoAuthenticationProvider.class.getName()) || DaoAuthenticationProvider.class.equals(requiredType)) {
return (T) authenticationProvider(getUserDetailService(name));
}
}
if (containsBean(name) || !artifactObjectProvider.containsObject(name) || destroying) {
return super.doGetBean(name, requiredType, args, typeCheckOnly);
} else {
Expand Down Expand Up @@ -134,4 +160,59 @@ private boolean implementsLifecycle(BeanDefinition beanDefinition) {
public void markForDestroy() {
this.destroying = true;
}

/**
* TODO improve this code error check, ugly hack to bypass non FIPS compliant security algorithms
*
* The use of sun.misc.Unsafe API is discouraged. For the time being, using sun.misc.Unsafe to instantiate
* DaoAuthenticationProvider is a necessary workaround to avoid FIPS compliance issues caused by MD5 and SHA1.
* However, once we upgrade to Spring 6.x, we can switch to using the new constructor, passing a compliant PasswordEncoder.
* This will allow us to address the compliance issue more cleanly and avoid reliance on unsafe practices.
*/
static DaoAuthenticationProvider authenticationProvider(UserDetailsService userDetailsService) {
try {
Class<?> c = Class.forName("sun.misc.Unsafe");
Field field = Arrays.stream(c.getDeclaredFields()).filter(f -> f.getName().equals("theUnsafe"))
.findFirst().orElseThrow(() -> new RuntimeException("Field not found"));
field.setAccessible(true);
Method allocateInstance = c.getDeclaredMethod("allocateInstance", Class.class);
DaoAuthenticationProvider authProvider =
(DaoAuthenticationProvider) allocateInstance.invoke(field.get(null), DaoAuthenticationProvider.class);
authProvider.setPasswordEncoder(createDelegatingPasswordEncoder());
authProvider.setUserDetailsService(userDetailsService);
authProvider.setUserCache(new NullUserCache());
authProvider.setAuthoritiesMapper(new NullAuthoritiesMapper());

// Setting custom pre and post authentication checks
authProvider.setPreAuthenticationChecks(new CustomPreAuthenticationChecks());
authProvider.setPostAuthenticationChecks(new CustomPostAuthenticationChecks());

Field loggerField = AbstractUserDetailsAuthenticationProvider.class.getDeclaredField("logger");
loggerField.setAccessible(true);
loggerField.set(authProvider, LogFactory.getLog(AbstractUserDetailsAuthenticationProvider.class));

return authProvider;
} catch (ClassNotFoundException | IllegalAccessException | NoSuchMethodException | InvocationTargetException
| NoSuchFieldException e) {
throw new MuleRuntimeException(e);
}
}

static PasswordEncoder createDelegatingPasswordEncoder() {
String encodingId = "bcrypt";
Map<String, PasswordEncoder> encoders = new HashMap<>();
encoders.put(encodingId, new BCryptPasswordEncoder());
encoders.put("pbkdf2", Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8());
return new DelegatingPasswordEncoder(encodingId, encoders);
}

private UserDetailsService getUserDetailService(String name) {
List<PropertyValue> propertyValues = super.getBeanDefinition(name).getPropertyValues().getPropertyValueList();
String userServiceName = propertyValues.stream()
.filter(propertyValue -> "userDetailsService".equals(propertyValue.getName()))
.map(propertyValue -> ((RuntimeBeanReference) propertyValue.getValue()).getBeanName())
.findFirst()
.orElse(null);
return (UserDetailsService) super.getBean(userServiceName != null ? userServiceName : "userService");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com
* The software in this package is published under the terms of the CPAL v1.0
* license, a copy of which has been included with this distribution in the
* LICENSE.txt file.
*/
package org.mule.extension.spring.internal.util;

import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsChecker;

public class CustomPostAuthenticationChecks implements UserDetailsChecker {

@Override
public void check(UserDetails user) {
if (!user.isCredentialsNonExpired()) {
throw new CredentialsExpiredException("User credentials have expired");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com
* The software in this package is published under the terms of the CPAL v1.0
* license, a copy of which has been included with this distribution in the
* LICENSE.txt file.
*/
package org.mule.extension.spring.internal.util;

import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsChecker;

public class CustomPreAuthenticationChecks implements UserDetailsChecker {

@Override
public void check(UserDetails user) {
if (!user.isAccountNonLocked()) {
throw new LockedException("User account is locked");
}
if (!user.isEnabled()) {
throw new DisabledException("User is disabled");
}
if (!user.isAccountNonExpired()) {
throw new AccountExpiredException("User account has expired");
}
}
}
4 changes: 2 additions & 2 deletions module/src/test/resources/beans.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
<ss:authentication-manager alias="authenticationManager">
<ss:authentication-provider>
<ss:user-service id="userService">
<ss:user name="admin" password="{noop}admin" authorities="ROLE_ADMIN" />
<ss:user name="anon" password="{noop}anon" authorities="ROLE_ANON" />
<ss:user name="admin" password="{bcrypt}$2y$10$L7nVRlI9TswnzbbRJ0XtH.nEgR9ZMbcVK6rGZKt6rBqRUUGc/cIaq" authorities="ROLE_ADMIN" />
<ss:user name="anon" password="{bcrypt}$2y$10$yMGo.D9jbFXQ5qSwy2HtpeOLA5CvWybydInz2z8iYpbpU9JMLLjf6" authorities="ROLE_ANON" />
</ss:user-service>
</ss:authentication-provider>
</ss:authentication-manager>
Expand Down
Empty file added src/test/munit/.gitkeep
Empty file.
Empty file added src/test/resources/.gitkeep
Empty file.