initial commit

This commit is contained in:
Spencer Gibb
2016-02-10 17:25:39 -07:00
commit 9abd836d74
16 changed files with 1058 additions and 0 deletions

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2013-2015 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.vault;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author Spencer Gibb
*/
@Configuration
@EnableConfigurationProperties
@ConditionalOnProperty(name = "spring.cloud.vault.enabled", matchIfMissing = true)
public class VaultBootstrapConfiguration {
@Bean
public VaultClient vaultClient() {
return new VaultClient(vaultProperties());
}
@Bean
public VaultProperties vaultProperties() {
return new VaultProperties();
}
@Bean
public VaultPropertySourceLocator vaultPropertySourceLocator() {
return new VaultPropertySourceLocator(vaultClient(), vaultProperties());
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2013-2015 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.vault;
import java.util.Collections;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
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.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestTemplate;
/**
* @author Spencer Gibb
*/
@RequiredArgsConstructor
public class VaultClient {
public static final String VAULT_TOKEN = "X-Vault-Token";
// protected static final ParameterizedTypeReference<Map<String, String>> typeRef = new ParameterizedTypeReference<Map<String, String>>() { };
@Setter
private RestTemplate rest = new RestTemplate();
private final VaultProperties properties;
public Map<String, String> read(String key) {
String url = String.format("%s://%s:%s/v1/{backend}/{key}",
this.properties.getScheme(), this.properties.getHost(), this.properties.getPort());
HttpHeaders headers = new HttpHeaders();
headers.add(VAULT_TOKEN, this.properties.getToken());
try {
ResponseEntity<VaultResponse> response = this.rest.exchange(url, HttpMethod.GET,
new HttpEntity<>(headers), VaultResponse.class, this.properties.getBackend(), 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;
}
return Collections.emptyMap();
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2013-2015 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.vault;
import lombok.Data;
import org.hibernate.validator.constraints.NotEmpty;
import org.hibernate.validator.constraints.Range;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author Spencer Gibb
*/
@ConfigurationProperties("spring.cloud.vault")
@Data
public class VaultProperties {
private boolean enabled = true;
@NotEmpty
private String host = "127.0.0.1";
@Range(min = 1, max = 65535)
private int port = 8200;
private String scheme = "http";
@NotEmpty
private String backend = "secret";
@NotEmpty
private String defaultContext = "application";
@NotEmpty
private String profileSeparator = ",";
@NotEmpty
private String token;
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright 2013-2015 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.vault;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import lombok.extern.apachecommons.CommonsLog;
import org.springframework.core.env.EnumerablePropertySource;
/**
* @author Spencer Gibb
*/
@CommonsLog
public class VaultPropertySource extends EnumerablePropertySource<VaultClient> {
private String context;
private Map<String, String> properties = new LinkedHashMap<>();
public VaultPropertySource(String context, VaultClient source) {
super(context, source);
this.context = context;
}
public void init() {
try {
Map<String, String> values = this.source.read(this.context);
if (values != null) {
this.properties.putAll(values);
}
} catch (Exception e) {
log.error("Unable to read properties from vault for key "+this.context, e);
}
}
@Override
public Object getProperty(String name) {
return this.properties.get(name);
}
@Override
public String[] getPropertyNames() {
Set<String> strings = this.properties.keySet();
return strings.toArray(new String[strings.size()]);
}
}

View File

@@ -0,0 +1,86 @@
/*
* Copyright 2013-2015 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.vault;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.springframework.cloud.bootstrap.config.PropertySourceLocator;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;
/**
* @author Spencer Gibb
*/
public class VaultPropertySourceLocator implements PropertySourceLocator {
private VaultClient vault;
private VaultProperties properties;
public VaultPropertySourceLocator(VaultClient vault, VaultProperties properties) {
this.vault = vault;
this.properties = properties;
}
@Override
public PropertySource<?> locate(Environment environment) {
if (environment instanceof ConfigurableEnvironment) {
ConfigurableEnvironment env = (ConfigurableEnvironment) environment;
String appName = env.getProperty("spring.application.name");
List<String> profiles = Arrays.asList(env.getActiveProfiles());
List<String> contexts = new ArrayList<>();
String defaultContext = this.properties.getDefaultContext();
contexts.add(defaultContext);
addProfiles(contexts, defaultContext, profiles);
String baseContext = appName;
contexts.add(baseContext);
addProfiles(contexts, baseContext, profiles);
Collections.reverse(contexts);
CompositePropertySource composite = new CompositePropertySource("vault");
for (String propertySourceContext : contexts) {
VaultPropertySource propertySource = create(propertySourceContext);
propertySource.init();
composite.addPropertySource(propertySource);
}
return composite;
}
return null;
}
private VaultPropertySource create(String context) {
return new VaultPropertySource(context, this.vault);
}
private void addProfiles(List<String> contexts, String baseContext,
List<String> profiles) {
for (String profile : profiles) {
contexts.add(baseContext + this.properties.getProfileSeparator() + profile);
}
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2013-2015 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.vault;
import java.util.Map;
import lombok.Data;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* @author Spencer Gibb
*/
@Data
public class VaultResponse {
private String auth;
private Map<String, String> data;
@JsonProperty("lease_duration")
private long leaseDuration;
@JsonProperty("lease_id")
private String leaseId;
private boolean renewable;
}

View File

@@ -0,0 +1,3 @@
# Bootstrap Configuration
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.vault.VaultBootstrapConfiguration

View File

@@ -0,0 +1,27 @@
package org.springframework.cloud.vault;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.boot.test.WebIntegrationTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = VaultTests.TestApplication.class)
@WebIntegrationTest()
public class VaultTests {
@Test
public void contextLoads() {
}
@SpringBootApplication
public static class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}
}

View File

@@ -0,0 +1,3 @@
spring:
application.name: testVaultApp
cloud.vault.token: token