Skip to content

Commit

Permalink
Merge branch 'sstiglitz-rebase-for-publish'
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanjbaxter committed Nov 6, 2019
2 parents c0dd3aa + 91a6301 commit 8025d7d
Show file tree
Hide file tree
Showing 21 changed files with 766 additions and 11 deletions.
8 changes: 8 additions & 0 deletions docs/src/main/asciidoc/spring-cloud-config.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1335,6 +1335,14 @@ So, if you want a profile-specific file, `/\*/development/*/logback.xml` can be
NOTE: If you do not want to supply the `label` and let the server use the default label, you can supply a `useDefaultLabel` request parameter.
So, the preceding example for the `default` profile could be `/foo/default/nginx.conf?useDefaultLabel`.

=== Decrpyting Plain Text

By default, encrypted values in plain text files are not decrypted. In order to enable decryption for plain text files, set `spring.cloud.config.server.encrypt.enabled=true` and `spring.cloud.config.server.encrypt.plainTextEncrypt=true` in `bootstrap.[yml|properties]`

NOTE: Decrpyting plain text files is only supported for YAML, JSON, and properties file extensions.

If this feature is enabled, and an unsupported file extention is requested, any encrypted values in the file will not be decrypted.

== Embedding the Config Server

The Config Server runs best as a standalone application.
Expand Down
4 changes: 4 additions & 0 deletions spring-cloud-config-server/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
</dependency>
<dependency>
<groupId>org.tmatesoft.svnkit</groupId>
<artifactId>svnkit</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
@EnableConfigurationProperties(ConfigServerProperties.class)
@Import({ EnvironmentRepositoryConfiguration.class, CompositeConfiguration.class,
ResourceRepositoryConfiguration.class, ConfigServerEncryptionConfiguration.class,
ConfigServerMvcConfiguration.class })
ConfigServerMvcConfiguration.class, ResourceEncryptorConfiguration.class })
public class ConfigServerAutoConfiguration {

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,16 @@

package org.springframework.cloud.config.server.config;

import java.util.HashMap;
import java.util.Map;

import com.fasterxml.jackson.databind.ObjectMapper;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.cloud.config.server.encryption.EnvironmentEncryptor;
import org.springframework.cloud.config.server.encryption.ResourceEncryptor;
import org.springframework.cloud.config.server.environment.EnvironmentController;
import org.springframework.cloud.config.server.environment.EnvironmentEncryptorEnvironmentRepository;
import org.springframework.cloud.config.server.environment.EnvironmentRepository;
Expand Down Expand Up @@ -49,6 +53,9 @@ public class ConfigServerMvcConfiguration implements WebMvcConfigurer {
@Autowired(required = false)
private ObjectMapper objectMapper = new ObjectMapper();

@Autowired(required = false)
private Map<String, ResourceEncryptor> resourceEncryptorMap = new HashMap<>();

@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.mediaType("properties", MediaType.valueOf("text/plain"));
Expand All @@ -72,14 +79,16 @@ public EnvironmentController environmentController(
public ResourceController resourceController(ResourceRepository repository,
EnvironmentRepository envRepository, ConfigServerProperties server) {
ResourceController controller = new ResourceController(repository,
encrypted(envRepository, server));
encrypted(envRepository, server), this.resourceEncryptorMap);
controller.setEncryptEnabled(server.getEncrypt().isEnabled());
controller.setPlainTextEncryptEnabled(server.getEncrypt().isPlainTextEncrypt());
return controller;
}

private EnvironmentRepository encrypted(EnvironmentRepository envRepository,
ConfigServerProperties server) {
EnvironmentEncryptorEnvironmentRepository encrypted = new EnvironmentEncryptorEnvironmentRepository(
envRepository, this.environmentEncryptor);
envRepository, environmentEncryptor);
encrypted.setOverrides(server.getOverrides());
return encrypted;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,12 @@ public static class Encrypt {
*/
private boolean enabled = true;

/**
* Enable decryption of environment properties served by plain text endpoint
* {@link org.springframework.cloud.config.server.resource.ResourceController}.
*/
private boolean plainTextEncrypt = false;

public boolean isEnabled() {
return this.enabled;
}
Expand All @@ -165,6 +171,14 @@ public void setEnabled(boolean enabled) {
this.enabled = enabled;
}

public boolean isPlainTextEncrypt() {
return plainTextEncrypt;
}

public void setPlainTextEncrypt(boolean plainTextEncrypt) {
this.plainTextEncrypt = plainTextEncrypt;
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright 2002-2019 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
*
* https://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.config;

import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.cloud.config.server.encryption.CipherResourceJsonEncryptor;
import org.springframework.cloud.config.server.encryption.CipherResourcePropertiesEncryptor;
import org.springframework.cloud.config.server.encryption.CipherResourceYamlEncryptor;
import org.springframework.cloud.config.server.encryption.ResourceEncryptor;
import org.springframework.cloud.config.server.encryption.TextEncryptorLocator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* Adds configuration to decrypt plain text files served through
* {@link org.springframework.cloud.config.server.resource.ResourceController}. Each
* supported extension is added as a key with its associated @{link
* org.springframework.cloud.config.server.encryption.ResourceEncryptor} implementation as
* a value.
*
* @author Sean Stiglitz
*/
@Configuration
@ConditionalOnExpression("${spring.cloud.config.server.encrypt.enabled:true} && ${spring.cloud.config.server.encrypt.plainTextEncrypt:false}")
public class ResourceEncryptorConfiguration {

@Autowired
private TextEncryptorLocator encryptor;

@Bean
Map<String, ResourceEncryptor> resourceEncryptors() {
Map<String, ResourceEncryptor> resourceEncryptorMap = new HashMap<>();
addSupportedExtensionsToMap(resourceEncryptorMap,
new CipherResourceJsonEncryptor(encryptor));
addSupportedExtensionsToMap(resourceEncryptorMap,
new CipherResourcePropertiesEncryptor(encryptor));
addSupportedExtensionsToMap(resourceEncryptorMap,
new CipherResourceYamlEncryptor(encryptor));
return resourceEncryptorMap;
}

private void addSupportedExtensionsToMap(
Map<String, ResourceEncryptor> resourceEncryptorMap,
ResourceEncryptor resourceEncryptor) {
for (String ext : resourceEncryptor.getSupportedExtensions()) {
resourceEncryptorMap.put(ext, resourceEncryptor);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* Copyright 2002-2019 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
*
* https://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 java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;

import org.springframework.cloud.config.environment.Environment;
import org.springframework.util.StringUtils;

/**
* Abstract base class for any @{link
* org.springframework.cloud.config.server.encryption.ResourceEncryptor} implementations.
* Meant to house shared configuration and logic.
*
* @author Sean Stiglitz
*/
abstract class AbstractCipherResourceEncryptor implements ResourceEncryptor {

protected final String CIPHER_MARKER = "{cipher}";

private final TextEncryptorLocator encryptor;

private EnvironmentPrefixHelper helper = new EnvironmentPrefixHelper();

AbstractCipherResourceEncryptor(TextEncryptorLocator encryptor) {
this.encryptor = encryptor;
}

@Override
public abstract List<String> getSupportedExtensions();

@Override
public abstract String decrypt(String text, Environment environment)
throws IOException;

protected String decryptWithJacksonParser(String text, String name, String[] profiles,
JsonFactory factory) throws IOException {
Set<String> valsToDecrpyt = new HashSet<String>();
JsonParser parser = factory.createParser(text);
JsonToken token;

while ((token = parser.nextToken()) != null) {
if (token.equals(JsonToken.VALUE_STRING)
&& parser.getValueAsString().startsWith(CIPHER_MARKER)) {
valsToDecrpyt.add(parser.getValueAsString().trim());
}
}

for (String value : valsToDecrpyt) {
String decryptedValue = decryptValue(value.replace(CIPHER_MARKER, ""), name,
profiles);
text = text.replace(value, decryptedValue);
}

return text;
}

protected String decryptValue(String value, String name, String[] profiles) {
return encryptor
.locate(this.helper.getEncryptorKeys(name,
StringUtils.arrayToCommaDelimitedString(profiles), value))
.decrypt(this.helper.stripPrefix(value));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright 2013-2019 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
*
* https://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 java.io.IOException;
import java.util.Arrays;
import java.util.List;

import com.fasterxml.jackson.core.JsonFactory;

import org.springframework.cloud.config.environment.Environment;
import org.springframework.stereotype.Component;

/**
* @{link org.springframework.cloud.config.server.encryption.ResourceEncryptor}
* implementation that can decrypt property values prefixed with {cipher} marker in a JSON
* file.
* @author Sean Stiglitz
*/
@Component
public class CipherResourceJsonEncryptor extends AbstractCipherResourceEncryptor
implements ResourceEncryptor {

private static final List<String> SUPPORTED_EXTENSIONS = Arrays.asList("json");

private final JsonFactory factory;

public CipherResourceJsonEncryptor(TextEncryptorLocator encryptor) {
super(encryptor);
this.factory = new JsonFactory();
}

@Override
public List<String> getSupportedExtensions() {
return SUPPORTED_EXTENSIONS;
}

@Override
public String decrypt(String text, Environment environment) throws IOException {
return decryptWithJacksonParser(text, environment.getName(),
environment.getProfiles(), factory);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Copyright 2002-2019 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
*
* https://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 java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;

import org.springframework.cloud.config.environment.Environment;
import org.springframework.stereotype.Component;

/**
* @{link org.springframework.cloud.config.server.encryption.ResourceEncryptor}
* implementation that can decrypt property values prefixed with {cipher} marker in a
* Properties file.
* @author Sean Stiglitz
*/
@Component
public class CipherResourcePropertiesEncryptor extends AbstractCipherResourceEncryptor
implements ResourceEncryptor {

private static final List<String> SUPPORTED_EXTENSIONS = Arrays.asList("properties");

public CipherResourcePropertiesEncryptor(TextEncryptorLocator encryptor) {
super(encryptor);
}

@Override
public List<String> getSupportedExtensions() {
return SUPPORTED_EXTENSIONS;
}

@Override
public String decrypt(String text, Environment environment) throws IOException {
Set<String> valsToDecrpyt = new HashSet<String>();
Properties properties = new Properties();
StringBuffer sb = new StringBuffer();
properties.load(new ByteArrayInputStream(text.getBytes()));

for (Object value : properties.values()) {
String valueStr = value.toString();
if (valueStr.startsWith(CIPHER_MARKER)) {
valsToDecrpyt.add(valueStr);
}
}

for (String value : valsToDecrpyt) {
String decryptedValue = decryptValue(value.replace(CIPHER_MARKER, ""),
environment.getName(), environment.getProfiles());
text = text.replace(value, decryptedValue);
}

return text;
}

}
Loading

0 comments on commit 8025d7d

Please sign in to comment.