Make text encryptor configuration an autoconfig
So that it can be controlled with @ConditionalOnBean etc.
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -35,7 +35,7 @@ public class ConfigServerMvcConfiguration {
|
||||
@Autowired
|
||||
private ConfigServerProperties server;
|
||||
|
||||
@Autowired
|
||||
@Autowired(required=false)
|
||||
private EnvironmentEncryptor environmentEncryptor;
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -132,7 +132,7 @@ public class JGitEnvironmentRepositoryIntegrationTests {
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@Import({ PropertyPlaceholderAutoConfiguration.class, ConfigServerConfiguration.class })
|
||||
@Import({ PropertyPlaceholderAutoConfiguration.class, EnvironmentRepositoryConfiguration.class })
|
||||
protected static class TestConfiguration {
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
|
||||
|
||||
@@ -143,7 +143,7 @@ public class SVNKitEnvironmentRepositoryIntegrationTests {
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@Import({ PropertyPlaceholderAutoConfiguration.class, ConfigServerConfiguration.class })
|
||||
@Import({ PropertyPlaceholderAutoConfiguration.class, EnvironmentRepositoryConfiguration.class })
|
||||
protected static class TestConfiguration {
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user