Make text encryptor configuration an autoconfig

So that it can be controlled with @ConditionalOnBean etc.
This commit is contained in:
Dave Syer
2015-05-26 16:54:57 +01:00
parent 7437cdef49
commit 2a0f73740a
14 changed files with 116 additions and 66 deletions

View File

@@ -11,7 +11,7 @@ public class ConfigServerApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(ConfigServerApplication.class).properties(
"spring.config.name=configserver").run(args);
"spring.config.name=configserver", "debug=true").run(args);
}
}

View File

@@ -37,7 +37,7 @@ import org.springframework.util.StringUtils;
public class ConfigServerBootstrapConfiguration {
@ConditionalOnProperty("spring.cloud.config.server.bootstrap")
@Import(ConfigServerConfiguration.class)
@Import(EnvironmentRepositoryConfiguration.class)
protected static class LocalPropertySourceLocatorConfiguration {
@Autowired

View File

@@ -35,7 +35,7 @@ public class ConfigServerMvcConfiguration {
@Autowired
private ConfigServerProperties server;
@Autowired
@Autowired(required=false)
private EnvironmentEncryptor environmentEncryptor;
@Bean

View File

@@ -31,9 +31,8 @@ import org.springframework.context.annotation.Import;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({ConfigServerConfiguration.class,
ConfigServerMvcConfiguration.class,
ConfigServerEncryptionConfiguration.class})
@Import({ EnvironmentRepositoryConfiguration.class,
ConfigServerEncryptionConfiguration.class, ConfigServerMvcConfiguration.class })
public @interface EnableConfigServer {
}

View File

@@ -25,7 +25,6 @@ import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.config.server.encryption.SingleTextEncryptorLocator;
import org.springframework.cloud.config.server.encryption.TextEncryptorLocator;
import org.springframework.cloud.context.encrypt.EncryptorFactory;
@@ -66,7 +65,7 @@ public class EncryptionController {
private final ConfigServerProperties properties;
public EncryptionController(TextEncryptorLocator encryptorLocator,
ConfigServerProperties configServerProperties) {
ConfigServerProperties configServerProperties) {
this.encryptorLocator = encryptorLocator;
this.properties = configServerProperties;
}
@@ -105,15 +104,16 @@ public class EncryptionController {
}
@RequestMapping(value = "/key", method = RequestMethod.POST, params = { "!password" })
@RequestMapping(value = "/key", method = RequestMethod.POST, params = { "!password" })
public ResponseEntity<Map<String, Object>> uploadKey(@RequestBody String data,
@RequestHeader("Content-Type") MediaType type) {
Map<String, Object> body = new HashMap<String, Object>();
body.put("status", "OK");
TextEncryptor encryptor = new EncryptorFactory().create(stripFormData(data, type, false));
updateEncryptor(encryptor);
TextEncryptor encryptor = new EncryptorFactory().create(stripFormData(data, type,
false));
updateEncryptor(encryptor);
if (encryptor instanceof RsaKeyHolder) {
body.put("publicKey", ((RsaKeyHolder) encryptor).getPublicKey());
}
@@ -127,7 +127,8 @@ public class EncryptionController {
private void updateEncryptor(TextEncryptor encryptor) {
if (encryptorLocator instanceof SingleTextEncryptorLocator) {
((SingleTextEncryptorLocator) encryptorLocator).setEncryptor(encryptor);
} else {
}
else {
throw new IncompatibleTextEncryptorLocatorException();
}
}
@@ -157,8 +158,7 @@ public class EncryptionController {
}
@RequestMapping(value = "encrypt", method = RequestMethod.POST)
public String encrypt(
@RequestBody String data,
public String encrypt(@RequestBody String data,
@RequestHeader("Content-Type") MediaType type) {
return encrypt(properties.getDefaultApplicationName(),
@@ -166,25 +166,23 @@ public class EncryptionController {
}
@RequestMapping(value = "/encrypt/{name}/{profiles}", method = RequestMethod.POST)
public String encrypt(
@PathVariable String name,
@PathVariable String profiles,
@RequestBody String data,
@RequestHeader("Content-Type") MediaType type) {
public String encrypt(@PathVariable String name, @PathVariable String profiles,
@RequestBody String data, @RequestHeader("Content-Type") MediaType type) {
try {
TextEncryptor encryptor = checkEncryptorInstalled(encryptorLocator.locate(name, profiles));
TextEncryptor encryptor = checkEncryptorInstalled(encryptorLocator.locate(
name, profiles));
String encrypted = encryptor.encrypt(stripFormData(data, type, false));
logger.info("Encrypted data");
return encrypted;
} catch (IllegalArgumentException e) {
}
catch (IllegalArgumentException e) {
throw new InvalidCipherException();
}
}
@RequestMapping(value = "decrypt", method = RequestMethod.POST)
public String decrypt(
@RequestBody String data,
public String decrypt(@RequestBody String data,
@RequestHeader("Content-Type") MediaType type) {
return decrypt(properties.getDefaultApplicationName(),
@@ -192,24 +190,23 @@ public class EncryptionController {
}
@RequestMapping(value = "/decrypt/{name}/{profiles}", method = RequestMethod.POST)
public String decrypt(
@PathVariable String name,
@PathVariable String profiles,
@RequestBody String data,
@RequestHeader("Content-Type") MediaType type) {
public String decrypt(@PathVariable String name, @PathVariable String profiles,
@RequestBody String data, @RequestHeader("Content-Type") MediaType type) {
try {
TextEncryptor encryptor = checkEncryptorInstalled(encryptorLocator.locate(name, profiles));
TextEncryptor encryptor = checkEncryptorInstalled(encryptorLocator.locate(
name, profiles));
String decrypted = encryptor.decrypt(stripFormData(data, type, true));
logger.info("Decrypted cipher data");
return decrypted;
} catch (IllegalArgumentException e) {
}
catch (IllegalArgumentException e) {
throw new InvalidCipherException();
}
}
private TextEncryptor checkEncryptorInstalled(TextEncryptor encryptor) {
if (encryptor == null) {
if (encryptor == null || encryptor.encrypt("FOO").equals("FOO")) {
throw new KeyNotInstalledException();
}
return encryptor;
@@ -217,7 +214,7 @@ public class EncryptionController {
private TextEncryptor locateDefaultTextEncryptor() {
return encryptorLocator.locate(properties.getDefaultApplicationName(),
properties.getDefaultProfile());
properties.getDefaultProfile());
}
private String stripFormData(String data, MediaType type, boolean cipher) {
@@ -232,19 +229,20 @@ public class EncryptionController {
catch (UnsupportedEncodingException e) {
// Really?
}
String candidate = data.substring(0, data.length()-1);
String candidate = data.substring(0, data.length() - 1);
if (cipher) {
if (data.endsWith("=")) {
if (data.length()/2!=(data.length()+1)/2) {
try {
Hex.decode(candidate);
return candidate;
} catch (IllegalArgumentException e) {
if (Base64.isBase64(data.getBytes())) {
return data;
}
}
}
if (data.length() / 2 != (data.length() + 1) / 2) {
try {
Hex.decode(candidate);
return candidate;
}
catch (IllegalArgumentException e) {
if (Base64.isBase64(data.getBytes())) {
return data;
}
}
}
}
return data;
}

View File

@@ -72,7 +72,8 @@ public class EnvironmentController {
private boolean stripDocument = true;
public EnvironmentController(EnvironmentRepository repository, EnvironmentEncryptor environmentEncryptor) {
public EnvironmentController(EnvironmentRepository repository,
EnvironmentEncryptor environmentEncryptor) {
super();
this.repository = repository;
this.defaultLabel = repository.getDefaultLabel();
@@ -97,8 +98,10 @@ public class EnvironmentController {
@RequestMapping("/{name}/{profiles}/{label:.*}")
public Environment labelled(@PathVariable String name, @PathVariable String profiles,
@PathVariable String label) {
Environment environment = environmentEncryptor.decrypt(repository.findOne(name, profiles,
label));
Environment environment = repository.findOne(name, profiles, label);
if (environmentEncryptor != null) {
environment = environmentEncryptor.decrypt(environment);
}
if (!overrides.isEmpty()) {
environment.addFirst(new PropertySource("overrides", overrides));
}
@@ -163,7 +166,8 @@ public class EnvironmentController {
Object value = result.get("document");
if (value instanceof Collection) {
return getSuccess(new Yaml().dumpAs(value, Tag.SEQ, FlowStyle.BLOCK));
} else {
}
else {
return getSuccess(new Yaml().dumpAs(value, Tag.STR, FlowStyle.BLOCK));
}
}

View File

@@ -31,7 +31,7 @@ import org.springframework.core.env.ConfigurableEnvironment;
@Configuration
@ConditionalOnMissingBean(EnvironmentRepository.class)
@EnableConfigurationProperties(ConfigServerProperties.class)
public class ConfigServerConfiguration {
public class EnvironmentRepositoryConfiguration {
@Bean
@ConditionalOnProperty(value = "spring.cloud.config.server.health.enabled", matchIfMissing = true)

View File

@@ -17,12 +17,10 @@
package org.springframework.cloud.config.server.encryption;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.config.server.ConfigServerProperties;
import org.springframework.cloud.config.server.EncryptionController;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.encrypt.TextEncryptor;
/**
* @author Bartosz Wojtkiewicz
@@ -32,28 +30,20 @@ import org.springframework.security.crypto.encrypt.TextEncryptor;
@Configuration
public class ConfigServerEncryptionConfiguration {
@Autowired(required = false)
private TextEncryptor encryptor;
@Autowired
private TextEncryptorLocator locator;
@Autowired
private ConfigServerProperties properties;
@Bean
public EncryptionController encryptionController() {
return new EncryptionController(locator, properties);
}
@Bean
public EnvironmentEncryptor environmentEncryptor() {
return new CipherEnvironmentEncryptor(locator);
}
@Bean
@ConditionalOnMissingBean
public TextEncryptorLocator textEncryptorLocator() {
return new SingleTextEncryptorLocator(encryptor);
public EncryptionController encryptionController() {
return new EncryptionController(locator, properties);
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2002-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.config.server.encryption;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.encrypt.Encryptors;
import org.springframework.security.crypto.encrypt.TextEncryptor;
/**
* @author Bartosz Wojtkiewicz
* @author Rafal Zukowski
*
*/
@Configuration
public class EncryptionAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public TextEncryptorLocator textEncryptorLocator(TextEncryptor encryptor) {
return new SingleTextEncryptorLocator(encryptor);
}
@ConditionalOnMissingBean(TextEncryptor.class)
protected static class DefaultTextEncryptorConfiguration {
@Bean
public TextEncryptor nullTextEncryptor() {
return Encryptors.noOpText();
}
}
}

View File

@@ -5,3 +5,7 @@ org.springframework.cloud.config.server.ConfigServerBootstrapConfiguration
# Application listeners
org.springframework.context.ApplicationListener=\
org.springframework.cloud.config.server.ConfigServerBootstrapApplicationListener
# Autoconfiguration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.config.server.encryption.EncryptionAutoConfiguration

View File

@@ -16,10 +16,10 @@
package org.springframework.cloud.config.server;
import org.junit.Test;
import org.springframework.cloud.config.server.encryption.SingleTextEncryptorLocator;
import org.springframework.cloud.context.encrypt.KeyFormatException;
import org.springframework.http.MediaType;
import org.springframework.security.crypto.encrypt.Encryptors;
import org.springframework.security.rsa.crypto.RsaSecretEncryptor;
import static org.junit.Assert.assertEquals;
@@ -41,6 +41,12 @@ public class EncryptionControllerTests {
controller.decrypt("foo", MediaType.TEXT_PLAIN);
}
@Test(expected = KeyNotInstalledException.class)
public void cannotDecryptWithNoopEncryptor() {
textEncryptorLocator.setEncryptor(Encryptors.noOpText());
controller.decrypt("foo", MediaType.TEXT_PLAIN);
}
@Test(expected = KeyFormatException.class)
public void cannotUploadPublicKey() {
controller.uploadKey("ssh-rsa ...", MediaType.TEXT_PLAIN);

View File

@@ -132,7 +132,7 @@ public class JGitEnvironmentRepositoryIntegrationTests {
}
@Configuration
@Import({ PropertyPlaceholderAutoConfiguration.class, ConfigServerConfiguration.class })
@Import({ PropertyPlaceholderAutoConfiguration.class, EnvironmentRepositoryConfiguration.class })
protected static class TestConfiguration {
}

View File

@@ -30,7 +30,7 @@ import org.junit.Test;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.cloud.config.server.ConfigServerConfiguration;
import org.springframework.cloud.config.server.EnvironmentRepositoryConfiguration;
import org.springframework.cloud.config.server.ConfigServerTestUtils;
import org.springframework.cloud.config.server.EnvironmentRepository;
import org.springframework.context.ConfigurableApplicationContext;
@@ -107,7 +107,7 @@ public class MultipleJGitEnvironmentRepositoryIntegrationTests {
}
@Configuration
@Import({ PropertyPlaceholderAutoConfiguration.class, ConfigServerConfiguration.class })
@Import({ PropertyPlaceholderAutoConfiguration.class, EnvironmentRepositoryConfiguration.class })
protected static class TestConfiguration {
}

View File

@@ -143,7 +143,7 @@ public class SVNKitEnvironmentRepositoryIntegrationTests {
}
@Configuration
@Import({ PropertyPlaceholderAutoConfiguration.class, ConfigServerConfiguration.class })
@Import({ PropertyPlaceholderAutoConfiguration.class, EnvironmentRepositoryConfiguration.class })
protected static class TestConfiguration {
}