diff --git a/docs/src/main/asciidoc/spring-cloud-config.adoc b/docs/src/main/asciidoc/spring-cloud-config.adoc index f4ab6f8a..f9f19e85 100644 --- a/docs/src/main/asciidoc/spring-cloud-config.adoc +++ b/docs/src/main/asciidoc/spring-cloud-config.adoc @@ -429,6 +429,12 @@ Example: |*strictHostKeyChecking* |`true` or `false`. If false, ignore errors with host key + +|*knownHostsFile* +|Location of custom .known_hosts file + +|*preferredAuthentications* +|Override server authentication method order. This should allow evade login prompts if server has keyboard-interactive authentication before `publickey` method. |=== diff --git a/spring-cloud-config-dependencies/pom.xml b/spring-cloud-config-dependencies/pom.xml index edb05cfa..c972fe52 100644 --- a/spring-cloud-config-dependencies/pom.xml +++ b/spring-cloud-config-dependencies/pom.xml @@ -38,7 +38,8 @@ org.eclipse.jgit org.eclipse.jgit - 4.6.0.201612231935-r + + 3.6.1.201501031845-r org.tmatesoft.svnkit diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/JGitEnvironmentRepository.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/JGitEnvironmentRepository.java index f71b0ad4..1e7c33c5 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/JGitEnvironmentRepository.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/JGitEnvironmentRepository.java @@ -210,7 +210,7 @@ public class JGitEnvironmentRepository extends AbstractScmEnvironmentRepository checkout(git, label); } // always return what is currently HEAD as the version - return git.getRepository().findRef("HEAD").getObjectId().getName(); + return git.getRepository().getRef("HEAD").getObjectId().getName(); } catch (RefNotFoundException e) { throw new NoSuchLabelException("No such label: " + label, e); @@ -338,7 +338,7 @@ public class JGitEnvironmentRepository extends AbstractScmEnvironmentRepository private MergeResult merge(Git git, String label) { try { MergeCommand merge = git.merge(); - merge.include(git.getRepository().findRef("origin/" + label)); + merge.include(git.getRepository().getRef("origin/" + label)); MergeResult result = merge.call(); if (!result.getMergeStatus().isSuccessful()) { this.logger.warn("Merged from remote " + label + " with result " diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/KnownHostsFileIsValid.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/KnownHostsFileIsValid.java new file mode 100644 index 00000000..f11af222 --- /dev/null +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/KnownHostsFileIsValid.java @@ -0,0 +1,40 @@ +/* + * Copyright 2017 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.ssh; + +import org.springframework.validation.annotation.Validated; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Beans annotated with {@link KnownHostsFileIsValid} and {@link Validated} will have the constraints applied. + * + * @author Edgars Jasmans + **/ +@Constraint(validatedBy = KnownHostsFileValidator.class) +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface KnownHostsFileIsValid { + String message() default "{KnownHostsFileIsValid.message}"; + Class[] groups() default {}; + Class[] payload() default {}; +} diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/KnownHostsFileValidator.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/KnownHostsFileValidator.java new file mode 100644 index 00000000..281cf37c --- /dev/null +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/KnownHostsFileValidator.java @@ -0,0 +1,54 @@ +/* + * Copyright 2017 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.ssh; + +import org.springframework.validation.annotation.Validated; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import java.io.File; + +import static java.lang.String.*; + +/** + * JSR-303 Cross Field validator that ensures that a {@link SshUriProperties} bean for the constraints: + * - Verifies that known hosts file exists + *

+ * Beans annotated with {@link KnownHostsFileIsValid} and {@link Validated} will have the constraints applied. + * + * @author Edgars Jasmans + */ +public class KnownHostsFileValidator implements ConstraintValidator { + + @Override + public void initialize(KnownHostsFileIsValid knownHostsFileIsValid) { + // No initialization required + } + + @Override + public boolean isValid(SshUriProperties sshUriProperties, ConstraintValidatorContext context) { + String knownHostsFile = sshUriProperties.getKnownHostsFile(); + if (knownHostsFile != null && !new File(knownHostsFile).exists()) { + context.disableDefaultConstraintViolation(); + context.buildConstraintViolationWithTemplate( + format("File '%s' specified in property 'spring.cloud.config.server.git.knownHostsFile' could not be located", knownHostsFile)) + .addConstraintViolation(); + return false; + } + return true; + } +} diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/PrivateKeyValidator.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/PrivateKeyValidator.java index a3632538..cd95a705 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/PrivateKeyValidator.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/PrivateKeyValidator.java @@ -32,7 +32,7 @@ import static org.springframework.cloud.config.server.ssh.SshPropertyValidator.i import static org.springframework.util.StringUtils.hasText; /** - * JSR-303 Cross Field validator that ensures that a {@link SshUriProperties} bean for the constraints: + * JSR-303 Cross Field validator that ensures that an {@link SshUriProperties} bean for the constraints: * - Private key is present and can be correctly parsed using {@link com.jcraft.jsch.KeyPair} * * Beans annotated with {@link PrivateKeyValidator} and {@link Validated} will have the constraints applied. @@ -67,9 +67,9 @@ public class PrivateKeyValidator implements ConstraintValidator repos = new LinkedHashMap<>(); @@ -106,6 +151,16 @@ public abstract class SshUri { return this; } + public SshUri.SshUriPropertiesBuilder knownHostsFile(String knownHostsFile) { + this.knownHostsFile = knownHostsFile; + return this; + } + + public SshUri.SshUriPropertiesBuilder preferredAuthentications(String preferredAuthentications) { + this.preferredAuthentications = preferredAuthentications; + return this; + } + public SshUri.SshUriPropertiesBuilder ignoreLocalSshSettings(boolean ignoreLocalSshSettings) { this.ignoreLocalSshSettings = ignoreLocalSshSettings; return this; @@ -139,12 +194,22 @@ public abstract class SshUri { sshUriNestedRepoProperties.setHostKeyAlgorithm(hostKeyAlgorithm); sshUriNestedRepoProperties.setHostKey(hostKey); sshUriNestedRepoProperties.setPrivateKey(privateKey); + sshUriNestedRepoProperties.setKnownHostsFile(knownHostsFile); + sshUriNestedRepoProperties.setPreferredAuthentications(preferredAuthentications); sshUriNestedRepoProperties.setIgnoreLocalSshSettings(ignoreLocalSshSettings); sshUriNestedRepoProperties.setStrictHostKeyChecking(strictHostKeyChecking); } public String toString() { - return "org.springframework.cloud.config.server.ssh.SshUriProperties.SshUriPropertiesBuilder(uri=" + this.uri + "hostKeyAlgorithm=" + this.hostKeyAlgorithm + ", hostKey=" + this.hostKey + ", privateKey=" + this.privateKey + ", ignoreLocalSshSettings=" + this.ignoreLocalSshSettings + ", strictHostKeyChecking=" + this.strictHostKeyChecking + ", repos=" + this.repos + ")"; + return "org.springframework.cloud.config.server.ssh.SshUriProperties.SshUriPropertiesBuilder(uri=" + this.uri + + "hostKeyAlgorithm=" + this.hostKeyAlgorithm + + ", hostKey=" + this.hostKey + + ", privateKey=" + this.privateKey + + ", knownHostsFile=" + this.knownHostsFile + + ", preferredAuthentications=" + this.preferredAuthentications + + ", ignoreLocalSshSettings=" + this.ignoreLocalSshSettings + + ", strictHostKeyChecking=" + this.strictHostKeyChecking + + ", repos=" + this.repos + ")"; } } } diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/SshUriProperties.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/SshUriProperties.java index c5a25ddb..7aa9879d 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/SshUriProperties.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/SshUriProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015 - 2017 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. @@ -31,6 +31,7 @@ import org.springframework.validation.annotation.Validated; @PrivateKeyIsValid @HostKeyAndAlgoBothExist @HostKeyAlgoSupported +@KnownHostsFileIsValid public class SshUriProperties extends SshUri { private Map repos = new LinkedHashMap<>(); diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/JGitEnvironmentRepositoryTests.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/JGitEnvironmentRepositoryTests.java index 8996f0c2..4e68a384 100644 --- a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/JGitEnvironmentRepositoryTests.java +++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/JGitEnvironmentRepositoryTests.java @@ -379,7 +379,7 @@ public class JGitEnvironmentRepositoryTests { // refresh()->return // git.getRepository().getRef("HEAD").getObjectId().getName(); Ref headRef = mock(Ref.class); - when(repository.findRef(anyString())).thenReturn(headRef); + when(repository.getRef(anyString())).thenReturn(headRef); ObjectId newObjectId = ObjectId.fromRaw(new int[] { 1, 2, 3, 4, 5 }); when(headRef.getObjectId()).thenReturn(newObjectId); @@ -435,7 +435,7 @@ public class JGitEnvironmentRepositoryTests { //refresh()->return git.getRepository().getRef("HEAD").getObjectId().getName(); Ref headRef = mock(Ref.class); - when(repository.findRef(anyString())).thenReturn(headRef); + when(repository.getRef(anyString())).thenReturn(headRef); ObjectId newObjectId = ObjectId.fromRaw(new int[]{1,2,3,4,5}); when(headRef.getObjectId()).thenReturn(newObjectId); @@ -503,7 +503,7 @@ public class JGitEnvironmentRepositoryTests { // refresh()->return // git.getRepository().getRef("HEAD").getObjectId().getName(); Ref headRef = mock(Ref.class); - when(repository.findRef(anyString())).thenReturn(headRef); + when(repository.getRef(anyString())).thenReturn(headRef); ObjectId newObjectId = ObjectId.fromRaw(new int[] { 1, 2, 3, 4, 5 }); when(headRef.getObjectId()).thenReturn(newObjectId); diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/ssh/PropertyBasedSshSessionFactoryTest.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/ssh/PropertyBasedSshSessionFactoryTest.java index c9c10f19..71736e55 100644 --- a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/ssh/PropertyBasedSshSessionFactoryTest.java +++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/ssh/PropertyBasedSshSessionFactoryTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015 - 2017 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. @@ -24,6 +24,7 @@ import java.util.Map; import org.eclipse.jgit.transport.OpenSshConfig.Host; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -137,6 +138,37 @@ public class PropertyBasedSshSessionFactoryTest { Assert.assertEquals(HOST_KEY, hostKey.getKey()); } + @Test + public void preferredAuthenticationsIsSpecified() { + SshUri sshKey = new SshUriProperties.SshUriPropertiesBuilder() + .uri("ssh://gitlab.example.local:3322/somerepo.git") + .privateKey(PRIVATE_KEY) + .preferredAuthentications("password,keyboard-interactive") + .build(); + setupSessionFactory(sshKey); + + factory.configure(hc, session); + verify(session).setConfig("PreferredAuthentications", "password,keyboard-interactive"); + verify(session).setConfig("StrictHostKeyChecking", "no"); + verifyNoMoreInteractions(session); + } + + @Test + public void customKnownHostsFileIsUsed() throws Exception { + SshUri sshKey = new SshUriProperties.SshUriPropertiesBuilder() + .uri("git@gitlab.example.local:someorg/somerepo.git") + .privateKey(PRIVATE_KEY) + .knownHostsFile("/ssh/known_hosts") + .build(); + setupSessionFactory(sshKey); + + factory.createSession(hc, null, SshUriPropertyProcessor.getHostname(sshKey.getUri()), 22, null); + ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); + + verify(jSch).setKnownHosts(captor.capture()); + Assert.assertEquals("/ssh/known_hosts", captor.getValue()); + } + private void setupSessionFactory(SshUri sshKey) { Map sshKeysByHostname = new HashMap<>(); sshKeysByHostname.put(SshUriPropertyProcessor.getHostname(sshKey.getUri()), sshKey); diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/ssh/SshPropertyValidatorTest.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/ssh/SshPropertyValidatorTest.java index 154c139a..942f96ec 100644 --- a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/ssh/SshPropertyValidatorTest.java +++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/ssh/SshPropertyValidatorTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015 - 2017 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. @@ -185,4 +185,25 @@ public class SshPropertyValidatorTest { assertThat(constraintViolations, hasSize(0)); } + + @Test + public void preferredAuthenticationsIsValidated() throws Exception { + SshUriProperties sshUriProperties = new SshUriProperties(); + assertThat(validator.validate(sshUriProperties), hasSize(0)); + + sshUriProperties.setPreferredAuthentications("keyboard-interactive, public-key ,kerberos"); + assertThat(validator.validate(sshUriProperties), hasSize(0)); + + sshUriProperties.setPreferredAuthentications(",,"); + assertThat(validator.validate(sshUriProperties), hasSize(1)); + } + + @Test + public void knowHostsFileIsValidated() throws Exception { + SshUriProperties sshUriProperties = new SshUriProperties(); + assertThat(validator.validate(sshUriProperties), hasSize(0)); + + sshUriProperties.setKnownHostsFile("non-existing.file"); + assertThat(validator.validate(sshUriProperties), hasSize(1)); + } } \ No newline at end of file