Skip to content

Commit

Permalink
temporary work with ServerKeyVerifier but not convince
Browse files Browse the repository at this point in the history
Signed-off-by: Olivier Lamy <[email protected]>
  • Loading branch information
olamy committed Apr 18, 2024
1 parent 3210e64 commit ea1fd59
Show file tree
Hide file tree
Showing 8 changed files with 313 additions and 14 deletions.
20 changes: 7 additions & 13 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
<dependency>
<groupId>io.jenkins.tools.bom</groupId>
<artifactId>bom-2.414.x</artifactId>
<version>2907.vcb_35d6f2f7de</version>
<version>2961.v1f472390972e</version>
<type>pom</type>
<scope>import</scope>
</dependency>
Expand All @@ -103,6 +103,12 @@
<groupId>io.jenkins.plugins.mina-sshd-api</groupId>
<artifactId>mina-sshd-api-core</artifactId>
</dependency>
<!-- this could be optional? -->
<dependency>
<groupId>net.i2p.crypto</groupId>
<artifactId>eddsa</artifactId>
<version>0.3.0</version>
</dependency>
<dependency>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit</artifactId>
Expand Down Expand Up @@ -202,12 +208,6 @@
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>ssh-credentials</artifactId>
<exclusions>
<exclusion>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>trilead-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
Expand Down Expand Up @@ -239,12 +239,6 @@
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>git-server</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>trilead-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<!-- Credentials test requires scm-api -->
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
package org.eclipse.jgit.transport.sshd;

import java.io.File;
import java.io.IOException;
import java.nio.file.InvalidPathException;
import java.security.KeyPair;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
import org.apache.sshd.client.ClientBuilder;
import org.apache.sshd.client.SshClient;
import org.apache.sshd.client.auth.UserAuthFactory;
import org.apache.sshd.client.auth.keyboard.UserAuthKeyboardInteractiveFactory;
import org.apache.sshd.client.auth.password.UserAuthPasswordFactory;
import org.apache.sshd.client.config.hosts.HostConfigEntryResolver;
import org.apache.sshd.client.keyverifier.KnownHostsServerKeyVerifier;
import org.apache.sshd.client.keyverifier.ServerKeyVerifier;
import org.apache.sshd.common.NamedFactory;
import org.apache.sshd.common.compression.BuiltinCompressions;
import org.apache.sshd.common.keyprovider.KeyIdentityProvider;
import org.apache.sshd.common.signature.BuiltinSignatures;
import org.apache.sshd.common.signature.Signature;
import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.errors.TransportException;
import org.eclipse.jgit.internal.transport.sshd.GssApiWithMicAuthFactory;
import org.eclipse.jgit.internal.transport.sshd.JGitPublicKeyAuthFactory;
import org.eclipse.jgit.internal.transport.sshd.JGitSshClient;
import org.eclipse.jgit.internal.transport.sshd.JGitSshConfig;
import org.eclipse.jgit.internal.transport.sshd.JGitUserInteraction;
import org.eclipse.jgit.internal.transport.sshd.PasswordProviderWrapper;
import org.eclipse.jgit.internal.transport.sshd.SshdText;
import org.eclipse.jgit.internal.transport.sshd.agent.JGitSshAgentFactory;
import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.SshConstants;
import org.eclipse.jgit.transport.URIish;
import org.eclipse.jgit.transport.sshd.agent.ConnectorFactory;
import org.eclipse.jgit.util.FS;
import org.jenkinsci.plugins.gitclient.jgit.SmartCredentialsProvider;
import org.jenkinsci.plugins.gitclient.verifier.SshHostKeyVerificationStrategy;

public class JenkinsSshdSessionFactory extends SshdSessionFactory {

private final ServerKeyVerifier serverKeyVerifier;
private final SmartCredentialsProvider credentialsProvider;
private final Map<Tuple, HostConfigEntryResolver> defaultHostConfigEntryResolver = new ConcurrentHashMap<>();
private final AtomicBoolean closing = new AtomicBoolean();

private final Set<SshdSession> sessions = new HashSet<>();

public JenkinsSshdSessionFactory(
ServerKeyVerifier serverKeyVerifier, SmartCredentialsProvider credentialsProvider) {
super();
this.serverKeyVerifier = serverKeyVerifier;
this.credentialsProvider = credentialsProvider;
}

@Override
public SshdSession getSession(URIish uri, CredentialsProvider credentialsProvider, FS fs, int tms)
throws TransportException {
SshdSession session = null;
try {
session = new SshdSession(uri, () -> {
File home = getHomeDirectory();
if (home == null) {
// Always use the detected filesystem for the user home!
// It makes no sense to have different "user home"
// directories depending on what file system a repository
// is.
home = FS.DETECTED.userHome();
}
File sshDir = getSshDirectory();
if (sshDir == null) {
sshDir = new File(home, SshConstants.SSH_DIR);
}
HostConfigEntryResolver configFile = getHostConfigEntryResolver(home, sshDir);
KeyIdentityProvider defaultKeysProvider = toKeyIdentityProvider(getDefaultKeys(sshDir));
Supplier<KeyPasswordProvider> keyPasswordProvider =
() -> createKeyPasswordProvider(credentialsProvider);
ServerKeyVerifier serverKeyVerifier = new KnownHostsServerKeyVerifier(
JenkinsSshdSessionFactory.this.serverKeyVerifier,
SshHostKeyVerificationStrategy.JGIT_KNOWN_HOSTS_FILE.toPath());
SshClient client = ClientBuilder.builder()
.factory(JGitSshClient::new)
.filePasswordProvider(new PasswordProviderWrapper(keyPasswordProvider))
.hostConfigEntryResolver(configFile)
.serverKeyVerifier(serverKeyVerifier)
.signatureFactories(getSignatureFactories())
.compressionFactories(new ArrayList<>(BuiltinCompressions.VALUES))
.build();
client.setUserInteraction(new JGitUserInteraction(credentialsProvider));
client.setUserAuthFactories(getUserAuthFactories());
client.setKeyIdentityProvider(defaultKeysProvider);
ConnectorFactory connectors = getConnectorFactory();
if (connectors != null) {
client.setAgentFactory(new JGitSshAgentFactory(connectors, home));
}
// JGit-specific things:
JGitSshClient jgitClient = (JGitSshClient) client;
jgitClient.setKeyCache(getKeyCache());
jgitClient.setCredentialsProvider(credentialsProvider);
// FIXME Jenkins proxies?
jgitClient.setProxyDatabase(new DefaultProxyDataFactory());
jgitClient.setKeyPasswordProviderFactory(keyPasswordProvider);
String defaultAuths = getDefaultPreferredAuthentications();
if (defaultAuths != null) {
jgitClient.setAttribute(JGitSshClient.PREFERRED_AUTHENTICATIONS, defaultAuths);
}
if (home != null) {
try {
jgitClient.setAttribute(
JGitSshClient.HOME_DIRECTORY,
home.getAbsoluteFile().toPath());
} catch (SecurityException | InvalidPathException e) {
// Ignore
}
}
// Other things?
return client;
});
session.addCloseListener(s -> unregister(s));
register(session);
session.connect(Duration.ofMillis(tms));
return session;
} catch (Exception e) {
unregister(session);
if (e instanceof TransportException) {
throw (TransportException) e;
}
throw new TransportException(uri, e.getMessage(), e);
}
}

@NonNull
private HostConfigEntryResolver getHostConfigEntryResolver(@NonNull File homeDir, @NonNull File sshDir) {
return defaultHostConfigEntryResolver.computeIfAbsent(
new Tuple(new Object[] {homeDir, sshDir}),
t -> new JGitSshConfig(createSshConfigStore(homeDir, getSshConfig(sshDir), getLocalUserName())));
}

/** A simple general map key. */
private static final class Tuple {
private Object[] objects;

public Tuple(Object[] objects) {
this.objects = objects;
}

@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj != null && obj.getClass() == JenkinsSshdSessionFactory.Tuple.class) {
JenkinsSshdSessionFactory.Tuple other = (JenkinsSshdSessionFactory.Tuple) obj;
return Arrays.equals(objects, other.objects);
}
return false;
}

@Override
public int hashCode() {
return Arrays.hashCode(objects);
}
}

private KeyIdentityProvider toKeyIdentityProvider(Iterable<KeyPair> keys) {
if (keys instanceof KeyIdentityProvider) {
return (KeyIdentityProvider) keys;
}
return (session) -> keys;
}

private void register(SshdSession newSession) throws IOException {
if (newSession == null) {
return;
}
if (closing.get()) {
throw new IOException(SshdText.get().sshClosingDown);
}
synchronized (this) {
sessions.add(newSession);
}
}

private void unregister(SshdSession oldSession) {
boolean cleanKeys = false;
synchronized (this) {
sessions.remove(oldSession);
cleanKeys = closing.get() && sessions.isEmpty();
}
if (cleanKeys) {
KeyCache cache = getKeyCache();
if (cache != null) {
cache.close();
}
}
}

/**
* Apache MINA sshd 2.6.0 has removed DSA, DSA_CERT and RSA_CERT. We have to
* set it up explicitly to still allow users to connect with DSA keys.
*
* @return a list of supported signature factories
*/
@SuppressWarnings("deprecation")
private static List<NamedFactory<Signature>> getSignatureFactories() {
// @formatter:off
// FIXME Check FIPS?
return Arrays.asList(
BuiltinSignatures.nistp256_cert,
BuiltinSignatures.nistp384_cert,
BuiltinSignatures.nistp521_cert,
BuiltinSignatures.ed25519_cert,
BuiltinSignatures.rsaSHA512_cert,
BuiltinSignatures.rsaSHA256_cert,
BuiltinSignatures.rsa_cert,
BuiltinSignatures.nistp256,
BuiltinSignatures.nistp384,
BuiltinSignatures.nistp521,
BuiltinSignatures.ed25519,
BuiltinSignatures.sk_ecdsa_sha2_nistp256,
BuiltinSignatures.sk_ssh_ed25519,
BuiltinSignatures.rsaSHA512,
BuiltinSignatures.rsaSHA256,
BuiltinSignatures.rsa,
BuiltinSignatures.dsa_cert,
BuiltinSignatures.dsa);
// @formatter:on
}

/**
* Gets the user authentication mechanisms (or rather, factories for them).
* By default this returns gssapi-with-mic, public-key, password, and
* keyboard-interactive, in that order. The order is only significant if the
* ssh config does <em>not</em> set {@code PreferredAuthentications}; if it
* is set, the order defined there will be taken.
*
* @return the non-empty list of factories.
*/
@NonNull
private List<UserAuthFactory> getUserAuthFactories() {
// About the order of password and keyboard-interactive, see upstream
// bug https://issues.apache.org/jira/projects/SSHD/issues/SSHD-866 .
// Password auth doesn't have this problem.
return Collections.unmodifiableList(Arrays.asList(
GssApiWithMicAuthFactory.INSTANCE,
JGitPublicKeyAuthFactory.FACTORY,
UserAuthPasswordFactory.INSTANCE,
UserAuthKeyboardInteractiveFactory.INSTANCE));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,11 @@ public class JGitAPIImpl extends LegacyCompatibleGitAPIImpl {

// to avoid rogue plugins from clobbering what we use, always
// make a point of overwriting it with ours.
// SshdSessionFactoryBuilder builder = new SshdSessionFactoryBuilder();
SshSessionFactory.setInstance(
buildSshdSessionFactory(hostKeyFactory.forJGit(listener), asSmartCredentialsProvider()));
// SshSessionFactory.setInstance(new JenkinsSshdSessionFactory(
// hostKeyFactory.forJGit(listener).getServerKeyVerifier(), asSmartCredentialsProvider()));
if (httpConnectionFactory != null) {
httpConnectionFactory.setCredentialsProvider(asSmartCredentialsProvider());
// allow override of HttpConnectionFactory to avoid JENKINS-37934
Expand All @@ -190,7 +193,6 @@ public class JGitAPIImpl extends LegacyCompatibleGitAPIImpl {

private SshdSessionFactory buildSshdSessionFactory(
AbstractJGitHostKeyVerifier abstractJGitHostKeyVerifier, SmartCredentialsProvider credentialsProvider) {

if (Files.notExists(SshHostKeyVerificationStrategy.JGIT_KNOWN_HOSTS_FILE.toPath())) {
try {
Files.createDirectories(SshHostKeyVerificationStrategy.JGIT_KNOWN_HOSTS_FILE
Expand Down Expand Up @@ -232,6 +234,7 @@ public SshdSession getSession(URIish uri, CredentialsProvider credentialsProvide
sshdSession.addCloseListener(sshdSession1 -> {
if (tmpKey != null) {
try {
listener.getLogger().println("deleting");
Files.deleteIfExists(tmpKey);
} catch (IOException e) {
if (LOGGER.isLoggable(Level.FINE))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.jenkinsci.plugins.gitclient.verifier;

import hudson.model.TaskListener;
import org.apache.sshd.client.keyverifier.ServerKeyVerifier;
import org.eclipse.jgit.internal.transport.ssh.OpenSshConfigFile;
import org.jenkinsci.remoting.SerializableOnlyOverRemoting;

Expand All @@ -17,4 +18,6 @@ public TaskListener getTaskListener() {
}

public abstract OpenSshConfigFile.HostEntry customizeHostEntry(OpenSshConfigFile.HostEntry hostEntry);

public abstract ServerKeyVerifier getServerKeyVerifier();
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.logging.Logger;
import org.apache.sshd.client.keyverifier.AcceptAllServerKeyVerifier;
import org.apache.sshd.client.keyverifier.DefaultKnownHostsServerKeyVerifier;
import org.apache.sshd.client.keyverifier.ServerKeyVerifier;
import org.eclipse.jgit.internal.transport.ssh.OpenSshConfigFile;
import org.eclipse.jgit.transport.SshConstants;

Expand Down Expand Up @@ -50,5 +53,10 @@ public OpenSshConfigFile.HostEntry customizeHostEntry(OpenSshConfigFile.HostEntr

return hostEntry;
}

@Override
public ServerKeyVerifier getServerKeyVerifier() {
return new DefaultKnownHostsServerKeyVerifier(AcceptAllServerKeyVerifier.INSTANCE, false);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
import java.nio.file.Path;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.sshd.client.keyverifier.AcceptAllServerKeyVerifier;
import org.apache.sshd.client.keyverifier.DefaultKnownHostsServerKeyVerifier;
import org.apache.sshd.client.keyverifier.ServerKeyVerifier;
import org.eclipse.jgit.internal.transport.ssh.OpenSshConfigFile;
import org.eclipse.jgit.transport.SshConstants;

Expand Down Expand Up @@ -56,6 +59,11 @@ public OpenSshConfigFile.HostEntry customizeHostEntry(OpenSshConfigFile.HostEntr
hostEntry.setValue(SshConstants.STRICT_HOST_KEY_CHECKING, SshConstants.YES);
return hostEntry;
}

@Override
public ServerKeyVerifier getServerKeyVerifier() {
return new DefaultKnownHostsServerKeyVerifier(AcceptAllServerKeyVerifier.INSTANCE, true);
}
}

private void logHint(TaskListener listener) {
Expand Down
Loading

0 comments on commit ea1fd59

Please sign in to comment.