diff --git a/docs/pom.xml b/docs/pom.xml index ff6fb294..a03d8a2b 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -6,7 +6,7 @@ org.springframework.cloud spring-cloud-config - 1.2.3.BUILD-SNAPSHOT + 1.3.0.BUILD-SNAPSHOT pom Spring Cloud Config Docs diff --git a/pom.xml b/pom.xml index 70351dea..86b1f176 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.springframework.cloud spring-cloud-config - 1.2.3.BUILD-SNAPSHOT + 1.3.0.BUILD-SNAPSHOT pom Spring Cloud Config Spring Cloud Config @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-build - 1.2.2.BUILD-SNAPSHOT + 1.3.0.BUILD-SNAPSHOT diff --git a/spring-cloud-config-client/pom.xml b/spring-cloud-config-client/pom.xml index f27b03be..5817b46b 100644 --- a/spring-cloud-config-client/pom.xml +++ b/spring-cloud-config-client/pom.xml @@ -10,7 +10,7 @@ org.springframework.cloud spring-cloud-config - 1.2.3.BUILD-SNAPSHOT + 1.3.0.BUILD-SNAPSHOT .. diff --git a/spring-cloud-config-dependencies/pom.xml b/spring-cloud-config-dependencies/pom.xml index b479ae7b..6e038a8f 100644 --- a/spring-cloud-config-dependencies/pom.xml +++ b/spring-cloud-config-dependencies/pom.xml @@ -5,11 +5,11 @@ spring-cloud-dependencies-parent org.springframework.cloud - 1.2.2.BUILD-SNAPSHOT + 1.3.0.BUILD-SNAPSHOT spring-cloud-config-dependencies - 1.2.3.BUILD-SNAPSHOT + 1.3.0.BUILD-SNAPSHOT pom spring-cloud-config-dependencies Spring Cloud Config Dependencies @@ -35,6 +35,11 @@ spring-cloud-config-monitor ${project.version} + + org.springframework.vault + spring-vault-core + 1.0.0.M1 + org.eclipse.jgit org.eclipse.jgit diff --git a/spring-cloud-config-monitor/pom.xml b/spring-cloud-config-monitor/pom.xml index e9b6dc67..e154b441 100644 --- a/spring-cloud-config-monitor/pom.xml +++ b/spring-cloud-config-monitor/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-config - 1.2.3.BUILD-SNAPSHOT + 1.3.0.BUILD-SNAPSHOT .. spring-cloud-config-monitor diff --git a/spring-cloud-config-monitor/src/main/java/org/springframework/cloud/config/monitor/FileMonitorConfiguration.java b/spring-cloud-config-monitor/src/main/java/org/springframework/cloud/config/monitor/FileMonitorConfiguration.java index 33afb6a2..20fb5393 100644 --- a/spring-cloud-config-monitor/src/main/java/org/springframework/cloud/config/monitor/FileMonitorConfiguration.java +++ b/spring-cloud-config-monitor/src/main/java/org/springframework/cloud/config/monitor/FileMonitorConfiguration.java @@ -39,6 +39,7 @@ import org.springframework.cloud.config.server.environment.NativeEnvironmentRepo import org.springframework.context.ResourceLoaderAware; import org.springframework.context.SmartLifecycle; import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.http.HttpHeaders; @@ -182,11 +183,13 @@ public class FileMonitorConfiguration implements SmartLifecycle, ResourceLoaderA } private Set getFileRepo() { - if (this.scmRepository != null - && this.scmRepository.getUri().startsWith("file:")) { + if (this.scmRepository != null) { try { - return Collections.singleton(Paths.get(this.resourceLoader - .getResource(this.scmRepository.getUri()).getURI())); + + Resource resource = this.resourceLoader.getResource(this.scmRepository.getUri()); + if (resource instanceof FileSystemResource) { + return Collections.singleton(Paths.get(resource.getURI())); + } } catch (IOException e) { log.error("Cannot resolve URI for path: " + this.scmRepository.getUri()); diff --git a/spring-cloud-config-sample/pom.xml b/spring-cloud-config-sample/pom.xml index 5514e1bc..113c6933 100644 --- a/spring-cloud-config-sample/pom.xml +++ b/spring-cloud-config-sample/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-config - 1.2.3.BUILD-SNAPSHOT + 1.3.0.BUILD-SNAPSHOT .. diff --git a/spring-cloud-config-sample/src/test/java/sample/ApplicationTests.java b/spring-cloud-config-sample/src/test/java/sample/ApplicationTests.java index 90cc3eec..91aeb8af 100644 --- a/spring-cloud-config-sample/src/test/java/sample/ApplicationTests.java +++ b/spring-cloud-config-sample/src/test/java/sample/ApplicationTests.java @@ -9,8 +9,8 @@ import org.junit.runner.RunWith; import org.springframework.boot.SpringApplication; import org.springframework.boot.context.embedded.EmbeddedWebApplicationContext; import org.springframework.boot.context.embedded.LocalServerPort; -import org.springframework.boot.test.TestRestTemplate; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.cloud.config.server.test.ConfigServerTestUtils; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.test.context.junit4.SpringRunner; @@ -22,7 +22,8 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen @SpringBootTest(classes = Application.class, // Normally spring.cloud.config.enabled:true is the default but since we have the config // server on the classpath we need to set it explicitly - properties = { "spring.cloud.config.enabled:true" }, webEnvironment = RANDOM_PORT) + properties = { "spring.cloud.config.enabled:true", + "management.security.enabled=false" }, webEnvironment = RANDOM_PORT) public class ApplicationTests { private static int configPort = 0; diff --git a/spring-cloud-config-server/pom.xml b/spring-cloud-config-server/pom.xml index 5b8752ed..311e1bb7 100644 --- a/spring-cloud-config-server/pom.xml +++ b/spring-cloud-config-server/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-config - 1.2.3.BUILD-SNAPSHOT + 1.3.0.BUILD-SNAPSHOT .. @@ -42,6 +42,11 @@ org.springframework.security spring-security-rsa + + org.springframework.vault + spring-vault-core + true + org.eclipse.jgit org.eclipse.jgit diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/VaultEnvironmentRepository.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/VaultEnvironmentRepository.java index e68b26a7..1fd69ecd 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/VaultEnvironmentRepository.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/VaultEnvironmentRepository.java @@ -1,41 +1,62 @@ +/* + * Copyright 2013-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.environment; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Properties; import javax.servlet.http.HttpServletRequest; -import com.fasterxml.jackson.annotation.JsonRawValue; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.databind.JsonNode; import org.hibernate.validator.constraints.NotEmpty; import org.hibernate.validator.constraints.Range; +import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.config.YamlPropertiesFactoryBean; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.cloud.config.environment.Environment; import org.springframework.cloud.config.environment.PropertySource; import org.springframework.core.io.ByteArrayResource; import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; import org.springframework.util.StringUtils; -import org.springframework.web.client.HttpStatusCodeException; +import org.springframework.vault.client.VaultClient; +import org.springframework.vault.client.VaultEndpoint; +import org.springframework.vault.client.VaultException; +import org.springframework.vault.client.VaultResponseEntity; +import org.springframework.vault.support.VaultResponseSupport; +import org.springframework.vault.support.VaultToken; import org.springframework.web.client.RestTemplate; -import com.fasterxml.jackson.annotation.JsonProperty; - import static org.springframework.cloud.config.client.ConfigClientProperties.STATE_HEADER; import static org.springframework.cloud.config.client.ConfigClientProperties.TOKEN_HEADER; /** * @author Spencer Gibb + * @author Mark Paluch */ @ConfigurationProperties("spring.cloud.config.server.vault") -public class VaultEnvironmentRepository extends AbstractOrderedEnvironmentRepository { +public class VaultEnvironmentRepository extends AbstractOrderedEnvironmentRepository + implements InitializingBean{ public static final String VAULT_TOKEN = "X-Vault-Token"; @@ -67,6 +88,7 @@ public class VaultEnvironmentRepository extends AbstractOrderedEnvironmentReposi private HttpServletRequest request; private EnvironmentWatch watch; + private VaultClient client; public VaultEnvironmentRepository(HttpServletRequest request, EnvironmentWatch watch, RestTemplate rest) { this.request = request; @@ -74,6 +96,11 @@ public class VaultEnvironmentRepository extends AbstractOrderedEnvironmentReposi this.rest = rest; } + @Override + public void afterPropertiesSet() { + this.client = new VaultClient(rest, getVaultEndpoint()); + } + @Override public Environment findOne(String application, String profile, String label) { @@ -136,34 +163,47 @@ public class VaultEnvironmentRepository extends AbstractOrderedEnvironmentReposi } String read(String key) { - String url = String.format("%s://%s:%s/v1/{backend}/{key}", this.scheme, - this.host, this.port); - - HttpHeaders headers = new HttpHeaders(); String token = request.getHeader(TOKEN_HEADER); if (!StringUtils.hasLength(token)) { - throw new IllegalArgumentException("Missing required header: "+TOKEN_HEADER); - } - headers.add(VAULT_TOKEN, token); - try { - ResponseEntity response = this.rest.exchange(url, - HttpMethod.GET, new HttpEntity<>(headers), VaultResponse.class, - this.backend, key); - - HttpStatus status = response.getStatusCode(); - if (status == HttpStatus.OK) { - return response.getBody().getData(); - } - } - catch (HttpStatusCodeException e) { - if (e.getStatusCode() == HttpStatus.NOT_FOUND) { - return null; - } - throw e; + throw new IllegalArgumentException("Missing required header: " + TOKEN_HEADER); } - return null; + VaultToken vaultToken = VaultToken.of(token); + VaultResponseEntity response = client.exchange("{backend}/{key}", HttpMethod.GET, + new HttpEntity<>(VaultClient.createHeaders(vaultToken)), VaultResponse.class, getUriVariables(key)); + + HttpStatus status = response.getStatusCode(); + + if (status == HttpStatus.OK) { + + JsonNode data = response.getBody().getData(); + return data != null ? data.toString() : null; + } + + if (status == HttpStatus.NOT_FOUND) { + return null; + } + + throw new VaultException(response.getMessage()); + } + + private Map getUriVariables(String key) { + Map uriVariables = new HashMap<>(2, 1); + uriVariables.put("backend", backend); + uriVariables.put("key", key); + return uriVariables; + } + + private VaultEndpoint getVaultEndpoint() { + + VaultEndpoint vaultEndpoint = new VaultEndpoint(); + + vaultEndpoint.setScheme(scheme); + vaultEndpoint.setHost(host); + vaultEndpoint.setPort(port); + + return vaultEndpoint; } public void setHost(String host) { @@ -195,61 +235,7 @@ public class VaultEnvironmentRepository extends AbstractOrderedEnvironmentReposi super.setOrder(order); } - static class VaultResponse { - private String auth; - - private Object data; - - @JsonProperty("lease_duration") - private long leaseDuration; - - @JsonProperty("lease_id") - private String leaseId; - - private boolean renewable; - - public VaultResponse() { - } - - public String getAuth() { - return auth; - } - - public void setAuth(String auth) { - this.auth = auth; - } - - @JsonRawValue - public String getData() { - return data == null ? null : data.toString(); - } - - public void setData(JsonNode data) { - this.data = data; - } - - public long getLeaseDuration() { - return leaseDuration; - } - - public void setLeaseDuration(long leaseDuration) { - this.leaseDuration = leaseDuration; - } - - public String getLeaseId() { - return leaseId; - } - - public void setLeaseId(String leaseId) { - this.leaseId = leaseId; - } - - public boolean isRenewable() { - return renewable; - } - - public void setRenewable(boolean renewable) { - this.renewable = renewable; - } + @JsonIgnoreProperties(ignoreUnknown = true) + static class VaultResponse extends VaultResponseSupport { } } diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/NativeConfigServerIntegrationTests.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/NativeConfigServerIntegrationTests.java index 084fdee0..0dadce1a 100644 --- a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/NativeConfigServerIntegrationTests.java +++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/NativeConfigServerIntegrationTests.java @@ -7,8 +7,8 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.context.embedded.LocalServerPort; -import org.springframework.boot.test.TestRestTemplate; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.cloud.config.environment.Environment; import org.springframework.cloud.config.server.test.ConfigServerTestUtils; import org.springframework.http.HttpStatus; diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/SubversionConfigServerIntegrationTests.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/SubversionConfigServerIntegrationTests.java index 2e557aee..608fbe26 100644 --- a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/SubversionConfigServerIntegrationTests.java +++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/SubversionConfigServerIntegrationTests.java @@ -22,8 +22,8 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.embedded.LocalServerPort; -import org.springframework.boot.test.TestRestTemplate; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.cloud.config.environment.Environment; import org.springframework.cloud.config.server.environment.SvnKitEnvironmentRepository; import org.springframework.cloud.config.server.test.ConfigServerTestUtils; diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/VaultEnvironmentRepositoryTests.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/VaultEnvironmentRepositoryTests.java index a3962d85..9db71148 100644 --- a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/VaultEnvironmentRepositoryTests.java +++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/VaultEnvironmentRepositoryTests.java @@ -3,9 +3,12 @@ package org.springframework.cloud.config.server.environment; import static org.junit.Assert.assertEquals; import java.io.IOException; +import java.net.URI; import java.util.HashMap; import java.util.Map; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; @@ -16,13 +19,16 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.DefaultUriTemplateHandler; /** * @author Spencer Gibb * @author Ryan Baxter + * @author Mark Paluch */ public class VaultEnvironmentRepositoryTests { + private ObjectMapper objectMapper = new ObjectMapper(); @Before public void init() {} @@ -33,22 +39,22 @@ public class VaultEnvironmentRepositoryTests { configRequest.addHeader("X-CONFIG-TOKEN", "mytoken"); RestTemplate rest = Mockito.mock(RestTemplate.class); ResponseEntity myAppResp = Mockito.mock(ResponseEntity.class); + Mockito.when(rest.getUriTemplateHandler()).thenReturn(new DefaultUriTemplateHandler()); Mockito.when(myAppResp.getStatusCode()).thenReturn(HttpStatus.OK); VaultEnvironmentRepository.VaultResponse myAppVaultResp = Mockito.mock(VaultEnvironmentRepository.VaultResponse.class); - Mockito.when(myAppVaultResp.getData()).thenReturn("{\"foo\":\"bar\"}"); + Mockito.when(myAppVaultResp.getData()).thenReturn(asJsonNode("{\"foo\":\"bar\"}")); Mockito.when(myAppResp.getBody()).thenReturn(myAppVaultResp); - Mockito.when(rest.exchange(Mockito.eq("http://127.0.0.1:8200/v1/{backend}/{key}"), - Mockito.eq(HttpMethod.GET), Mockito.any(HttpEntity.class), Mockito.eq(VaultEnvironmentRepository.VaultResponse.class), - Mockito.eq("secret"), Mockito.eq("myapp"))).thenReturn(myAppResp); + Mockito.when(rest.exchange(Mockito.eq(URI.create("http://127.0.0.1:8200/v1/secret/myapp")), + Mockito.eq(HttpMethod.GET), Mockito.any(HttpEntity.class), Mockito.eq(VaultEnvironmentRepository.VaultResponse.class))).thenReturn(myAppResp); ResponseEntity appResp = Mockito.mock(ResponseEntity.class); Mockito.when(appResp.getStatusCode()).thenReturn(HttpStatus.OK); VaultEnvironmentRepository.VaultResponse appVaultResp = Mockito.mock(VaultEnvironmentRepository.VaultResponse.class); Mockito.when(appVaultResp.getData()).thenReturn(null); Mockito.when(appResp.getBody()).thenReturn(appVaultResp); - Mockito.when(rest.exchange(Mockito.eq("http://127.0.0.1:8200/v1/{backend}/{key}"), - Mockito.eq(HttpMethod.GET), Mockito.any(HttpEntity.class), Mockito.eq(VaultEnvironmentRepository.VaultResponse.class), - Mockito.eq("secret"), Mockito.eq("application"))).thenReturn(appResp); + Mockito.when(rest.exchange(Mockito.eq(URI.create("http://127.0.0.1:8200/v1/secret/application")), + Mockito.eq(HttpMethod.GET), Mockito.any(HttpEntity.class), Mockito.eq(VaultEnvironmentRepository.VaultResponse.class))).thenReturn(appResp); VaultEnvironmentRepository repo = new VaultEnvironmentRepository(configRequest, new EnvironmentWatch.Default(), rest); + repo.afterPropertiesSet(); Environment e = repo.findOne("myapp", null, null); assertEquals("myapp", e.getName()); Map result = new HashMap(); @@ -61,14 +67,24 @@ public class VaultEnvironmentRepositoryTests { MockHttpServletRequest configRequest = new MockHttpServletRequest(); RestTemplate rest = Mockito.mock(RestTemplate.class); ResponseEntity myAppResp = Mockito.mock(ResponseEntity.class); + Mockito.when(rest.getUriTemplateHandler()).thenReturn(new DefaultUriTemplateHandler()); Mockito.when(myAppResp.getStatusCode()).thenReturn(HttpStatus.OK); VaultEnvironmentRepository.VaultResponse myAppVaultResp = Mockito.mock(VaultEnvironmentRepository.VaultResponse.class); - Mockito.when(myAppVaultResp.getData()).thenReturn("{\"foo\":\"bar\"}"); + Mockito.when(myAppVaultResp.getData()).thenReturn(asJsonNode("{\"foo\":\"bar\"}")); Mockito.when(myAppResp.getBody()).thenReturn(myAppVaultResp); Mockito.when(rest.exchange(Mockito.eq("http://127.0.0.1:8200/v1/{backend}/{key}"), Mockito.eq(HttpMethod.GET), Mockito.any(HttpEntity.class), Mockito.eq(VaultEnvironmentRepository.VaultResponse.class), Mockito.eq("secret"), Mockito.eq("myapp"))).thenReturn(myAppResp); VaultEnvironmentRepository repo = new VaultEnvironmentRepository(configRequest, new EnvironmentWatch.Default(), rest); + repo.afterPropertiesSet(); repo.findOne("myapp", null, null); } + + private JsonNode asJsonNode(String content) { + try { + return objectMapper.readTree(content); + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + } } diff --git a/spring-cloud-starter-config/pom.xml b/spring-cloud-starter-config/pom.xml index 9760a1c1..1a379652 100644 --- a/spring-cloud-starter-config/pom.xml +++ b/spring-cloud-starter-config/pom.xml @@ -5,10 +5,10 @@ org.springframework.cloud spring-cloud-config - 1.2.3.BUILD-SNAPSHOT + 1.3.0.BUILD-SNAPSHOT spring-cloud-starter-config - 1.2.3.BUILD-SNAPSHOT + 1.3.0.BUILD-SNAPSHOT spring-cloud-starter-config Spring Cloud Starter https://projects.spring.io/spring-cloud