/* * Copyright (C) 2020, 2021 Thomas Wolf and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport.sshd; import java.io.File; import java.nio.file.Path; import java.security.KeyPair; import java.util.Collections; import java.util.List; import java.util.function.BiFunction; import java.util.function.Function; import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.transport.SshConfigStore; import org.eclipse.jgit.transport.sshd.agent.ConnectorFactory; import org.eclipse.jgit.util.StringUtils; /** * A builder API to configure {@link SshdSessionFactory SshdSessionFactories}. * * @since 5.8 */ public final class SshdSessionFactoryBuilder { private final State state = new State(); /** * Sets the {@link ProxyDataFactory} to use for {@link SshdSessionFactory * SshdSessionFactories} created by {@link #build(KeyCache)}. * * @param proxyDataFactory * to use * @return this {@link SshdSessionFactoryBuilder} */ public SshdSessionFactoryBuilder setProxyDataFactory( ProxyDataFactory proxyDataFactory) { this.state.proxyDataFactory = proxyDataFactory; return this; } /** * Sets the home directory to use for {@link SshdSessionFactory * SshdSessionFactories} created by {@link #build(KeyCache)}. * * @param homeDirectory * to use; may be {@code null}, in which case the home directory * as defined by {@link org.eclipse.jgit.util.FS#userHome() * FS.userHome()} is assumed * @return this {@link SshdSessionFactoryBuilder} */ public SshdSessionFactoryBuilder setHomeDirectory(File homeDirectory) { this.state.homeDirectory = homeDirectory; return this; } /** * Sets the SSH directory to use for {@link SshdSessionFactory * SshdSessionFactories} created by {@link #build(KeyCache)}. * * @param sshDirectory * to use; may be {@code null}, in which case ".ssh" under the * {@link #setHomeDirectory(File) home directory} is assumed * @return this {@link SshdSessionFactoryBuilder} */ public SshdSessionFactoryBuilder setSshDirectory(File sshDirectory) { this.state.sshDirectory = sshDirectory; return this; } /** * Sets the default preferred authentication mechanisms to use for * {@link SshdSessionFactory SshdSessionFactories} created by * {@link #build(KeyCache)}. * * @param authentications * comma-separated list of authentication mechanism names; if * {@code null} or empty, the default as specified by * {@link SshdSessionFactory#getDefaultPreferredAuthentications()} * will be used * @return this {@link SshdSessionFactoryBuilder} */ public SshdSessionFactoryBuilder setPreferredAuthentications( String authentications) { this.state.preferredAuthentications = authentications; return this; } /** * Sets a function that returns the SSH config file, given the SSH * directory. The function may return {@code null}, in which case no SSH * config file will be used. If a non-null file is returned, it will be used * when it exists. If no supplier has been set, or the supplier has been set * explicitly to {@code null}, by default a file named * {@link org.eclipse.jgit.transport.SshConstants#CONFIG * SshConstants.CONFIG} in the {@link #setSshDirectory(File) SSH directory} * is used. * * @param supplier * returning a {@link File} for the SSH config file to use, or * returning {@code null} if no config file is to be used * @return this {@link SshdSessionFactoryBuilder} */ public SshdSessionFactoryBuilder setConfigFile( Function supplier) { this.state.configFileFinder = supplier; return this; } /** * A factory interface for creating a {@link SshConfigStore}. */ @FunctionalInterface public interface ConfigStoreFactory { /** * Creates a {@link SshConfigStore}. May return {@code null} if none is * to be used. * * @param homeDir * to use for ~-replacements * @param configFile * to use, may be {@code null} if none * @param localUserName * name of the current user in the local OS * @return the {@link SshConfigStore}, or {@code null} if none is to be * used */ SshConfigStore create(@NonNull File homeDir, File configFile, String localUserName); } /** * Sets a factory for the {@link SshConfigStore} to use. If not set or * explicitly set to {@code null}, the default as specified by * {@link SshdSessionFactory#createSshConfigStore(File, File, String)} is * used. * * @param factory * to set * @return this {@link SshdSessionFactoryBuilder} */ public SshdSessionFactoryBuilder setConfigStoreFactory( ConfigStoreFactory factory) { this.state.configFactory = factory; return this; } /** * Sets a function that returns the default known hosts files, given the SSH * directory. If not set or explicitly set to {@code null}, the defaults as * specified by {@link SshdSessionFactory#getDefaultKnownHostsFiles(File)} * are used. * * @param supplier * to get the default known hosts files * @return this {@link SshdSessionFactoryBuilder} */ public SshdSessionFactoryBuilder setDefaultKnownHostsFiles( Function> supplier) { this.state.knownHostsFileFinder = supplier; return this; } /** * Sets a function that returns the default private key files, given the SSH * directory. If not set or explicitly set to {@code null}, the defaults as * specified by {@link SshdSessionFactory#getDefaultIdentities(File)} are * used. * * @param supplier * to get the default private key files * @return this {@link SshdSessionFactoryBuilder} */ public SshdSessionFactoryBuilder setDefaultIdentities( Function> supplier) { this.state.defaultKeyFileFinder = supplier; return this; } /** * Sets a function that returns the default private keys, given the SSH * directory. If not set or explicitly set to {@code null}, the defaults as * specified by {@link SshdSessionFactory#getDefaultKeys(File)} are used. * * @param provider * to get the default private key files * @return this {@link SshdSessionFactoryBuilder} */ public SshdSessionFactoryBuilder setDefaultKeysProvider( Function> provider) { this.state.defaultKeysProvider = provider; return this; } /** * Sets a factory function to create a {@link KeyPasswordProvider}. If not * set or explicitly set to {@code null}, or if the factory returns * {@code null}, the default as specified by * {@link SshdSessionFactory#createKeyPasswordProvider(CredentialsProvider)} * is used. * * @param factory * to create a {@link KeyPasswordProvider} * @return this {@link SshdSessionFactoryBuilder} */ public SshdSessionFactoryBuilder setKeyPasswordProvider( Function factory) { this.state.passphraseProviderFactory = factory; return this; } /** * Sets a function that creates a new {@link ServerKeyDatabase}, given the * SSH and home directory. If not set or explicitly set to {@code null}, or * if the {@code factory} returns {@code null}, the default as specified by * {@link SshdSessionFactory#createServerKeyDatabase(File, File)} is used. * * @param factory * to create a {@link ServerKeyDatabase} * @return this {@link SshdSessionFactoryBuilder} */ public SshdSessionFactoryBuilder setServerKeyDatabase( BiFunction factory) { this.state.serverKeyDatabaseCreator = factory; return this; } /** * Sets an explicit {@link ConnectorFactory}. If {@code null}, there will be * no support for SSH agents. *

* If not set, the created {@link SshdSessionFactory} will use the * {@link java.util.ServiceLoader} to find an {@link ConnectorFactory}. *

* * @param factory * {@link ConnectorFactory} to use * @return this {@link SshdSessionFactoryBuilder} * @since 6.0 */ public SshdSessionFactoryBuilder setConnectorFactory( ConnectorFactory factory) { this.state.connectorFactory = factory; this.state.connectorFactorySet = true; return this; } /** * Removes a previously set {@link ConnectorFactory}. The created * {@link SshdSessionFactory} will use the {@link java.util.ServiceLoader} * to find an {@link ConnectorFactory}. This is also the default if * {@link #setConnectorFactory(ConnectorFactory)} isn't called at all. * * @return this {@link SshdSessionFactoryBuilder} * @since 6.0 */ public SshdSessionFactoryBuilder withDefaultConnectorFactory() { this.state.connectorFactory = null; this.state.connectorFactorySet = false; return this; } /** * Builds a {@link SshdSessionFactory} as configured, using the given * {@link KeyCache} for caching keys. *

* Different {@link SshdSessionFactory SshdSessionFactories} should * not share the same {@link KeyCache} since the cache is * invalidated when the factory itself or when the last {@link SshdSession} * created from the factory is closed. *

* * @param cache * to use for caching ssh keys; may be {@code null} if no caching * is desired. * @return the {@link SshdSessionFactory} */ public SshdSessionFactory build(KeyCache cache) { // Use a copy to avoid that subsequent calls to setters affect an // already created SshdSessionFactory. return state.copy().build(cache); } private static class State { ProxyDataFactory proxyDataFactory; File homeDirectory; File sshDirectory; String preferredAuthentications; Function configFileFinder; ConfigStoreFactory configFactory; Function passphraseProviderFactory; Function> knownHostsFileFinder; Function> defaultKeyFileFinder; Function> defaultKeysProvider; BiFunction serverKeyDatabaseCreator; ConnectorFactory connectorFactory; boolean connectorFactorySet; State copy() { State c = new State(); c.proxyDataFactory = proxyDataFactory; c.homeDirectory = homeDirectory; c.sshDirectory = sshDirectory; c.preferredAuthentications = preferredAuthentications; c.configFileFinder = configFileFinder; c.configFactory = configFactory; c.passphraseProviderFactory = passphraseProviderFactory; c.knownHostsFileFinder = knownHostsFileFinder; c.defaultKeyFileFinder = defaultKeyFileFinder; c.defaultKeysProvider = defaultKeysProvider; c.serverKeyDatabaseCreator = serverKeyDatabaseCreator; c.connectorFactory = connectorFactory; c.connectorFactorySet = connectorFactorySet; return c; } SshdSessionFactory build(KeyCache cache) { SshdSessionFactory factory = new SessionFactory(cache, proxyDataFactory); factory.setHomeDirectory(homeDirectory); factory.setSshDirectory(sshDirectory); return factory; } private class SessionFactory extends SshdSessionFactory { public SessionFactory(KeyCache cache, ProxyDataFactory proxyDataFactory) { super(cache, proxyDataFactory); } @Override protected File getSshConfig(File sshDir) { if (configFileFinder != null) { return configFileFinder.apply(sshDir); } return super.getSshConfig(sshDir); } @Override protected List getDefaultKnownHostsFiles(File sshDir) { if (knownHostsFileFinder != null) { List result = knownHostsFileFinder.apply(sshDir); return result == null ? Collections.emptyList() : result; } return super.getDefaultKnownHostsFiles(sshDir); } @Override protected List getDefaultIdentities(File sshDir) { if (defaultKeyFileFinder != null) { List result = defaultKeyFileFinder.apply(sshDir); return result == null ? Collections.emptyList() : result; } return super.getDefaultIdentities(sshDir); } @Override protected String getDefaultPreferredAuthentications() { if (!StringUtils.isEmptyOrNull(preferredAuthentications)) { return preferredAuthentications; } return super.getDefaultPreferredAuthentications(); } @Override protected Iterable getDefaultKeys(File sshDir) { if (defaultKeysProvider != null) { Iterable result = defaultKeysProvider .apply(sshDir); return result == null ? Collections.emptyList() : result; } return super.getDefaultKeys(sshDir); } @Override protected KeyPasswordProvider createKeyPasswordProvider( CredentialsProvider provider) { if (passphraseProviderFactory != null) { KeyPasswordProvider result = passphraseProviderFactory .apply(provider); if (result != null) { return result; } } return super.createKeyPasswordProvider(provider); } @Override protected ServerKeyDatabase createServerKeyDatabase(File homeDir, File sshDir) { if (serverKeyDatabaseCreator != null) { ServerKeyDatabase result = serverKeyDatabaseCreator .apply(homeDir, sshDir); if (result != null) { return result; } } return super.createServerKeyDatabase(homeDir, sshDir); } @Override protected SshConfigStore createSshConfigStore(File homeDir, File configFile, String localUserName) { if (configFactory != null) { return configFactory.create(homeDir, configFile, localUserName); } return super.createSshConfigStore(homeDir, configFile, localUserName); } @Override protected ConnectorFactory getConnectorFactory() { if (connectorFactorySet) { return connectorFactory; } // Use default via ServiceLoader return super.getConnectorFactory(); } } } }