Implement configuration options for ssh passphrase and strict host key checking (#535)
* add strictHostKeyChecking config option * Add passphrase configuration for SSH connections * test passphrase property is pulled from the environment * test that a if a passphrase is confgured a PassphraseCredentialsProvider is added to the git command.
This commit is contained in:
committed by
Spencer Gibb
parent
4e24a1f38e
commit
405ebad792
@@ -45,6 +45,7 @@ import org.eclipse.jgit.transport.*;
|
||||
import org.eclipse.jgit.transport.OpenSshConfig.Host;
|
||||
import org.eclipse.jgit.util.FileUtils;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.cloud.config.server.support.PassphraseCredentialsProvider;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.io.UrlResource;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -150,6 +151,7 @@ public class JGitEnvironmentRepository extends AbstractScmEnvironmentRepository
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
Assert.state(getUri() != null,
|
||||
"You need to configure a uri for the git repository");
|
||||
initialize();
|
||||
if (this.cloneOnStart) {
|
||||
initClonedRepository();
|
||||
}
|
||||
@@ -287,10 +289,7 @@ public class JGitEnvironmentRepository extends AbstractScmEnvironmentRepository
|
||||
|
||||
setTimeout(fetch);
|
||||
try {
|
||||
if (hasText(getUsername())) {
|
||||
setCredentialsProvider(fetch);
|
||||
}
|
||||
|
||||
setCredentialsProvider(fetch);
|
||||
FetchResult result = fetch.call();
|
||||
if(result.getTrackingRefUpdates() != null && result.getTrackingRefUpdates().size() > 0) {
|
||||
this.logger.info("Fetched for remote " + label + " and found " + result.getTrackingRefUpdates().size()
|
||||
@@ -385,9 +384,7 @@ public class JGitEnvironmentRepository extends AbstractScmEnvironmentRepository
|
||||
CloneCommand clone = this.gitFactory.getCloneCommandByCloneRepository()
|
||||
.setURI(getUri()).setDirectory(getBasedir());
|
||||
setTimeout(clone);
|
||||
if (hasText(getUsername())) {
|
||||
setCredentialsProvider(clone);
|
||||
}
|
||||
setCredentialsProvider(clone);
|
||||
try {
|
||||
return clone.call();
|
||||
}
|
||||
@@ -409,11 +406,11 @@ public class JGitEnvironmentRepository extends AbstractScmEnvironmentRepository
|
||||
}
|
||||
|
||||
private void initialize() {
|
||||
if (getUri().startsWith("file:") && !this.initialized) {
|
||||
if (!this.initialized) {
|
||||
SshSessionFactory.setInstance(new JschConfigSessionFactory() {
|
||||
@Override
|
||||
protected void configure(Host hc, Session session) {
|
||||
session.setConfig("StrictHostKeyChecking", "no");
|
||||
session.setConfig("StrictHostKeyChecking", isStrictHostKeyChecking() ? "yes" : "no");
|
||||
}
|
||||
});
|
||||
this.initialized = true;
|
||||
@@ -421,8 +418,13 @@ public class JGitEnvironmentRepository extends AbstractScmEnvironmentRepository
|
||||
}
|
||||
|
||||
private void setCredentialsProvider(TransportCommand<?, ?> cmd) {
|
||||
cmd.setCredentialsProvider(
|
||||
new UsernamePasswordCredentialsProvider(getUsername(), getPassword()));
|
||||
if (hasText(getUsername())) {
|
||||
cmd.setCredentialsProvider(
|
||||
new UsernamePasswordCredentialsProvider(getUsername(), getPassword()));
|
||||
} else if (hasText(getPassphrase())) {
|
||||
cmd.setCredentialsProvider(
|
||||
new PassphraseCredentialsProvider(getPassphrase()));
|
||||
}
|
||||
}
|
||||
|
||||
private void setTimeout(TransportCommand<?, ?> pull) {
|
||||
|
||||
@@ -64,6 +64,14 @@ public class AbstractScmAccessor implements ResourceLoaderAware {
|
||||
* Password for authentication with remote repository.
|
||||
*/
|
||||
private String password;
|
||||
/**
|
||||
* Passphrase for unlocking your ssh private key.
|
||||
*/
|
||||
private String passphrase;
|
||||
/**
|
||||
* Reject incoming SSH host keys from remote servers not in the known host list.
|
||||
*/
|
||||
private boolean strictHostKeyChecking;
|
||||
/**
|
||||
* Search paths to use within local working copy. By default searches only the root.
|
||||
*/
|
||||
@@ -159,6 +167,22 @@ public class AbstractScmAccessor implements ResourceLoaderAware {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public String getPassphrase() {
|
||||
return passphrase;
|
||||
}
|
||||
|
||||
public void setPassphrase(String passphrase) {
|
||||
this.passphrase = passphrase;
|
||||
}
|
||||
|
||||
public boolean isStrictHostKeyChecking() {
|
||||
return strictHostKeyChecking;
|
||||
}
|
||||
|
||||
public void setStrictHostKeyChecking(boolean strictHostKeyChecking) {
|
||||
this.strictHostKeyChecking = strictHostKeyChecking;
|
||||
}
|
||||
|
||||
protected File getWorkingDirectory() {
|
||||
if (this.uri.startsWith("file:")) {
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.config.server.support;
|
||||
|
||||
import org.eclipse.jgit.errors.UnsupportedCredentialItem;
|
||||
import org.eclipse.jgit.transport.CredentialItem;
|
||||
import org.eclipse.jgit.transport.CredentialsProvider;
|
||||
import org.eclipse.jgit.transport.URIish;
|
||||
|
||||
public class PassphraseCredentialsProvider extends CredentialsProvider {
|
||||
public static final String PROMPT = "Passphrase for";
|
||||
private final String passphrase;
|
||||
|
||||
/**
|
||||
* Initialize the provider with a the ssh passphrase.
|
||||
*
|
||||
* @param passphrase
|
||||
*/
|
||||
public PassphraseCredentialsProvider(String passphrase) {
|
||||
super();
|
||||
this.passphrase = passphrase;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean isInteractive() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean supports(CredentialItem... items) {
|
||||
for (final CredentialItem item : items) {
|
||||
if (item instanceof CredentialItem.StringType && item.getPromptText().startsWith(PROMPT)) {
|
||||
continue;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask for the credential items to be populated with the passphrase.
|
||||
*
|
||||
* @param uri
|
||||
* the URI of the remote resource that needs authentication.
|
||||
* @param items
|
||||
* the items the application requires to complete authentication.
|
||||
* @return {@code true} if the request was successful and values were
|
||||
* supplied; {@code false} if the user canceled the request and did
|
||||
* not supply all requested values.
|
||||
* @throws UnsupportedCredentialItem
|
||||
* if one of the items supplied is not supported.
|
||||
*/
|
||||
@Override
|
||||
public boolean get(URIish uri, CredentialItem... items) throws UnsupportedCredentialItem {
|
||||
for (final CredentialItem item : items) {
|
||||
if (item instanceof CredentialItem.StringType && item.getPromptText().startsWith(PROMPT)) {
|
||||
((CredentialItem.StringType) item).setValue(passphrase);
|
||||
continue;
|
||||
}
|
||||
throw new UnsupportedCredentialItem(uri, item.getClass().getName() + ":" + item.getPromptText());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -483,6 +483,27 @@ public class JGitEnvironmentRepositoryIntegrationTests {
|
||||
return localRef.getObjectId().getName();
|
||||
}
|
||||
|
||||
public void passphrase() throws IOException {
|
||||
String uri = ConfigServerTestUtils.prepareLocalRepo("config-repo");
|
||||
final String passphrase = "thisismypassphrase";
|
||||
this.context = new SpringApplicationBuilder(TestConfiguration.class).web(false)
|
||||
.run("--spring.cloud.config.server.git.uri=" + uri,
|
||||
"--spring.cloud.config.server.git.passphrase=" + passphrase);
|
||||
JGitEnvironmentRepository repository = this.context.getBean(JGitEnvironmentRepository.class);
|
||||
assertThat(repository.getPassphrase(), Matchers.containsString(passphrase));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void strictHostKeyChecking() throws IOException {
|
||||
String uri = ConfigServerTestUtils.prepareLocalRepo("config-repo");
|
||||
final boolean strictHostKeyChecking = true;
|
||||
this.context = new SpringApplicationBuilder(TestConfiguration.class).web(false)
|
||||
.run("--spring.cloud.config.server.git.uri=" + uri,
|
||||
"--spring.cloud.config.server.git.strict-host-key-checking=" + strictHostKeyChecking);
|
||||
JGitEnvironmentRepository repository = this.context.getBean(JGitEnvironmentRepository.class);
|
||||
assertEquals(repository.isStrictHostKeyChecking(), strictHostKeyChecking);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(ConfigServerProperties.class)
|
||||
@Import({ PropertyPlaceholderAutoConfiguration.class,
|
||||
|
||||
@@ -24,6 +24,7 @@ import java.util.List;
|
||||
|
||||
|
||||
import org.eclipse.jgit.api.CheckoutCommand;
|
||||
import com.jcraft.jsch.Session;
|
||||
import org.eclipse.jgit.api.CloneCommand;
|
||||
import org.eclipse.jgit.api.FetchCommand;
|
||||
import org.eclipse.jgit.api.Git;
|
||||
@@ -40,19 +41,34 @@ import org.eclipse.jgit.lib.Ref;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.lib.StoredConfig;
|
||||
import org.eclipse.jgit.transport.FetchResult;
|
||||
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||
import org.eclipse.jgit.transport.CredentialItem;
|
||||
import org.eclipse.jgit.transport.CredentialsProvider;
|
||||
import org.eclipse.jgit.transport.JschConfigSessionFactory;
|
||||
import org.eclipse.jgit.transport.OpenSshConfig;
|
||||
import org.eclipse.jgit.transport.SshSessionFactory;
|
||||
import org.eclipse.jgit.transport.URIish;
|
||||
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
|
||||
import org.eclipse.jgit.util.FS;
|
||||
import org.eclipse.jgit.util.FileUtils;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.springframework.cloud.config.environment.Environment;
|
||||
import org.springframework.cloud.config.server.support.PassphraseCredentialsProvider;
|
||||
import org.springframework.cloud.config.server.test.ConfigServerTestUtils;
|
||||
import org.springframework.core.env.StandardEnvironment;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Mockito.*;
|
||||
@@ -69,6 +85,9 @@ public class JGitEnvironmentRepositoryTests {
|
||||
|
||||
private File basedir = new File("target/config");
|
||||
|
||||
@Rule
|
||||
public final ExpectedException exception = ExpectedException.none();
|
||||
|
||||
@Before
|
||||
public void init() throws Exception {
|
||||
String uri = ConfigServerTestUtils.prepareLocalRepo();
|
||||
@@ -177,6 +196,8 @@ public class JGitEnvironmentRepositoryTests {
|
||||
assertVersion(environment);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void uriWithHostOnly() throws Exception {
|
||||
this.repository.setUri("git://localhost");
|
||||
@@ -517,6 +538,113 @@ public class JGitEnvironmentRepositoryTests {
|
||||
assertFalse("baseDir should be deleted when clone fails", this.basedir.exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void usernamePasswordShouldSetCredentials() throws Exception {
|
||||
Git mockGit = mock(Git.class);
|
||||
MockCloneCommand mockCloneCommand = new MockCloneCommand(mockGit);
|
||||
|
||||
JGitEnvironmentRepository envRepository = new JGitEnvironmentRepository(this.environment);
|
||||
envRepository.setGitFactory(new MockGitFactory(mockGit, mockCloneCommand));
|
||||
envRepository.setUri("git+ssh://git@somegitserver/somegitrepo");
|
||||
envRepository.setBasedir(new File("./mybasedir"));
|
||||
final String username = "someuser";
|
||||
final String password = "mypassword";
|
||||
envRepository.setUsername(username);
|
||||
envRepository.setPassword(password);
|
||||
envRepository.setCloneOnStart(true);
|
||||
envRepository.afterPropertiesSet();
|
||||
|
||||
assertTrue(mockCloneCommand.getCredentialsProvider() instanceof UsernamePasswordCredentialsProvider);
|
||||
|
||||
CredentialsProvider provider = mockCloneCommand.getCredentialsProvider();
|
||||
CredentialItem.Username usernameCredential = new CredentialItem.Username();
|
||||
CredentialItem.Password passwordCredential = new CredentialItem.Password();
|
||||
assertTrue(provider.supports(usernameCredential));
|
||||
assertTrue(provider.supports(passwordCredential));
|
||||
|
||||
provider.get(new URIish(), usernameCredential);
|
||||
assertEquals(usernameCredential.getValue(), username);
|
||||
provider.get(new URIish(), passwordCredential);
|
||||
assertEquals(String.valueOf(passwordCredential.getValue()), password);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void passphraseShouldSetCredentials() throws Exception {
|
||||
final String passphrase = "mypassphrase";
|
||||
Git mockGit = mock(Git.class);
|
||||
MockCloneCommand mockCloneCommand = new MockCloneCommand(mockGit);
|
||||
|
||||
JGitEnvironmentRepository envRepository = new JGitEnvironmentRepository(this.environment);
|
||||
envRepository.setGitFactory(new MockGitFactory(mockGit, mockCloneCommand));
|
||||
envRepository.setUri("git+ssh://git@somegitserver/somegitrepo");
|
||||
envRepository.setBasedir(new File("./mybasedir"));
|
||||
envRepository.setPassphrase(passphrase);
|
||||
envRepository.setCloneOnStart(true);
|
||||
envRepository.afterPropertiesSet();
|
||||
|
||||
assertTrue(mockCloneCommand.hasPassphraseCredentialsProvider());
|
||||
|
||||
CredentialsProvider provider = mockCloneCommand.getCredentialsProvider();
|
||||
assertFalse(provider.isInteractive());
|
||||
|
||||
CredentialItem.StringType stringCredential = new CredentialItem.StringType(PassphraseCredentialsProvider.PROMPT, true);
|
||||
|
||||
assertTrue(provider.supports(stringCredential));
|
||||
provider.get(new URIish(), stringCredential);
|
||||
assertEquals(stringCredential.getValue(), passphrase);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void strictHostKeyCheckShouldCheck() throws Exception {
|
||||
String uri = "git+ssh://git@somegitserver/somegitrepo";
|
||||
SshSessionFactory.setInstance(null);
|
||||
JGitEnvironmentRepository envRepository = new JGitEnvironmentRepository(this.environment);
|
||||
envRepository.setUri(uri);
|
||||
envRepository.setBasedir(new File("./mybasedir"));
|
||||
envRepository.setStrictHostKeyChecking(true);
|
||||
envRepository.setCloneOnStart(true);
|
||||
try {
|
||||
// this will throw but we don't care about connecting.
|
||||
envRepository.afterPropertiesSet();
|
||||
} catch (Exception e) {
|
||||
final OpenSshConfig.Host hc = OpenSshConfig.get(FS.detect()).lookup("github.com");
|
||||
JschConfigSessionFactory factory = (JschConfigSessionFactory) SshSessionFactory.getInstance();
|
||||
// There's no public method that can be used to inspect the ssh configuration, so we'll reflect
|
||||
// the configure method to allow us to check that the config property is set as expected.
|
||||
Method configure = factory.getClass().getDeclaredMethod("configure", OpenSshConfig.Host.class, Session.class );
|
||||
configure.setAccessible(true);
|
||||
Session session = mock(Session.class);
|
||||
ArgumentCaptor<String> keyCaptor = ArgumentCaptor.forClass(String.class);
|
||||
ArgumentCaptor<String> valueCaptor = ArgumentCaptor.forClass(String.class);
|
||||
configure.invoke(factory, hc, session );
|
||||
verify(session).setConfig(keyCaptor.capture(), valueCaptor.capture());
|
||||
configure.setAccessible(false);
|
||||
assertTrue("yes".equals(valueCaptor.getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
class MockCloneCommand extends CloneCommand {
|
||||
private Git mockGit;
|
||||
|
||||
public MockCloneCommand(Git mockGit) {
|
||||
this.mockGit = mockGit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Git call() throws GitAPIException, InvalidRemoteException {
|
||||
return mockGit;
|
||||
}
|
||||
|
||||
public boolean hasPassphraseCredentialsProvider() {
|
||||
return credentialsProvider instanceof PassphraseCredentialsProvider;
|
||||
}
|
||||
|
||||
public CredentialsProvider getCredentialsProvider() {
|
||||
return credentialsProvider;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class MockGitFactory extends JGitEnvironmentRepository.JGitFactory {
|
||||
|
||||
private Git mockGit;
|
||||
|
||||
Reference in New Issue
Block a user