From 2a0f73740a0487bd432ba5bbfb90c87a24e0c7e2 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Tue, 26 May 2015 16:54:57 +0100 Subject: [PATCH] Make text encryptor configuration an autoconfig So that it can be controlled with @ConditionalOnBean etc. --- .../server/ConfigServerApplication.java | 2 +- .../ConfigServerBootstrapConfiguration.java | 2 +- .../server/ConfigServerMvcConfiguration.java | 2 +- .../config/server/EnableConfigServer.java | 5 +- .../config/server/EncryptionController.java | 72 +++++++++---------- .../config/server/EnvironmentController.java | 12 ++-- ...> EnvironmentRepositoryConfiguration.java} | 2 +- .../ConfigServerEncryptionConfiguration.java | 16 +---- .../EncryptionAutoConfiguration.java | 49 +++++++++++++ .../main/resources/META-INF/spring.factories | 4 ++ .../server/EncryptionControllerTests.java | 8 ++- ...EnvironmentRepositoryIntegrationTests.java | 2 +- ...EnvironmentRepositoryIntegrationTests.java | 4 +- ...EnvironmentRepositoryIntegrationTests.java | 2 +- 14 files changed, 116 insertions(+), 66 deletions(-) rename spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/{ConfigServerConfiguration.java => EnvironmentRepositoryConfiguration.java} (95%) create mode 100644 spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/encryption/EncryptionAutoConfiguration.java diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ConfigServerApplication.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ConfigServerApplication.java index 9fc9e486..f3d42d0f 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ConfigServerApplication.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ConfigServerApplication.java @@ -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); } } diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ConfigServerBootstrapConfiguration.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ConfigServerBootstrapConfiguration.java index 1dbd0abc..621a8b42 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ConfigServerBootstrapConfiguration.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ConfigServerBootstrapConfiguration.java @@ -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 diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ConfigServerMvcConfiguration.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ConfigServerMvcConfiguration.java index 05b5d890..e2521980 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ConfigServerMvcConfiguration.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ConfigServerMvcConfiguration.java @@ -35,7 +35,7 @@ public class ConfigServerMvcConfiguration { @Autowired private ConfigServerProperties server; - @Autowired + @Autowired(required=false) private EnvironmentEncryptor environmentEncryptor; @Bean diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/EnableConfigServer.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/EnableConfigServer.java index 3bf27850..65a4e027 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/EnableConfigServer.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/EnableConfigServer.java @@ -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 { } diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/EncryptionController.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/EncryptionController.java index 22791079..29a588ee 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/EncryptionController.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/EncryptionController.java @@ -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> uploadKey(@RequestBody String data, @RequestHeader("Content-Type") MediaType type) { Map body = new HashMap(); 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; } diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/EnvironmentController.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/EnvironmentController.java index 32801364..ff1150cd 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/EnvironmentController.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/EnvironmentController.java @@ -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)); } } diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ConfigServerConfiguration.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/EnvironmentRepositoryConfiguration.java similarity index 95% rename from spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ConfigServerConfiguration.java rename to spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/EnvironmentRepositoryConfiguration.java index a08e4d1b..019e8763 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ConfigServerConfiguration.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/EnvironmentRepositoryConfiguration.java @@ -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) diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/encryption/ConfigServerEncryptionConfiguration.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/encryption/ConfigServerEncryptionConfiguration.java index 60c5c590..44d63aed 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/encryption/ConfigServerEncryptionConfiguration.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/encryption/ConfigServerEncryptionConfiguration.java @@ -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); } + } diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/encryption/EncryptionAutoConfiguration.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/encryption/EncryptionAutoConfiguration.java new file mode 100644 index 00000000..8b538662 --- /dev/null +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/encryption/EncryptionAutoConfiguration.java @@ -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(); + } + + } + +} diff --git a/spring-cloud-config-server/src/main/resources/META-INF/spring.factories b/spring-cloud-config-server/src/main/resources/META-INF/spring.factories index 5332de41..0bcdc3ea 100644 --- a/spring-cloud-config-server/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-config-server/src/main/resources/META-INF/spring.factories @@ -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 diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/EncryptionControllerTests.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/EncryptionControllerTests.java index 025731e8..11be3bde 100644 --- a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/EncryptionControllerTests.java +++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/EncryptionControllerTests.java @@ -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); diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/JGitEnvironmentRepositoryIntegrationTests.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/JGitEnvironmentRepositoryIntegrationTests.java index 520acaa3..cf72ee33 100644 --- a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/JGitEnvironmentRepositoryIntegrationTests.java +++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/JGitEnvironmentRepositoryIntegrationTests.java @@ -132,7 +132,7 @@ public class JGitEnvironmentRepositoryIntegrationTests { } @Configuration - @Import({ PropertyPlaceholderAutoConfiguration.class, ConfigServerConfiguration.class }) + @Import({ PropertyPlaceholderAutoConfiguration.class, EnvironmentRepositoryConfiguration.class }) protected static class TestConfiguration { } diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/MultipleJGitEnvironmentRepositoryIntegrationTests.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/MultipleJGitEnvironmentRepositoryIntegrationTests.java index e0634a73..2289e724 100644 --- a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/MultipleJGitEnvironmentRepositoryIntegrationTests.java +++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/MultipleJGitEnvironmentRepositoryIntegrationTests.java @@ -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 { } diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/SVNKitEnvironmentRepositoryIntegrationTests.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/SVNKitEnvironmentRepositoryIntegrationTests.java index b1200591..10b5d3f7 100644 --- a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/SVNKitEnvironmentRepositoryIntegrationTests.java +++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/SVNKitEnvironmentRepositoryIntegrationTests.java @@ -143,7 +143,7 @@ public class SVNKitEnvironmentRepositoryIntegrationTests { } @Configuration - @Import({ PropertyPlaceholderAutoConfiguration.class, ConfigServerConfiguration.class }) + @Import({ PropertyPlaceholderAutoConfiguration.class, EnvironmentRepositoryConfiguration.class }) protected static class TestConfiguration { }