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

Spring Boot 3.1 SSL Bundles Support Not Working #96

Closed
amgadhenan opened this issue Jul 31, 2023 · 14 comments
Closed

Spring Boot 3.1 SSL Bundles Support Not Working #96

amgadhenan opened this issue Jul 31, 2023 · 14 comments

Comments

@amgadhenan
Copy link

amgadhenan commented Jul 31, 2023

mq-jms-spring-boot-starter:3.1.2
Java17

spring:
  ssl:
    bundle:
      jks:
        mq-ssl-bundle:
          key:
            alias: "mq-ssl-bundle"
          keystore:
            type: PKCS12
            location: key.p12
            password: password

ibm:
  mq:
    conn-name: localhost(1424)
    queue-manager: QQQ1
    channel: CLNT.E
    user: user_00
    ssl-bundle: "mq-ssl-bundle"

When you use Spring boot 3.1 SSL Bundles, it's unable to get SSL bundle properly

MQConfigurationSslBundles.getSSLSocketFactory

is getting called before bundles get initialized in the constructor

@Component
public class MQConfigurationSslBundles {
  private static Logger logger = LoggerFactory.getLogger(MQConfigurationSslBundles.class);

  static SslBundles bundles = null;

  /* This is called during the initialisation phase */
  public MQConfigurationSslBundles(SslBundles sslBundles) {
    logger.trace("constructor - Bundles are {}", (sslBundles == null) ? "null" : "not null");
    bundles = sslBundles;
  }
  
  static boolean isSupported() {
    logger.trace("SSLBundles are supported");
    return true;
  }

  /* If the bundle name does not exist, then getBundle throws an exception. Since
     there is always some default bundle, we can't rely on there being no bundle.
     So we log an error, but otherwise try to continue.
   */
  public static SSLSocketFactory getSSLSocketFactory(String b) {
    SSLSocketFactory sf = null;
    logger.trace("getSSLSocketFactory for {}", b);

    if (b == null || b.isEmpty()) {
      return sf;
    }

    if (bundles != null) {
      try {
        SslBundle sb = bundles.getBundle(b);
        sf = sb.createSslContext().getSocketFactory();
      }
      catch (NoSuchSslBundleException e) {
        logger.error("No SSL bundle found for {}", b);
      }
    }
    return sf;
  }
}
@ibmmqmet
Copy link
Collaborator

ibmmqmet commented Aug 1, 2023

What evidence do you have for this? Have you run a program with trace-level logging to show the sequence?

The sample application (s2.tls.jms3) works fine for me. And the configuration dependencies tell Spring to drive the SslBundle stuff before any of the other configuration.

@amgadhenan
Copy link
Author

ibm_mq_log

@johkin
Copy link

johkin commented Oct 3, 2023

I have run into the same issue whe using the starter. Spring initializes the MQConfigurationSslBundles after the ConnectionFactory has been created.
An easy solution would be to just remove the call to the static method, and make sure that you inject all your dependencies?
My experience is that static methods never works well in a Spring application.

@johkin
Copy link

johkin commented Oct 3, 2023

I managed to get things working by creating a MQConnectionFactoryCustomizer that sets the SslSocketFactory before initialization is completed. That could perhaps be a nice setup in the starter also?

@Bean
    public MQConnectionFactoryCustomizer sslCustomizer(
        Optional<SslBundles> bundles, Optional<MQConfigurationProperties> properties) {
        log.debug("Creating MQConnectionFactoryCustomizer for SslBundles");

        if (bundles.isEmpty() || properties.isEmpty()) {
            return mqConnectionFactory -> {};
        }

        return connectionFactory -> {
            if (properties.get().getSslBundle() != null) {
                log.debug("Trying to configure MqConnectionFactory to use bundle {}", properties.get().getSslBundle());
                SslBundle bundle = bundles.get().getBundle(properties.get().getSslBundle());
                if (bundle != null) {
                    SSLSocketFactory socketFactory = bundle.createSslContext().getSocketFactory();
                    log.debug("Found bundle, created SocketFactory {}", socketFactory);
                    connectionFactory.setSSLSocketFactory(socketFactory);
                }
            }
        };
    }

I was hoping that one could use @ConditionalOnBeans-annotation, but that didn't work as expected.

@ibmmqmet
Copy link
Collaborator

ibmmqmet commented Oct 9, 2023

I think I now have the sequencing right so the SSLBundles get initialised before other properties. My tests are showing it called at a suitable time.

(Some of the implementation is done the way it is in order to simplify having a mostly-common set of code with the JMS2/Spring2 variant where the SSLBundles classes are not available.)

My plan is to release the update soon to pick up the next version of the MQ client.

ibmmqmet added a commit that referenced this issue Oct 23, 2023
- Uniform Cluster balancing options now available in JMS
Fix SSLBundle sequencing (#96)
@mschwartau
Copy link

Unfortunately, it is still not working. Tested it with spring boot 3.1.5 and mq-jms-spring-boot-starter version 3.1.5. The problem is that MQConfigurationSslBundles.getSSLSocketFactory is called by MQConnectionFactoryFactory.createConnectionFactory before a new instance of MQConfigurationSslBundles is created. Therefore the static variable MQConfigurationSslBundles.sslbundles, which is initialized in the constructor, is null and the call to MQConfigurationSslBundles.getSSLSocketFactory fails.

However, as a workaround I added a small configuration class (hack ;-)) to our project which fixes this issue so that the ssl connections can be established:

/**
 * The constructor {@code MQConfigurationSslBundles} needs to be called before {@code MQConfigurationSslBundles.getSSLSocketFactory}
 * because otherwise the ssl connection can't be established. The mq-jms-spring has an issue that the class {@code MQConfigurationSslBundles}
 * is instantiated only after the call to {@code MQConfigurationSslBundles.getSSLSocketFactory} which leads to an exception.
 * This bugfix guarantes that the {@code MQConfigurationSslBundles} constructor is directly called after creating the ssl bundles
 * so that the static instance variables in {@code MQConfigurationSslBundles} are initialized before the {@code MQConfigurationSslBundles.getSSLSocketFactory} call.
 *
 * @see <a href="https://github.com/ibm-messaging/mq-jms-spring/issues/96">mq-jms-spring issue regarding this fix</a>
 * @see org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration which creates an instance of DefaultSslBundleRegistry by default.
 */
@Configuration
public class MqConfigurationFix {

    @Bean
    public DefaultSslBundleRegistry sslBundleRegistry(List<SslBundleRegistrar> sslBundleRegistrars) {
        // copied from org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration
        DefaultSslBundleRegistry registry = new DefaultSslBundleRegistry();
        sslBundleRegistrars.forEach((registrar) -> {
            registrar.registerBundles(registry);
        });
        // that's the bugfix code which initializes the static instance variables in MQConfigurationSslBundles
        new MQConfigurationSslBundles(registry);
        return registry;
    }

}

For further analysis why MQConnectionFactoryFactory.createConnectionFactory is called so early I added a breakpoint to this method and exported the stacktrace:

createConnectionFactory:69, MQConnectionFactoryFactory (com.ibm.mq.spring.boot)
createConnectionFactory:80, MQConnectionFactoryConfiguration (com.ibm.mq.spring.boot)
cachingJmsConnectionFactory:66, MQConnectionFactoryConfiguration$RegularMQConnectionFactoryConfiguration (com.ibm.mq.spring.boot)
invoke0:-1, NativeMethodAccessorImpl (jdk.internal.reflect)
invoke:77, NativeMethodAccessorImpl (jdk.internal.reflect)
invoke:43, DelegatingMethodAccessorImpl (jdk.internal.reflect)
invoke:568, Method (java.lang.reflect)
instantiate:139, SimpleInstantiationStrategy (org.springframework.beans.factory.support)
instantiate:650, ConstructorResolver (org.springframework.beans.factory.support)
instantiateUsingFactoryMethod:642, ConstructorResolver (org.springframework.beans.factory.support)
instantiateUsingFactoryMethod:1332, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBeanInstance:1162, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
doCreateBean:560, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBean:520, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
lambda$doGetBean$0:325, AbstractBeanFactory (org.springframework.beans.factory.support)
getObject:-1, AbstractBeanFactory$$Lambda$323/0x0000000800e19118 (org.springframework.beans.factory.support)
getSingleton:234, DefaultSingletonBeanRegistry (org.springframework.beans.factory.support)
doGetBean:323, AbstractBeanFactory (org.springframework.beans.factory.support)
getBean:199, AbstractBeanFactory (org.springframework.beans.factory.support)
resolveCandidate:254, DependencyDescriptor (org.springframework.beans.factory.config)
doResolveDependency:1417, DefaultListableBeanFactory (org.springframework.beans.factory.support)
resolveDependency:1337, DefaultListableBeanFactory (org.springframework.beans.factory.support)
resolveAutowiredArgument:910, ConstructorResolver (org.springframework.beans.factory.support)
createArgumentArray:788, ConstructorResolver (org.springframework.beans.factory.support)
instantiateUsingFactoryMethod:545, ConstructorResolver (org.springframework.beans.factory.support)
instantiateUsingFactoryMethod:1332, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBeanInstance:1162, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
doCreateBean:560, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBean:520, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
lambda$doGetBean$0:325, AbstractBeanFactory (org.springframework.beans.factory.support)
getObject:-1, AbstractBeanFactory$$Lambda$323/0x0000000800e19118 (org.springframework.beans.factory.support)
getSingleton:234, DefaultSingletonBeanRegistry (org.springframework.beans.factory.support)
doGetBean:323, AbstractBeanFactory (org.springframework.beans.factory.support)
getBean:199, AbstractBeanFactory (org.springframework.beans.factory.support)
resolveCandidate:254, DependencyDescriptor (org.springframework.beans.factory.config)
doResolveDependency:1417, DefaultListableBeanFactory (org.springframework.beans.factory.support)
resolveDependency:1337, DefaultListableBeanFactory (org.springframework.beans.factory.support)
resolveAutowiredArgument:910, ConstructorResolver (org.springframework.beans.factory.support)
createArgumentArray:788, ConstructorResolver (org.springframework.beans.factory.support)
autowireConstructor:240, ConstructorResolver (org.springframework.beans.factory.support)
autowireConstructor:1352, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBeanInstance:1189, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
doCreateBean:560, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBean:520, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
lambda$doGetBean$0:325, AbstractBeanFactory (org.springframework.beans.factory.support)
getObject:-1, AbstractBeanFactory$$Lambda$323/0x0000000800e19118 (org.springframework.beans.factory.support)
getSingleton:234, DefaultSingletonBeanRegistry (org.springframework.beans.factory.support)
doGetBean:323, AbstractBeanFactory (org.springframework.beans.factory.support)
getBean:199, AbstractBeanFactory (org.springframework.beans.factory.support)
resolveCandidate:254, DependencyDescriptor (org.springframework.beans.factory.config)
doResolveDependency:1417, DefaultListableBeanFactory (org.springframework.beans.factory.support)
resolveDependency:1337, DefaultListableBeanFactory (org.springframework.beans.factory.support)
resolveAutowiredArgument:910, ConstructorResolver (org.springframework.beans.factory.support)
createArgumentArray:788, ConstructorResolver (org.springframework.beans.factory.support)
autowireConstructor:240, ConstructorResolver (org.springframework.beans.factory.support)
autowireConstructor:1352, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBeanInstance:1189, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
doCreateBean:560, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBean:520, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
lambda$doGetBean$0:325, AbstractBeanFactory (org.springframework.beans.factory.support)
getObject:-1, AbstractBeanFactory$$Lambda$323/0x0000000800e19118 (org.springframework.beans.factory.support)
getSingleton:234, DefaultSingletonBeanRegistry (org.springframework.beans.factory.support)
doGetBean:323, AbstractBeanFactory (org.springframework.beans.factory.support)
getBean:199, AbstractBeanFactory (org.springframework.beans.factory.support)
resolveCandidate:254, DependencyDescriptor (org.springframework.beans.factory.config)
doResolveDependency:1417, DefaultListableBeanFactory (org.springframework.beans.factory.support)
resolveDependency:1337, DefaultListableBeanFactory (org.springframework.beans.factory.support)
resolveAutowiredArgument:910, ConstructorResolver (org.springframework.beans.factory.support)
createArgumentArray:788, ConstructorResolver (org.springframework.beans.factory.support)
autowireConstructor:240, ConstructorResolver (org.springframework.beans.factory.support)
autowireConstructor:1352, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBeanInstance:1189, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
doCreateBean:560, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBean:520, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
lambda$doGetBean$0:325, AbstractBeanFactory (org.springframework.beans.factory.support)
getObject:-1, AbstractBeanFactory$$Lambda$323/0x0000000800e19118 (org.springframework.beans.factory.support)
getSingleton:234, DefaultSingletonBeanRegistry (org.springframework.beans.factory.support)
doGetBean:323, AbstractBeanFactory (org.springframework.beans.factory.support)
getBean:199, AbstractBeanFactory (org.springframework.beans.factory.support)
resolveCandidate:254, DependencyDescriptor (org.springframework.beans.factory.config)
doResolveDependency:1417, DefaultListableBeanFactory (org.springframework.beans.factory.support)
resolveDependency:1337, DefaultListableBeanFactory (org.springframework.beans.factory.support)
resolveAutowiredArgument:910, ConstructorResolver (org.springframework.beans.factory.support)
createArgumentArray:788, ConstructorResolver (org.springframework.beans.factory.support)
autowireConstructor:240, ConstructorResolver (org.springframework.beans.factory.support)
autowireConstructor:1352, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBeanInstance:1189, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
doCreateBean:560, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBean:520, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
lambda$doGetBean$0:325, AbstractBeanFactory (org.springframework.beans.factory.support)
getObject:-1, AbstractBeanFactory$$Lambda$323/0x0000000800e19118 (org.springframework.beans.factory.support)
getSingleton:234, DefaultSingletonBeanRegistry (org.springframework.beans.factory.support)
doGetBean:323, AbstractBeanFactory (org.springframework.beans.factory.support)
getBean:199, AbstractBeanFactory (org.springframework.beans.factory.support)
resolveCandidate:254, DependencyDescriptor (org.springframework.beans.factory.config)
doResolveDependency:1417, DefaultListableBeanFactory (org.springframework.beans.factory.support)
resolveDependency:1337, DefaultListableBeanFactory (org.springframework.beans.factory.support)
resolveAutowiredArgument:910, ConstructorResolver (org.springframework.beans.factory.support)
createArgumentArray:788, ConstructorResolver (org.springframework.beans.factory.support)
autowireConstructor:240, ConstructorResolver (org.springframework.beans.factory.support)
autowireConstructor:1352, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBeanInstance:1189, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
doCreateBean:560, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBean:520, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
lambda$doGetBean$0:325, AbstractBeanFactory (org.springframework.beans.factory.support)
getObject:-1, AbstractBeanFactory$$Lambda$323/0x0000000800e19118 (org.springframework.beans.factory.support)
getSingleton:234, DefaultSingletonBeanRegistry (org.springframework.beans.factory.support)
doGetBean:323, AbstractBeanFactory (org.springframework.beans.factory.support)
getBean:199, AbstractBeanFactory (org.springframework.beans.factory.support)
preInstantiateSingletons:973, DefaultListableBeanFactory (org.springframework.beans.factory.support)
finishBeanFactoryInitialization:950, AbstractApplicationContext (org.springframework.context.support)
refresh:616, AbstractApplicationContext (org.springframework.context.support)
refresh:146, ServletWebServerApplicationContext (org.springframework.boot.web.servlet.context)
refresh:738, SpringApplication (org.springframework.boot)
refreshContext:440, SpringApplication (org.springframework.boot)
run:316, SpringApplication (org.springframework.boot)
run:1306, SpringApplication (org.springframework.boot)
run:1295, SpringApplication (org.springframework.boot)
main:38, Startup (***********)

@fdlk
Copy link

fdlk commented Nov 7, 2023

Running into this issue, took me a while to figure out what was going on cause nothing useful gets logged.
Can confirm that the workaround by @mschwartau works

@omarfi
Copy link

omarfi commented Jan 3, 2024

I can't get this to work either for the same reason (v3.1.5), so I'm reverting back to using the old (now deprecated) properties ibm.mq.jks.-store.
I hope this issue gets resolved before the old properties are removed.

@ibmmqmet
Copy link
Collaborator

ibmmqmet commented Jan 4, 2024

I've finally got some time available to take another look at this, but it would be very helpful if someone provided a complete testcase to demonstrate the issue. My own tests work fine.

@omarfi
Copy link

omarfi commented Jan 4, 2024

@ibmmqmet Have you considered using

@DependsOn("ibm.mq-com.ibm.mq.spring.boot.MQConfigurationProperties")

instead of

@AutoConfigureBefore({MQConfigurationProperties.class})

in MqConfigurationSslBundles.class?

I suspect that @AutoConfigureBefore-annotation only works for classes annotated with @autoConfiguration and not @configuration.

@fdlk
Copy link

fdlk commented Jan 4, 2024

To reproduce, I think you need a different reason to set up the ssl context early on. We have an as400 sql datasource with secure=true that probably kicks in too early.

@omarfi
Copy link

omarfi commented Jan 4, 2024

Simply adding the following bean also does trick
@Bean public MQConnectionFactoryCustomizer noOpCustomizer(MQConfigurationSslBundles ignored) { return cf -> {}; }

Might be a little mystical why it works at first glance, but it basically forces the creation of MQConfigurationSslBundles before the ConnectionFactory is created in MQConnectionFactoryConfiguration using the static method com.ibm.mq.spring.boot.MQConnectionFactoryFactory#createConnectionFactory.

The key here is that the ConnectionFactory bean depends on MQConnectionFactoryCustomizer , which ultimately leads to the final ordering.

@ibmmqmet
Copy link
Collaborator

Thanks for the suggestions on possible solutions. I've just released a new level that (hopefully) will deal with it - certainly I can see the ordering is slightly different.

But I've not been able to create a working broken test scenarion, so not been able to definitively prove the changes work.

@ibmmqmet
Copy link
Collaborator

ibmmqmet commented Mar 4, 2024

The ssl bundles support was reworked in the latest version now that there's no need to have common code with boot 2. Ought to be much cleaner now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants