1

I am attempting to get an embedded Spring Config Server implementation working that reads configuration from GitHub. I'm following this tutorial:

https://mromeh.com/2017/12/04/spring-boot-with-embedded-config-server-via-spring-cloud-config/

I am getting the following Exception when my Spring Boot app tries to start up:

Caused by: com.jcraft.jsch.JSchException: There are not any available ciphers. at com.jcraft.jsch.Session.send_kexinit(Session.java:629) at com.jcraft.jsch.Session.connect(Session.java:307) at org.eclipse.jgit.transport.JschConfigSessionFactory.getSession(JschConfigSessionFactory.java:146) ... 23 more

The only interesting bit in my code that I see contributing to this is my bootstrap.yml file, which looks like this:

spring:
  application:
    name: DemoApplication.yml

---
spring:
  cloud:
    config:
      failFast: true
      server:
        bootstrap: true
        git:
          uri: git@github.com:mycompany/demo-config.git

I am running OpenJDK 8 v212 on MacOS, per running the following:

#> java -version
openjdk version "1.8.0_212"
OpenJDK Runtime Environment (AdoptOpenJDK)(build 1.8.0_212-b03)
OpenJDK 64-Bit Server VM (AdoptOpenJDK)(build 25.212-b03, mixed mode)

I've searched through the Spring code and documentation, and have yet to find anything about passing configuration parameters or adding code to affect how the Jsch session being used by Spring is constructed. Everything I find suggests that what I'm doing should just work.

I'm at a loss as to where to go from here. Can someone tell me what I'm missing...what I need to do to get past this problem?

CryptoFool
  • 21,719
  • 5
  • 26
  • 44
  • You are using SSH, not SSL, and probably not JCE either.. – user207421 Jun 14 '19 at 04:51
  • Fine. I was just grasping at straws. I know I'm not using SSL, but I don't know enough about the JRE security code to know when the JCE does and does not come into play. If you correcting my naiveté got me closer to a solution, I'd be grateful for it. (I don't see that I ever mentioned SSL, btw). – CryptoFool Jun 14 '19 at 05:26
  • @user207421 JSch uses JCE -- http://www.jcraft.com/jsch/ *"JSch is in pure Java, but it depends on JavaTM Cryptography Extension (JCE)"* – Martin Prikryl Jun 14 '19 at 06:09
  • @Steve Does [JSch log file](https://stackoverflow.com/q/47411185/850848) show anything useful? – Martin Prikryl Jun 14 '19 at 06:11
  • @MartinPrikryl - Thanks for the suggestion. I didn't think to check my logs for information. Alas, there's nothing there. Since Spring is doing all the work here, I don't have any control over logging other than what gets written to stdout/stderr and to the SLF4J logging interface. Nothing is written to stdout/stderr, and I see nothing applicable in my application logs. - Actually, I could look into if I can cause JSch to do more verbose logging via Spring properties. Do you think that has any chance of providing an answer? – CryptoFool Jun 14 '19 at 06:15
  • Can you run a simple console JSch-only application in your environment? – Martin Prikryl Jun 14 '19 at 06:19
  • @MartinPrikryl - I was hoping not to have to learn how to do that. I'd never heard of JSch before this, much less coded against it. Say it works when I do that. Then what? I see this as a Spring configuration issue, or possibly some other environmental issue. I was hoping someone had come across this specifically as a Spring/JDK issue. I have large Spring app that otherwise works fine. It seems there ought to be a Spring solution to this. – CryptoFool Jun 14 '19 at 14:38
  • Whomever downvoted this - thanks so much for that very non-constructive (without a matching comment) feedback. - I can't imagine what one might see procedurally wrong with my question. – CryptoFool Jun 14 '19 at 14:40
  • If the console application works, it won't help you much (except maybe it would reinforce your assumption that it's Spring configuration issue). But if it does not work, it will allow you to investigate the problem further. After all it's your task to post [mcve] - and trying a console application is imo a part of finding what is the minimal example. – Martin Prikryl Jun 14 '19 at 14:43
  • @ MartinPrikryl - I apologize for my short comment well above. I often get the name of the comment poster vs the user that comment was targeted at mixed up. I just realized that you were supporting me with your comment...it wasn't another negative comment from @user207421. Sorry I missed that. – CryptoFool Jun 14 '19 at 14:45
  • @MartinPrikryl - fair enough. The tutorial I cite is really the MCVE, but I do understand that it is frowned upon to construct questions from external citations. I can construct a MCVE, but that would be a very simple Spring Boot app and not stand-alone JSch code. I appreciate all your help and will consider finding the time to do all that you suggest. Still seems strange to me that this isn't something anyone's come across before when I'm running simple Spring boilerplate. – CryptoFool Jun 14 '19 at 14:50
  • Although it's not obvious, Spring is using the JGit library to set up the SSH library - more detail on how this sets up SSH [here](https://wiki.eclipse.org/EGit/FAQ#SSH_config). As you've found, by default this uses JSch, which is configured by the `~/.ssh/config` file - if that exists you may find a clue in there. If the `ssh` command is already set up with identity keys etc, it would be worth trying setting the `GIT_SSH` environment variable to point to that directly though (`/usr/bin/ssh` on OS X?). This will bypass JSch entirely. – df778899 Jun 16 '19 at 13:49
  • *"I tried two experiments; hiding that file and putting an irrelevant key in the file. Doing these things leads to two different Jsch exceptions from the one I'm battling in this question."* - What exception do you get when you use *"irrelevant key"*? – Martin Prikryl Jun 16 '19 at 17:21
  • @df778899 - Dude! You da man. That works! It's not a full solution yet, but it gives me somewhere to start looking. Thanks so much. Two questions are immediately before me: 1) huh? - I need to understand what's going on here, and 2) if setting that env var is the answer, how do I best do that in my code or Spring config so I don't have to add a requirement for starting my app? I have found a way to set that env var at the top of my main(), but it's really messy. "System.setProperty()" doesn't work. I will work to understand this better at least to find a more obvious way to attack #2. – CryptoFool Jun 16 '19 at 17:24
  • Good news. I've put that, along with a suggestion about setting the env var in an answer. Please feel free to edit it if you find another way. – df778899 Jun 16 '19 at 18:17
  • @MartinPrikryl - Thanks again for your kind effort to help me. I'd offer you a bounty as well, but it seems that you're in fine shape points-wise and any small bounty I could offer you would be noise level. – CryptoFool Jun 16 '19 at 19:02

2 Answers2

2

To consolidate the comments earlier...

Behind the scenes, Spring is using JGit to make the SSH connection. By default this uses JSch to make the SSH connection, which is configured by the ~/.ssh/config file.

The wiki also has details of how to bypass JSch and use a native ssh command, the GIT_SSH environment variable can be set, e.g. to /usr/bin/ssh in OS X or Linux, or even something like C:\Program Files\TortoiseGit\bin\TortoiseGitPlink.exe.


Following the comment about how to avoid a dependency on setting the environment variable, note how the GIT_SSH environment variable is checked using a SystemReader in the TransportGitSsh.useExtSession() method.

This means one way would be to override the SystemReader class. It's not a small interface though, so would involve a fair bit of wrapping code - with the custom bit in getenv():

import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.SystemReader;

public class CustomSystemReader extends SystemReader {
    private final SystemReader systemReader;

    public CustomSystemReader(SystemReader systemReader) {
        this.systemReader = systemReader;
    }

    @Override
    public String getHostname() {
        return systemReader.getHostname();
    }

    @Override
    public String getenv(String variable) {
        if ("GIT_SSH".equals(variable))
            return "/usr/bin/ssh";
        return systemReader.getenv(variable);
    }

    @Override
    public String getProperty(String key) {
        return systemReader.getProperty(key);
    }

    @Override
    public FileBasedConfig openUserConfig(Config parent, FS fs) {
        return systemReader.openUserConfig(parent, fs);
    }

    @Override
    public FileBasedConfig openSystemConfig(Config parent, FS fs) {
        return systemReader.openSystemConfig(parent, fs);
    }

    @Override
    public long getCurrentTime() {
        return systemReader.getCurrentTime();
    }

    @Override
    public int getTimezone(long when) {
        return systemReader.getTimezone(when);
    }
}

Which can then be wired in like this:

    SystemReader.setInstance(
            new CustomSystemReader(SystemReader.getInstance()));
df778899
  • 10,703
  • 1
  • 24
  • 36
  • Thanks so much! I'll take a close look at this, and consider it as a potential ultimate solution. Cool that you added this answer, just so I can give you the bounty :) - seems that I can't actually award it for another 11 hours. – CryptoFool Jun 16 '19 at 18:35
  • Now that I know what it was that was horking Jsch, do you think there's any real reason to move away from its use? – CryptoFool Jun 16 '19 at 18:55
  • Will JSch simply not be used when my code reaches an environment with no ~/.ssh/config file? I guess I need to test for this. What a pain. It bothers me that Spring has left itself open to the problem I had here. Spring's behavior should not be dictated by an arbitrary entry in ~/.ssh/config, even if that's perfectly appropriate for lower level use of the Jsch library. I'd be much happier right now if I'd had to add an explicit Spring configuration value to point to my id_rsa keyfile. Even getting that from the environment by convention seems wrong. – CryptoFool Jun 16 '19 at 19:09
  • Good questions. By default, the [TransportGitSsh.useExtSession()](https://github.com/eclipse/jgit/blob/f3ec7cf3f0436a79e252251a31dbc62694555897/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitSsh.java#L221) method says it will use JSch, unless the `GIT_SSH` variable is in the environment. JSch is fine, it's a widely used library, but I can't spot an obvious way to stop [JGit configuring from ~/.ssh/config](https://github.com/eclipse/jgit/blob/f3ec7cf3f0436a79e252251a31dbc62694555897/org.eclipse.jgit/src/org/eclipse/jgit/transport/OpenSshConfig.java#L151) – df778899 Jun 16 '19 at 19:28
  • 1
    I just found that me thinks. See the "Git SSH configuration using properties" section of https://cloud.spring.io/spring-cloud-config/multi/multi__spring_cloud_config_server.html. We use AWS and Vault. I had already planned to wire Vault into this whole thing. I haven't use AWS CodeCommit before, but the "Authentication with AWS CodeCommit" section in this same document seems intriguing. It suggests that I might be able to avoid SSH creds completely when gaining access to my external configuration. This is all pretty head-spinning stuff for a "simple shared configuration mechanism" :) – CryptoFool Jun 16 '19 at 19:43
  • One other thing...github is likely not the ultimate destination for my shared config, regardless of the rest of this. I just used that solution to get started, I guess because the tutorial I grabbed first used it. I have many options. I simply didn't want to abandon the first thing I tried without understanding it, especially as it has produced so many new questions that I certainly do need to contend with. – CryptoFool Jun 16 '19 at 19:46
1

@df778899 gave me the direction I needed to figure this out, with the statement

As you've found, by default this uses JSch, which is configured by the ~/.ssh/config file - if that exists you may find a clue in there.

I had already looked to this file for clues, especially looking for anything about encryption setup. I saw that I had this commented out line near the top of the file:

#   Ciphers +aes256-cbc

What I had missed (I thought I'd done a text search for "cipher" but obviously, I didn't) was that buried way down in the middle of the file amidst a bunch of unrelated settings, I was doing the same thing again, not commented out this time:

Host *
    ...
    Ciphers +aes256-cbc
    ...

It is this line that is killing me with Jsch. If I comment out this one line, my simple setup works fine. @df778899's statement about ~/.ssh/config being critical to Jsch's setup was the push I needed.

This isn't going to part of my solution, but I'll point out that the following code seems to be how I can set GIT_SSH in Java, at the top of my main():

public static void main(String[] args) {

    try {
        Map<String, String> env = System.getenv();
        Field field = env.getClass().getDeclaredField("m");
        field.setAccessible(true);
        ((Map<String, String>) field.get(env)).put("GIT_SSH", "/usr/bin/ssh");
    } catch (NoSuchFieldException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }

    SpringApplication.run(DemoApplication.class, args);
}

This also "solves" my problem. That bit about getDeclaredField("m") is particularly strange. What the heck is "m"?

CryptoFool
  • 21,719
  • 5
  • 26
  • 44