[sshd agent] Introduce ConnectorDescriptor
Once a factory supports different SSH agents on the same platform, which is planned for Windows once we use Apache MINA sshd 2.8.0, client code may need to have a way to specify which SSH agent shall be used when the SSH config doesn't define anything. Add a mechanism by which a ConnectorFactory can tell what Connectors it may provide. Client code can use this to set the identityAgent parameter of ConnectorFactory.create() to the wanted default if it would be null otherwise. A ConnectorDescriptor is a pair of strings: an internal name, and a display name. The latter is included because client code might want to communicate agent names to the user, be it in error messages or in some chooser dialog where a user could define which of several alternative SSH agents should be used as default. The internal name is intended to be used in the IdentityAgent directive in ~/.ssh/config. Also make the ConnectorFactory discovered via the ServiceLoader accessible and overrideable. Provide static get/setDefault() methods, similar to the SshSessionFactory itself. Change-Id: Ie3d077395d32dfddc72bc8627e92b23636938182 Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
This commit is contained in:
parent
b84738c369
commit
e7838b9c08
|
@ -13,4 +13,5 @@ msgSendFailed=Sending {0} bytes to SSH agent failed; {0} bytes not written
|
||||||
msgSendFailed2=Sending {0} bytes to SSH agent failed: {1} - {2}
|
msgSendFailed2=Sending {0} bytes to SSH agent failed: {1} - {2}
|
||||||
msgSharedMemoryFailed=Could not set up shared memory for communicating with Pageant
|
msgSharedMemoryFailed=Could not set up shared memory for communicating with Pageant
|
||||||
msgShortRead=Short read from SSH agent, expected {0} bytes, got {1} bytes; last read() returned {2}
|
msgShortRead=Short read from SSH agent, expected {0} bytes, got {1} bytes; last read() returned {2}
|
||||||
|
pageant=Pageant
|
||||||
|
unixDefaultAgent=ssh-agent
|
||||||
|
|
|
@ -11,6 +11,8 @@
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
import org.eclipse.jgit.transport.sshd.agent.Connector;
|
import org.eclipse.jgit.transport.sshd.agent.Connector;
|
||||||
import org.eclipse.jgit.transport.sshd.agent.ConnectorFactory;
|
import org.eclipse.jgit.transport.sshd.agent.ConnectorFactory;
|
||||||
|
@ -41,4 +43,26 @@ public boolean isSupported() {
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return NAME;
|
return NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
* <p>
|
||||||
|
* This factory returns on Windows a
|
||||||
|
* {@link org.eclipse.jgit.transport.sshd.agent.ConnectorFactory.ConnectorDescriptor
|
||||||
|
* ConnectorDescriptor} for the internal name "pageant"; on Unix one for
|
||||||
|
* "SSH_AUTH_SOCK".
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Collection<ConnectorDescriptor> getSupportedConnectors() {
|
||||||
|
return Collections.singleton(getDefaultConnector());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConnectorDescriptor getDefaultConnector() {
|
||||||
|
if (SystemReader.getInstance().isWindows()) {
|
||||||
|
return PageantConnector.DESCRIPTOR;
|
||||||
|
}
|
||||||
|
return UnixDomainSocketConnector.DESCRIPTOR;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,12 +12,29 @@
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import org.eclipse.jgit.transport.sshd.agent.AbstractConnector;
|
import org.eclipse.jgit.transport.sshd.agent.AbstractConnector;
|
||||||
|
import org.eclipse.jgit.transport.sshd.agent.ConnectorFactory.ConnectorDescriptor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A connector using Pageant's shared memory IPC mechanism.
|
* A connector using Pageant's shared memory IPC mechanism.
|
||||||
*/
|
*/
|
||||||
public class PageantConnector extends AbstractConnector {
|
public class PageantConnector extends AbstractConnector {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link ConnectorDescriptor} for the {@link PageantConnector}.
|
||||||
|
*/
|
||||||
|
public static final ConnectorDescriptor DESCRIPTOR = new ConnectorDescriptor() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getIdentityAgent() {
|
||||||
|
return "pageant"; //$NON-NLS-1$
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayName() {
|
||||||
|
return Texts.get().pageant;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private final PageantLibrary lib;
|
private final PageantLibrary lib;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -42,5 +42,7 @@ public static Texts get() {
|
||||||
/***/ public String msgSendFailed2;
|
/***/ public String msgSendFailed2;
|
||||||
/***/ public String msgSharedMemoryFailed;
|
/***/ public String msgSharedMemoryFailed;
|
||||||
/***/ public String msgShortRead;
|
/***/ public String msgShortRead;
|
||||||
|
/***/ public String pageant;
|
||||||
|
/***/ public String unixDefaultAgent;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
|
|
||||||
import org.apache.sshd.common.SshException;
|
import org.apache.sshd.common.SshException;
|
||||||
import org.eclipse.jgit.transport.sshd.agent.AbstractConnector;
|
import org.eclipse.jgit.transport.sshd.agent.AbstractConnector;
|
||||||
|
import org.eclipse.jgit.transport.sshd.agent.ConnectorFactory.ConnectorDescriptor;
|
||||||
import org.eclipse.jgit.util.StringUtils;
|
import org.eclipse.jgit.util.StringUtils;
|
||||||
import org.eclipse.jgit.util.SystemReader;
|
import org.eclipse.jgit.util.SystemReader;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -38,6 +39,22 @@
|
||||||
*/
|
*/
|
||||||
public class UnixDomainSocketConnector extends AbstractConnector {
|
public class UnixDomainSocketConnector extends AbstractConnector {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link ConnectorDescriptor} for the {@link UnixDomainSocketConnector}.
|
||||||
|
*/
|
||||||
|
public static final ConnectorDescriptor DESCRIPTOR = new ConnectorDescriptor() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getIdentityAgent() {
|
||||||
|
return ENV_SSH_AUTH_SOCK;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayName() {
|
||||||
|
return Texts.get().unixDefaultAgent;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory
|
private static final Logger LOG = LoggerFactory
|
||||||
.getLogger(UnixDomainSocketConnector.class);
|
.getLogger(UnixDomainSocketConnector.class);
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
*/
|
*/
|
||||||
public final class ConnectorFactoryProvider {
|
public final class ConnectorFactoryProvider {
|
||||||
|
|
||||||
private static final ConnectorFactory FACTORY = loadDefaultFactory();
|
private static volatile ConnectorFactory INSTANCE = loadDefaultFactory();
|
||||||
|
|
||||||
private static ConnectorFactory loadDefaultFactory() {
|
private static ConnectorFactory loadDefaultFactory() {
|
||||||
ServiceLoader<ConnectorFactory> loader = ServiceLoader
|
ServiceLoader<ConnectorFactory> loader = ServiceLoader
|
||||||
|
@ -35,17 +35,27 @@ private static ConnectorFactory loadDefaultFactory() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ConnectorFactoryProvider() {
|
|
||||||
// No instantiation
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the default {@link ConnectorFactory} obtained via the
|
* Retrieves the currently set default {@link ConnectorFactory}.
|
||||||
* {@link ServiceLoader}.
|
|
||||||
*
|
*
|
||||||
* @return the {@link ConnectorFactory}, or {@code null} if none.
|
* @return the {@link ConnectorFactory}, or {@code null} if none.
|
||||||
*/
|
*/
|
||||||
public static ConnectorFactory getDefaultFactory() {
|
public static ConnectorFactory getDefaultFactory() {
|
||||||
return FACTORY;
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the default {@link ConnectorFactory}.
|
||||||
|
*
|
||||||
|
* @param factory
|
||||||
|
* {@link ConnectorFactory} to use, or {@code null} to use the
|
||||||
|
* factory discovered via the {@link ServiceLoader}.
|
||||||
|
*/
|
||||||
|
public static void setDefaultFactory(ConnectorFactory factory) {
|
||||||
|
INSTANCE = factory == null ? loadDefaultFactory() : factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ConnectorFactoryProvider() {
|
||||||
|
// No instantiation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,7 +56,6 @@
|
||||||
import org.eclipse.jgit.internal.transport.sshd.OpenSshServerKeyDatabase;
|
import org.eclipse.jgit.internal.transport.sshd.OpenSshServerKeyDatabase;
|
||||||
import org.eclipse.jgit.internal.transport.sshd.PasswordProviderWrapper;
|
import org.eclipse.jgit.internal.transport.sshd.PasswordProviderWrapper;
|
||||||
import org.eclipse.jgit.internal.transport.sshd.SshdText;
|
import org.eclipse.jgit.internal.transport.sshd.SshdText;
|
||||||
import org.eclipse.jgit.internal.transport.sshd.agent.ConnectorFactoryProvider;
|
|
||||||
import org.eclipse.jgit.internal.transport.sshd.agent.JGitSshAgentFactory;
|
import org.eclipse.jgit.internal.transport.sshd.agent.JGitSshAgentFactory;
|
||||||
import org.eclipse.jgit.transport.CredentialsProvider;
|
import org.eclipse.jgit.transport.CredentialsProvider;
|
||||||
import org.eclipse.jgit.transport.SshConfigStore;
|
import org.eclipse.jgit.transport.SshConfigStore;
|
||||||
|
@ -456,12 +455,15 @@ protected ServerKeyDatabase createServerKeyDatabase(@NonNull File homeDir,
|
||||||
/**
|
/**
|
||||||
* Gets a {@link ConnectorFactory}. If this returns {@code null}, SSH agents
|
* Gets a {@link ConnectorFactory}. If this returns {@code null}, SSH agents
|
||||||
* are not supported.
|
* are not supported.
|
||||||
|
* <p>
|
||||||
|
* The default implementation uses {@link ConnectorFactory#getDefault()}
|
||||||
|
* </p>
|
||||||
*
|
*
|
||||||
* @return the factory, or {@code null} if no SSH agent support is desired
|
* @return the factory, or {@code null} if no SSH agent support is desired
|
||||||
* @since 6.0
|
* @since 6.0
|
||||||
*/
|
*/
|
||||||
protected ConnectorFactory getConnectorFactory() {
|
protected ConnectorFactory getConnectorFactory() {
|
||||||
return ConnectorFactoryProvider.getDefaultFactory();
|
return ConnectorFactory.getDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -11,8 +11,10 @@
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
import org.eclipse.jgit.annotations.NonNull;
|
import org.eclipse.jgit.annotations.NonNull;
|
||||||
|
import org.eclipse.jgit.internal.transport.sshd.agent.ConnectorFactoryProvider;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A factory for creating {@link Connector}s. This is a service provider
|
* A factory for creating {@link Connector}s. This is a service provider
|
||||||
|
@ -24,14 +26,45 @@
|
||||||
*/
|
*/
|
||||||
public interface ConnectorFactory {
|
public interface ConnectorFactory {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the currently set default {@link ConnectorFactory}. This is the
|
||||||
|
* factory that is used unless overridden by the
|
||||||
|
* {@link org.eclipse.jgit.transport.sshd.SshdSessionFactory}.
|
||||||
|
*
|
||||||
|
* @return the current default factory; may be {@code null} if none is set
|
||||||
|
* and the {@link java.util.ServiceLoader} cannot find any suitable
|
||||||
|
* implementation
|
||||||
|
*/
|
||||||
|
static ConnectorFactory getDefault() {
|
||||||
|
return ConnectorFactoryProvider.getDefaultFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a default {@link ConnectorFactory}. This is the factory that is used
|
||||||
|
* unless overridden by the
|
||||||
|
* {@link org.eclipse.jgit.transport.sshd.SshdSessionFactory}.
|
||||||
|
* <p>
|
||||||
|
* If no default factory is set programmatically, an implementation is
|
||||||
|
* discovered via the {@link java.util.ServiceLoader}.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param factory
|
||||||
|
* {@link ConnectorFactory} to set, or {@code null} to revert to
|
||||||
|
* the default behavior of using the
|
||||||
|
* {@link java.util.ServiceLoader}.
|
||||||
|
*/
|
||||||
|
static void setDefault(ConnectorFactory factory) {
|
||||||
|
ConnectorFactoryProvider.setDefaultFactory(factory);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new {@link Connector}.
|
* Creates a new {@link Connector}.
|
||||||
*
|
*
|
||||||
* @param identityAgent
|
* @param identityAgent
|
||||||
* identifies the wanted agent connection; if {@code null}, the
|
* identifies the wanted agent connection; if {@code null}, the
|
||||||
* factory is free to provide a {@link Connector} to a default
|
* factory is free to provide a {@link Connector} to a default
|
||||||
* agent. The value will typically come from the IdentityAgent
|
* agent. The value will typically come from the
|
||||||
* setting in ~/.ssh/config.
|
* {@code IdentityAgent} setting in {@code ~/.ssh/config}.
|
||||||
* @param homeDir
|
* @param homeDir
|
||||||
* the current local user's home directory as configured in the
|
* the current local user's home directory as configured in the
|
||||||
* {@link org.eclipse.jgit.transport.sshd.SshdSessionFactory}
|
* {@link org.eclipse.jgit.transport.sshd.SshdSessionFactory}
|
||||||
|
@ -58,4 +91,83 @@ Connector create(String identityAgent, File homeDir)
|
||||||
*/
|
*/
|
||||||
String getName();
|
String getName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link ConnectorDescriptor}s describe available {@link Connector}s a
|
||||||
|
* {@link ConnectorFactory} may provide.
|
||||||
|
* <p>
|
||||||
|
* A {@link ConnectorFactory} may support connecting to different SSH
|
||||||
|
* agents. Agents are identified by name; a user can choose a specific agent
|
||||||
|
* for instance via the {@code IdentityAgent} setting in
|
||||||
|
* {@code ~/.ssh/config}.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* OpenSSH knows two built-in names: "none" for not using any agent, and
|
||||||
|
* "SSH_AUTH_SOCK" for using an agent that communicates over a Unix domain
|
||||||
|
* socket given by the value of environment variable {@code SSH_AUTH_SOCK}.
|
||||||
|
* Other agents can be specified in OpenSSH by specifying the socket file
|
||||||
|
* directly. (The "standard" OpenBSD OpenSSH knows only this communication
|
||||||
|
* mechanism.) "SSH_AUTH_SOCK" is also the default in OpenBSD OpenSSH if
|
||||||
|
* nothing is configured.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* A particular {@link ConnectorFactory} may support more communication
|
||||||
|
* mechanisms or different agents. For instance, a factory on Windows might
|
||||||
|
* support Pageant, Win32-OpenSSH, or even git bash ssh-agent, and might
|
||||||
|
* accept internal names like "pageant", "openssh", "SSH_AUTH_SOCK" in
|
||||||
|
* {@link ConnectorFactory#create(String, File)} to choose among them.
|
||||||
|
* </p>
|
||||||
|
* The {@link ConnectorDescriptor} interface and the
|
||||||
|
* {@link ConnectorFactory#getSupportedConnectors()} and
|
||||||
|
* {@link ConnectorFactory#getDefaultConnector()} methods provide a way for
|
||||||
|
* code using a {@link ConnectorFactory} to learn what the factory supports
|
||||||
|
* and thus implement some way by which a user can influence the default
|
||||||
|
* behavior if {@code IdentityAgent} is not set or
|
||||||
|
* {@link ConnectorFactory#create(String, File)} is called with
|
||||||
|
* {@code identityAgent == null}.
|
||||||
|
*/
|
||||||
|
interface ConnectorDescriptor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the internal name of a supported {@link Connector}. The
|
||||||
|
* internal name is the one a user can specify for instance in the
|
||||||
|
* {@code IdentityAgent} setting in {@code ~/.ssh/config} to select the
|
||||||
|
* connector.
|
||||||
|
*
|
||||||
|
* @return the internal name; not empty
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
String getIdentityAgent();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a display name for a {@link Connector}, suitable for
|
||||||
|
* showing in a UI.
|
||||||
|
*
|
||||||
|
* @return the display name; properly localized and not empty
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
String getDisplayName();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells which kinds of SSH agents this {@link ConnectorFactory} supports.
|
||||||
|
* <p>
|
||||||
|
* An implementation of this method should document the possible values it
|
||||||
|
* returns.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @return an immutable collection of {@link ConnectorDescriptor}s,
|
||||||
|
* including {@link #getDefaultConnector()} and not including a
|
||||||
|
* descriptor for internal name "none"
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
Collection<ConnectorDescriptor> getSupportedConnectors();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells what kind of {@link Connector} this {@link ConnectorFactory}
|
||||||
|
* creates if {@link ConnectorFactory#create(String, File)} is called with
|
||||||
|
* {@code identityAgent == null}.
|
||||||
|
*
|
||||||
|
* @return a {@link ConnectorDescriptor} for the default connector
|
||||||
|
*/
|
||||||
|
ConnectorDescriptor getDefaultConnector();
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@
|
||||||
*/
|
*/
|
||||||
public abstract class SshSessionFactory {
|
public abstract class SshSessionFactory {
|
||||||
|
|
||||||
private static SshSessionFactory INSTANCE = loadSshSessionFactory();
|
private static volatile SshSessionFactory INSTANCE = loadSshSessionFactory();
|
||||||
|
|
||||||
private static SshSessionFactory loadSshSessionFactory() {
|
private static SshSessionFactory loadSshSessionFactory() {
|
||||||
ServiceLoader<SshSessionFactory> loader = ServiceLoader.load(SshSessionFactory.class);
|
ServiceLoader<SshSessionFactory> loader = ServiceLoader.load(SshSessionFactory.class);
|
||||||
|
|
Loading…
Reference in New Issue