SshdSessionFactory: generalize providing default keys
Provide a mechanism for a subclass to provide its own set of default identities from anywhere as an Iterable<KeyPair>. The default implementation is functionally unchanged and uses the known default identity files in the ~/.ssh directory. A subclass can override the getDefaultKeys() function and return whatever keys are appropriate. Bug: 543152 Change-Id: I500d63146bc67e20e051f617790eb87c7cb500b6 Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
This commit is contained in:
parent
c4420d2434
commit
2cb842ef02
|
@ -63,7 +63,8 @@
|
||||||
* A {@link EncryptedFileKeyPairProvider} that uses an external
|
* A {@link EncryptedFileKeyPairProvider} that uses an external
|
||||||
* {@link KeyCache}.
|
* {@link KeyCache}.
|
||||||
*/
|
*/
|
||||||
public class CachingKeyPairProvider extends EncryptedFileKeyPairProvider {
|
public class CachingKeyPairProvider extends EncryptedFileKeyPairProvider
|
||||||
|
implements Iterable<KeyPair> {
|
||||||
|
|
||||||
private final KeyCache cache;
|
private final KeyCache cache;
|
||||||
|
|
||||||
|
@ -83,11 +84,17 @@ public CachingKeyPairProvider(List<Path> paths, KeyCache cache) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Iterable<KeyPair> loadKeys(Collection<? extends Path> resources) {
|
public Iterator<KeyPair> iterator() {
|
||||||
|
Collection<? extends Path> resources = getPaths();
|
||||||
if (resources.isEmpty()) {
|
if (resources.isEmpty()) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyListIterator();
|
||||||
}
|
}
|
||||||
return () -> new CancellingKeyPairIterator(resources);
|
return new CancellingKeyPairIterator(resources);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterable<KeyPair> loadKeys() {
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -70,7 +70,7 @@
|
||||||
* encrypted private key if the {@link FilePasswordProvider} is a
|
* encrypted private key if the {@link FilePasswordProvider} is a
|
||||||
* {@link RepeatingFilePasswordProvider}.
|
* {@link RepeatingFilePasswordProvider}.
|
||||||
*/
|
*/
|
||||||
public class EncryptedFileKeyPairProvider extends FileKeyPairProvider {
|
public abstract class EncryptedFileKeyPairProvider extends FileKeyPairProvider {
|
||||||
|
|
||||||
// TODO: remove this class once we're based on sshd > 2.1.0. See upstream
|
// TODO: remove this class once we're based on sshd > 2.1.0. See upstream
|
||||||
// issue SSHD-850 https://issues.apache.org/jira/browse/SSHD-850 and commit
|
// issue SSHD-850 https://issues.apache.org/jira/browse/SSHD-850 and commit
|
||||||
|
|
|
@ -70,7 +70,7 @@
|
||||||
import org.apache.sshd.common.future.SshFutureListener;
|
import org.apache.sshd.common.future.SshFutureListener;
|
||||||
import org.apache.sshd.common.io.IoConnectFuture;
|
import org.apache.sshd.common.io.IoConnectFuture;
|
||||||
import org.apache.sshd.common.io.IoSession;
|
import org.apache.sshd.common.io.IoSession;
|
||||||
import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
|
import org.apache.sshd.common.keyprovider.AbstractResourceKeyPairProvider;
|
||||||
import org.apache.sshd.common.keyprovider.KeyPairProvider;
|
import org.apache.sshd.common.keyprovider.KeyPairProvider;
|
||||||
import org.apache.sshd.common.session.helpers.AbstractSession;
|
import org.apache.sshd.common.session.helpers.AbstractSession;
|
||||||
import org.apache.sshd.common.util.ValidateUtils;
|
import org.apache.sshd.common.util.ValidateUtils;
|
||||||
|
@ -243,12 +243,11 @@ private JGitClientSession createSession(IoSession ioSession,
|
||||||
int numberOfPasswordPrompts = getNumberOfPasswordPrompts(hostConfig);
|
int numberOfPasswordPrompts = getNumberOfPasswordPrompts(hostConfig);
|
||||||
session.getProperties().put(PASSWORD_PROMPTS,
|
session.getProperties().put(PASSWORD_PROMPTS,
|
||||||
Integer.valueOf(numberOfPasswordPrompts));
|
Integer.valueOf(numberOfPasswordPrompts));
|
||||||
FilePasswordProvider provider = getFilePasswordProvider();
|
FilePasswordProvider passwordProvider = getFilePasswordProvider();
|
||||||
if (provider instanceof RepeatingFilePasswordProvider) {
|
if (passwordProvider instanceof RepeatingFilePasswordProvider) {
|
||||||
((RepeatingFilePasswordProvider) provider)
|
((RepeatingFilePasswordProvider) passwordProvider)
|
||||||
.setAttempts(numberOfPasswordPrompts);
|
.setAttempts(numberOfPasswordPrompts);
|
||||||
}
|
}
|
||||||
FileKeyPairProvider ourConfiguredKeysProvider = null;
|
|
||||||
List<Path> identities = hostConfig.getIdentities().stream()
|
List<Path> identities = hostConfig.getIdentities().stream()
|
||||||
.map(s -> {
|
.map(s -> {
|
||||||
try {
|
try {
|
||||||
|
@ -260,16 +259,16 @@ private JGitClientSession createSession(IoSession ioSession,
|
||||||
}
|
}
|
||||||
}).filter(p -> p != null && Files.exists(p))
|
}).filter(p -> p != null && Files.exists(p))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
ourConfiguredKeysProvider = new CachingKeyPairProvider(identities,
|
CachingKeyPairProvider ourConfiguredKeysProvider = new CachingKeyPairProvider(
|
||||||
keyCache);
|
identities, keyCache);
|
||||||
ourConfiguredKeysProvider.setPasswordFinder(getFilePasswordProvider());
|
ourConfiguredKeysProvider.setPasswordFinder(passwordProvider);
|
||||||
if (hostConfig.isIdentitiesOnly()) {
|
if (hostConfig.isIdentitiesOnly()) {
|
||||||
session.setKeyPairProvider(ourConfiguredKeysProvider);
|
session.setKeyPairProvider(ourConfiguredKeysProvider);
|
||||||
} else {
|
} else {
|
||||||
KeyPairProvider defaultKeysProvider = getKeyPairProvider();
|
KeyPairProvider defaultKeysProvider = getKeyPairProvider();
|
||||||
if (defaultKeysProvider instanceof FileKeyPairProvider) {
|
if (defaultKeysProvider instanceof AbstractResourceKeyPairProvider<?>) {
|
||||||
((FileKeyPairProvider) defaultKeysProvider)
|
((AbstractResourceKeyPairProvider<?>) defaultKeysProvider)
|
||||||
.setPasswordFinder(getFilePasswordProvider());
|
.setPasswordFinder(passwordProvider);
|
||||||
}
|
}
|
||||||
KeyPairProvider combinedProvider = new CombinedKeyPairProvider(
|
KeyPairProvider combinedProvider = new CombinedKeyPairProvider(
|
||||||
ourConfiguredKeysProvider, defaultKeysProvider);
|
ourConfiguredKeysProvider, defaultKeysProvider);
|
||||||
|
|
|
@ -47,6 +47,7 @@
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.security.KeyPair;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
@ -68,7 +69,6 @@
|
||||||
import org.apache.sshd.common.NamedFactory;
|
import org.apache.sshd.common.NamedFactory;
|
||||||
import org.apache.sshd.common.compression.BuiltinCompressions;
|
import org.apache.sshd.common.compression.BuiltinCompressions;
|
||||||
import org.apache.sshd.common.config.keys.FilePasswordProvider;
|
import org.apache.sshd.common.config.keys.FilePasswordProvider;
|
||||||
import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
|
|
||||||
import org.apache.sshd.common.keyprovider.KeyPairProvider;
|
import org.apache.sshd.common.keyprovider.KeyPairProvider;
|
||||||
import org.eclipse.jgit.annotations.NonNull;
|
import org.eclipse.jgit.annotations.NonNull;
|
||||||
import org.eclipse.jgit.errors.TransportException;
|
import org.eclipse.jgit.errors.TransportException;
|
||||||
|
@ -89,7 +89,9 @@
|
||||||
import org.eclipse.jgit.util.FS;
|
import org.eclipse.jgit.util.FS;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link SshSessionFactory} that uses Apache MINA sshd.
|
* A {@link SshSessionFactory} that uses Apache MINA sshd. Classes from Apache
|
||||||
|
* MINA sshd are kept private to avoid API evolution problems when Apache MINA
|
||||||
|
* sshd interfaces change.
|
||||||
*
|
*
|
||||||
* @since 5.2
|
* @since 5.2
|
||||||
*/
|
*/
|
||||||
|
@ -103,7 +105,7 @@ public class SshdSessionFactory extends SshSessionFactory implements Closeable {
|
||||||
|
|
||||||
private final Map<Tuple, ServerKeyVerifier> defaultServerKeyVerifier = new ConcurrentHashMap<>();
|
private final Map<Tuple, ServerKeyVerifier> defaultServerKeyVerifier = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
private final Map<Tuple, FileKeyPairProvider> defaultKeys = new ConcurrentHashMap<>();
|
private final Map<Tuple, Iterable<KeyPair>> defaultKeys = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
private final KeyCache keyCache;
|
private final KeyCache keyCache;
|
||||||
|
|
||||||
|
@ -209,8 +211,8 @@ public SshdSession getSession(URIish uri,
|
||||||
}
|
}
|
||||||
HostConfigEntryResolver configFile = getHostConfigEntryResolver(
|
HostConfigEntryResolver configFile = getHostConfigEntryResolver(
|
||||||
home, sshDir);
|
home, sshDir);
|
||||||
KeyPairProvider defaultKeysProvider = getDefaultKeysProvider(
|
KeyPairProvider defaultKeysProvider = toKeyPairProvider(
|
||||||
sshDir);
|
getDefaultKeys(sshDir));
|
||||||
KeyPasswordProvider passphrases = createKeyPasswordProvider(
|
KeyPasswordProvider passphrases = createKeyPasswordProvider(
|
||||||
credentialsProvider);
|
credentialsProvider);
|
||||||
SshClient client = ClientBuilder.builder()
|
SshClient client = ClientBuilder.builder()
|
||||||
|
@ -395,14 +397,38 @@ protected List<Path> getDefaultKnownHostsFiles(@NonNull File sshDir) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines a {@link KeyPairProvider} to use to load the default keys.
|
* Determines the default keys. The default implementation will lazy load
|
||||||
|
* the {@link #getDefaultIdentities(File) default identity files}.
|
||||||
|
* <p>
|
||||||
|
* Subclasses may override and return an {@link Iterable} of whatever keys
|
||||||
|
* are appropriate. If the returned iterable lazily loads keys, it should be
|
||||||
|
* an instance of
|
||||||
|
* {@link org.apache.sshd.common.keyprovider.AbstractResourceKeyPairProvider
|
||||||
|
* AbstractResourceKeyPairProvider} so that the session can later pass it
|
||||||
|
* the {@link #createKeyPasswordProvider(CredentialsProvider) password
|
||||||
|
* provider} wrapped as a {@link FilePasswordProvider} via
|
||||||
|
* {@link org.apache.sshd.common.keyprovider.AbstractResourceKeyPairProvider#setPasswordFinder(FilePasswordProvider)
|
||||||
|
* AbstractResourceKeyPairProvider#setPasswordFinder(FilePasswordProvider)}
|
||||||
|
* so that encrypted, password-protected keys can be loaded.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* The default implementation uses exactly this mechanism; class
|
||||||
|
* {@link CachingKeyPairProvider} may serve as a model for a customized
|
||||||
|
* lazy-loading {@link Iterable} implementation
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* If the {@link Iterable} returned has the keys already pre-loaded or
|
||||||
|
* otherwise doesn't need to decrypt encrypted keys, it can be any
|
||||||
|
* {@link Iterable}, for instance a simple {@link java.util.List List}.
|
||||||
|
* </p>
|
||||||
*
|
*
|
||||||
* @param sshDir
|
* @param sshDir
|
||||||
* to look in for keys
|
* to look in for keys
|
||||||
* @return the {@link KeyPairProvider}
|
* @return an {@link Iterable} over the default keys
|
||||||
|
* @since 5.3
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
private KeyPairProvider getDefaultKeysProvider(@NonNull File sshDir) {
|
protected Iterable<KeyPair> getDefaultKeys(@NonNull File sshDir) {
|
||||||
List<Path> defaultIdentities = getDefaultIdentities(sshDir);
|
List<Path> defaultIdentities = getDefaultIdentities(sshDir);
|
||||||
return defaultKeys.computeIfAbsent(
|
return defaultKeys.computeIfAbsent(
|
||||||
new Tuple(defaultIdentities.toArray(new Path[0])),
|
new Tuple(defaultIdentities.toArray(new Path[0])),
|
||||||
|
@ -410,6 +436,21 @@ private KeyPairProvider getDefaultKeysProvider(@NonNull File sshDir) {
|
||||||
getKeyCache()));
|
getKeyCache()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts an {@link Iterable} of {link KeyPair}s into a
|
||||||
|
* {@link KeyPairProvider}.
|
||||||
|
*
|
||||||
|
* @param keys
|
||||||
|
* to provide via the returned {@link KeyPairProvider}
|
||||||
|
* @return a {@link KeyPairProvider} that provides the given {@code keys}
|
||||||
|
*/
|
||||||
|
private KeyPairProvider toKeyPairProvider(Iterable<KeyPair> keys) {
|
||||||
|
if (keys instanceof KeyPairProvider) {
|
||||||
|
return (KeyPairProvider) keys;
|
||||||
|
}
|
||||||
|
return () -> keys;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a list of default identities, i.e., private key files that shall
|
* Gets a list of default identities, i.e., private key files that shall
|
||||||
* always be tried for public key authentication. Typically those are
|
* always be tried for public key authentication. Typically those are
|
||||||
|
|
Loading…
Reference in New Issue