0

I am using an internal encryption library which encrypts my config file at the run time. Now in order to be able to use this library, my config file must be outside my JAR. Therefore, I am using an external config file who's path is set in the environment variable.
When I try to run my springboot application through IntelliJ IDE it runs flawlessly. All I did was set the envrionment variable in Intellij'srun configuration. enter image description hereHowever, when I try to run it through gradle bootRun it complains Web server failed to start. Port 8080 was already in use.

[main] WARN org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.context.ApplicationContextException: Failed to start bean 'webServerStartStop'; nested exception is org.springframework.boot.web.server.PortInUseException: Port 8080 is already in use

The command that I am using is ./gradlew bootRun. I have removed the application.properties file from my resources folder and have it in some other path pointed by the environment variable. In my code, I am using the decryption library to read the config manually. I'm not supplying the config during the build but only reading the config path inside the code. Sample config for DB:

@Configuration
class JpaConfig {

    @Bean
    public DataSource getDataSource() throws ProtectConfigInitException {
        EncryptedProperties protectedConfig = new EncryptedProperties(System.getenv("CONFIG_FILE_PATH"));
        String dbUrl = protectedConfig.getProperty("spring.datasource.url");
        String dbDriverClassName = protectedConfig.getProperty("spring.datasource.driver-class-name");
        String dbUsername = protectedConfig.getProperty("spring.datasource.username");
        String dbPassword = protectedConfig.getProperty("spring.datasource.password");
        return DataSourceBuilder.create()
            .driverClassName(dbDriverClassName)
            .url(dbUrl)
            .username(dbUsername)
            .password(dbPassword)
            .build();
    }
}

Sample SSL Config:

@Component
@Log
public class SSLConfig {


    @Autowired
    Environment env;

    @PostConstruct
    public void configureSSL()  {
        try {
            EncryptedProperties protectedConfig = new EncryptedProperties(System.getenv("CONFIG_FILE_PATH"));

            String keyStore = protectedConfig.getProperty("server.ssl.key-store");
            String keyStorePassword = protectedConfig.getProperty("server.ssl.key-store-password");
            String keyStoreType = protectedConfig.getProperty("server.ssl.key-store-type");
            String keyStoreAlias = protectedConfig.getProperty("server.ssl.key-alias");
            String sslKeyPassword = protectedConfig.getProperty("server.ssl.key-password");

            System.setProperty("server.ssl.key-store", keyStore);
            System.setProperty("server.ssl.key-store-password", keyStorePassword);
            System.setProperty("server.ssl.key-store-type", keyStoreType);
            System.setProperty("server.ssl.key-alias", keyStoreAlias);
            System.setProperty("server.ssl.key-password", sslKeyPassword);

        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

@Configuration
public class WebServerConfig {
    @Bean
    public ServletWebServerFactory getTomcatServletWebServerFactory() {
        final var serverFactory = new TomcatServletWebServerFactory() {
            @Override
            protected void postProcessContext(Context context) {
                final var securityConstraint = new SecurityConstraint();
                securityConstraint.setUserConstraint("CONFIDENTIAL");
                final var collection = new SecurityCollection();
                collection.addPattern("/*");
                securityConstraint.addCollection(collection);
                context.addConstraint(securityConstraint);
            }
        };
        serverFactory.addAdditionalTomcatConnectors(getHttpConnector());
        return serverFactory;
    }

    private Connector getHttpConnector() {
        final var connector = new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL);
        connector.setScheme("http");
        connector.setPort(8080);
        connector.setSecure(false);
        connector.setRedirectPort(8443);
        return connector;
    }
}

If I try to supply spring.config.location through commandline with the gradle boot run command, it complains of incorrect keystore password. Here is the command I tried: ./gradlew bootRun -Dspring.config.location=/Users/myName/myProject/MyProjRoot/external.properties

Unable to start embedded Tomcat server
        at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:185)
        at org.springframework.context.support.DefaultLifecycleProcessor.access$200(DefaultLifecycleProcessor.java:53)
        at org.springframework.context.support.DefaultLifecycleProcessor$LifecycleGroup.start(DefaultLifecycleProcessor.java:360)
        at org.springframework.context.support.DefaultLifecycleProcessor.startBeans(DefaultLifecycleProcessor.java:158)
        at org.springframework.context.support.DefaultLifecycleProcessor.onRefresh(DefaultLifecycleProcessor.java:122)
        at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:895)
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:554)
        at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:143)
        at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:758)
        at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:750)
        at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:315)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1237)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226)
        at com.myApp.main(myAppApplication.java:20)
Caused by: org.springframework.boot.web.server.WebServerException: Unable to start embedded Tomcat server
        at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.start(TomcatWebServer.java:229)
        at org.springframework.boot.web.servlet.context.WebServerStartStopLifecycle.start(WebServerStartStopLifecycle.java:43)
        at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:182)
        ... 14 more
Caused by: java.lang.IllegalArgumentException: standardService.connector.startFailed
        at org.apache.catalina.core.StandardService.addConnector(StandardService.java:231)
        at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.addPreviouslyRemovedConnectors(TomcatWebServer.java:282)
        at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.start(TomcatWebServer.java:213)
        ... 16 more
Caused by: org.apache.catalina.LifecycleException: Protocol handler start failed
        at org.apache.catalina.connector.Connector.startInternal(Connector.java:1067)
        at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
        at org.apache.catalina.core.StandardService.addConnector(StandardService.java:227)
        ... 18 more
Caused by: java.lang.IllegalArgumentException: keystore password was incorrect
        at org.apache.tomcat.util.net.AbstractJsseEndpoint.createSSLContext(AbstractJsseEndpoint.java:99)
        at org.apache.tomcat.util.net.AbstractJsseEndpoint.initialiseSsl(AbstractJsseEndpoint.java:71)
        at org.apache.tomcat.util.net.NioEndpoint.bind(NioEndpoint.java:216)
        at org.apache.tomcat.util.net.AbstractEndpoint.bindWithCleanup(AbstractEndpoint.java:1141)
        at org.apache.tomcat.util.net.AbstractEndpoint.start(AbstractEndpoint.java:1227)
        at org.apache.coyote.AbstractProtocol.start(AbstractProtocol.java:592)
        at org.apache.catalina.connector.Connector.startInternal(Connector.java:1064)
        ... 20 more
Caused by: java.io.IOException: keystore password was incorrect
        at sun.security.pkcs12.PKCS12KeyStore.engineLoad(PKCS12KeyStore.java:2059)
        at java.security.KeyStore.load(KeyStore.java:1445)
        at org.apache.tomcat.util.security.KeyStoreUtil.load(KeyStoreUtil.java:67)
        at org.apache.tomcat.util.net.SSLUtilBase.getStore(SSLUtilBase.java:216)
        at org.apache.tomcat.util.net.SSLHostConfigCertificate.getCertificateKeystore(SSLHostConfigCertificate.java:207)
        at org.apache.tomcat.util.net.SSLUtilBase.getKeyManagers(SSLUtilBase.java:282)
        at org.apache.tomcat.util.net.SSLUtilBase.createSSLContext(SSLUtilBase.java:246)
        at org.apache.tomcat.util.net.AbstractJsseEndpoint.createSSLContext(AbstractJsseEndpoint.java:97)

This is a very strange behavior. My suspicion is that when my application tries to access the keystore password at run time, it is already encrypted. By the time it reads SSLConfig.java it has already errored out. That's just a guess though.

I am initializing the encryption library that reads the file and encrypts it right before my spring boot application starts. However, spring boot application does not directly know that this is the config file. I'm supplying the config manually in the SSLConfig, JpaConfig above.

@SpringBootApplication
public class MyApplication {
    @Autowired
    Environment env;
    public static void main(String[] args) {
        try {
            EncryptedProperties.init(System.getenv("CONFIG_FILE_PATH"));
        }catch(Exception e){
            throw new RuntimeException("Failed to encrypt properties" + e.getMessage());
        }
        SpringApplication.run(MyApplication.class, args);

    }

What gradle command should I use such that it works fine through the command line as well, the way it works through IntelliJ? The reason I'm interested through command line is that I also want to be able to compile a jar which will be my deployable.

jkasper
  • 236
  • 4
  • 19
  • 1
    What does ```echo %CONFIG_FILE_PATH%``` give you from command line? – KnockingHeads Jun 14 '21 at 06:27
  • May be you can also add the code to read your external file manually. If you are using try-with-resources and getResourceAsStream method, then you need to check the stream to be null and throw NPE from there to your method call in configuressl method. More details here: https://stackoverflow.com/questions/67396600/how-to-catch-exceptions-from-try-with-resources As far as I can see, it seems your file is not getting read and you are not catching it, which is a very legit case considering the mistake people do with getResourceAsStream method while reading a file. – KnockingHeads Jun 14 '21 at 06:32
  • 1
    Have you configured `bootRun` in your `build.gradle` to use system properties from Gradle’s JVM? If not, `-Dspring.config.location=…` won’t work as the system property will be set in Gradle’s JVM not the JVM that runs your app. – Andy Wilkinson Jun 14 '21 at 06:38
  • @Ashish: echo %CONFIG_FILE_PATH% prints the string as is: %CONFIG_FILE_PATH% echo $CONFIG_FILE_PATH prints the path to external.properties – jkasper Jun 14 '21 at 15:47
  • @AndyWilkinson: What value should I set? – jkasper Jun 14 '21 at 15:51
  • @Ashish: Just added an edit which shows that I'm encrypting my properties file right before starting the application. However, spring boot doesn't know that this is the properties file. Are you suggesting that I read the file manually here and set the properties? – jkasper Jun 14 '21 at 16:03
  • Try specifying the full path to config file instead of the relative path. – Andrey Jun 14 '21 at 16:52
  • @Andrey: I've tried specifying the full config path in the Environment Variable. Same results. Is there any other place you are suggesting me to put? – jkasper Jun 14 '21 at 17:57
  • I tried putting in a log statement as the first line in the main function log.info("PATH IS: "+System.getenv("CONFIG_FILE_PATH")); and when the application started, it did print this: [main] INFO - PATH IS: /Users/myName/myProject/MyProjRoot/external.properties – jkasper Jun 16 '21 at 15:55
  • @Ashish: The file is getting read properly indeed, as I see that the file's contents are encrypted, but I'm getting the keystore password incorrect error. – jkasper Jun 16 '21 at 15:59
  • Can you print the value of your ssl username and password values both the times, when running by Idea and when running as gradle bootRun. Then, if the values are different for password, then you can hard code the password just for debug purposes for bootRun task and have the value generated in idea console. I dont know, may be now if it seems to not be the properties file being read properly, the question might need a sample of build.gradle or some debug logs. – KnockingHeads Jun 17 '21 at 00:19

0 Answers0