Skip to content

Commit 8025d7d

Browse files
author
Ryan Baxter
committed
Merge branch 'sstiglitz-rebase-for-publish'
2 parents c0dd3aa + 91a6301 commit 8025d7d

21 files changed

Lines changed: 766 additions & 11 deletions

docs/src/main/asciidoc/spring-cloud-config.adoc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1335,6 +1335,14 @@ So, if you want a profile-specific file, `/\*/development/*/logback.xml` can be
13351335
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.
13361336
So, the preceding example for the `default` profile could be `/foo/default/nginx.conf?useDefaultLabel`.
13371337

1338+
=== Decrpyting Plain Text
1339+
1340+
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]`
1341+
1342+
NOTE: Decrpyting plain text files is only supported for YAML, JSON, and properties file extensions.
1343+
1344+
If this feature is enabled, and an unsupported file extention is requested, any encrypted values in the file will not be decrypted.
1345+
13381346
== Embedding the Config Server
13391347

13401348
The Config Server runs best as a standalone application.

spring-cloud-config-server/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@
7070
<groupId>org.yaml</groupId>
7171
<artifactId>snakeyaml</artifactId>
7272
</dependency>
73+
<dependency>
74+
<groupId>com.fasterxml.jackson.dataformat</groupId>
75+
<artifactId>jackson-dataformat-yaml</artifactId>
76+
</dependency>
7377
<dependency>
7478
<groupId>org.tmatesoft.svnkit</groupId>
7579
<artifactId>svnkit</artifactId>

spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/ConfigServerAutoConfiguration.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
@EnableConfigurationProperties(ConfigServerProperties.class)
3030
@Import({ EnvironmentRepositoryConfiguration.class, CompositeConfiguration.class,
3131
ResourceRepositoryConfiguration.class, ConfigServerEncryptionConfiguration.class,
32-
ConfigServerMvcConfiguration.class })
32+
ConfigServerMvcConfiguration.class, ResourceEncryptorConfiguration.class })
3333
public class ConfigServerAutoConfiguration {
3434

3535
}

spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/ConfigServerMvcConfiguration.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,16 @@
1616

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

19+
import java.util.HashMap;
20+
import java.util.Map;
21+
1922
import com.fasterxml.jackson.databind.ObjectMapper;
2023

2124
import org.springframework.beans.factory.annotation.Autowired;
2225
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
2326
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
2427
import org.springframework.cloud.config.server.encryption.EnvironmentEncryptor;
28+
import org.springframework.cloud.config.server.encryption.ResourceEncryptor;
2529
import org.springframework.cloud.config.server.environment.EnvironmentController;
2630
import org.springframework.cloud.config.server.environment.EnvironmentEncryptorEnvironmentRepository;
2731
import org.springframework.cloud.config.server.environment.EnvironmentRepository;
@@ -49,6 +53,9 @@ public class ConfigServerMvcConfiguration implements WebMvcConfigurer {
4953
@Autowired(required = false)
5054
private ObjectMapper objectMapper = new ObjectMapper();
5155

56+
@Autowired(required = false)
57+
private Map<String, ResourceEncryptor> resourceEncryptorMap = new HashMap<>();
58+
5259
@Override
5360
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
5461
configurer.mediaType("properties", MediaType.valueOf("text/plain"));
@@ -72,14 +79,16 @@ public EnvironmentController environmentController(
7279
public ResourceController resourceController(ResourceRepository repository,
7380
EnvironmentRepository envRepository, ConfigServerProperties server) {
7481
ResourceController controller = new ResourceController(repository,
75-
encrypted(envRepository, server));
82+
encrypted(envRepository, server), this.resourceEncryptorMap);
83+
controller.setEncryptEnabled(server.getEncrypt().isEnabled());
84+
controller.setPlainTextEncryptEnabled(server.getEncrypt().isPlainTextEncrypt());
7685
return controller;
7786
}
7887

7988
private EnvironmentRepository encrypted(EnvironmentRepository envRepository,
8089
ConfigServerProperties server) {
8190
EnvironmentEncryptorEnvironmentRepository encrypted = new EnvironmentEncryptorEnvironmentRepository(
82-
envRepository, this.environmentEncryptor);
91+
envRepository, environmentEncryptor);
8392
encrypted.setOverrides(server.getOverrides());
8493
return encrypted;
8594
}

spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/ConfigServerProperties.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,12 @@ public static class Encrypt {
157157
*/
158158
private boolean enabled = true;
159159

160+
/**
161+
* Enable decryption of environment properties served by plain text endpoint
162+
* {@link org.springframework.cloud.config.server.resource.ResourceController}.
163+
*/
164+
private boolean plainTextEncrypt = false;
165+
160166
public boolean isEnabled() {
161167
return this.enabled;
162168
}
@@ -165,6 +171,14 @@ public void setEnabled(boolean enabled) {
165171
this.enabled = enabled;
166172
}
167173

174+
public boolean isPlainTextEncrypt() {
175+
return plainTextEncrypt;
176+
}
177+
178+
public void setPlainTextEncrypt(boolean plainTextEncrypt) {
179+
this.plainTextEncrypt = plainTextEncrypt;
180+
}
181+
168182
}
169183

170184
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright 2002-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.cloud.config.server.config;
18+
19+
import java.util.HashMap;
20+
import java.util.Map;
21+
22+
import org.springframework.beans.factory.annotation.Autowired;
23+
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
24+
import org.springframework.cloud.config.server.encryption.CipherResourceJsonEncryptor;
25+
import org.springframework.cloud.config.server.encryption.CipherResourcePropertiesEncryptor;
26+
import org.springframework.cloud.config.server.encryption.CipherResourceYamlEncryptor;
27+
import org.springframework.cloud.config.server.encryption.ResourceEncryptor;
28+
import org.springframework.cloud.config.server.encryption.TextEncryptorLocator;
29+
import org.springframework.context.annotation.Bean;
30+
import org.springframework.context.annotation.Configuration;
31+
32+
/**
33+
* Adds configuration to decrypt plain text files served through
34+
* {@link org.springframework.cloud.config.server.resource.ResourceController}. Each
35+
* supported extension is added as a key with its associated @{link
36+
* org.springframework.cloud.config.server.encryption.ResourceEncryptor} implementation as
37+
* a value.
38+
*
39+
* @author Sean Stiglitz
40+
*/
41+
@Configuration
42+
@ConditionalOnExpression("${spring.cloud.config.server.encrypt.enabled:true} && ${spring.cloud.config.server.encrypt.plainTextEncrypt:false}")
43+
public class ResourceEncryptorConfiguration {
44+
45+
@Autowired
46+
private TextEncryptorLocator encryptor;
47+
48+
@Bean
49+
Map<String, ResourceEncryptor> resourceEncryptors() {
50+
Map<String, ResourceEncryptor> resourceEncryptorMap = new HashMap<>();
51+
addSupportedExtensionsToMap(resourceEncryptorMap,
52+
new CipherResourceJsonEncryptor(encryptor));
53+
addSupportedExtensionsToMap(resourceEncryptorMap,
54+
new CipherResourcePropertiesEncryptor(encryptor));
55+
addSupportedExtensionsToMap(resourceEncryptorMap,
56+
new CipherResourceYamlEncryptor(encryptor));
57+
return resourceEncryptorMap;
58+
}
59+
60+
private void addSupportedExtensionsToMap(
61+
Map<String, ResourceEncryptor> resourceEncryptorMap,
62+
ResourceEncryptor resourceEncryptor) {
63+
for (String ext : resourceEncryptor.getSupportedExtensions()) {
64+
resourceEncryptorMap.put(ext, resourceEncryptor);
65+
}
66+
}
67+
68+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* Copyright 2002-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.cloud.config.server.encryption;
18+
19+
import java.io.IOException;
20+
import java.util.HashSet;
21+
import java.util.List;
22+
import java.util.Set;
23+
24+
import com.fasterxml.jackson.core.JsonFactory;
25+
import com.fasterxml.jackson.core.JsonParser;
26+
import com.fasterxml.jackson.core.JsonToken;
27+
28+
import org.springframework.cloud.config.environment.Environment;
29+
import org.springframework.util.StringUtils;
30+
31+
/**
32+
* Abstract base class for any @{link
33+
* org.springframework.cloud.config.server.encryption.ResourceEncryptor} implementations.
34+
* Meant to house shared configuration and logic.
35+
*
36+
* @author Sean Stiglitz
37+
*/
38+
abstract class AbstractCipherResourceEncryptor implements ResourceEncryptor {
39+
40+
protected final String CIPHER_MARKER = "{cipher}";
41+
42+
private final TextEncryptorLocator encryptor;
43+
44+
private EnvironmentPrefixHelper helper = new EnvironmentPrefixHelper();
45+
46+
AbstractCipherResourceEncryptor(TextEncryptorLocator encryptor) {
47+
this.encryptor = encryptor;
48+
}
49+
50+
@Override
51+
public abstract List<String> getSupportedExtensions();
52+
53+
@Override
54+
public abstract String decrypt(String text, Environment environment)
55+
throws IOException;
56+
57+
protected String decryptWithJacksonParser(String text, String name, String[] profiles,
58+
JsonFactory factory) throws IOException {
59+
Set<String> valsToDecrpyt = new HashSet<String>();
60+
JsonParser parser = factory.createParser(text);
61+
JsonToken token;
62+
63+
while ((token = parser.nextToken()) != null) {
64+
if (token.equals(JsonToken.VALUE_STRING)
65+
&& parser.getValueAsString().startsWith(CIPHER_MARKER)) {
66+
valsToDecrpyt.add(parser.getValueAsString().trim());
67+
}
68+
}
69+
70+
for (String value : valsToDecrpyt) {
71+
String decryptedValue = decryptValue(value.replace(CIPHER_MARKER, ""), name,
72+
profiles);
73+
text = text.replace(value, decryptedValue);
74+
}
75+
76+
return text;
77+
}
78+
79+
protected String decryptValue(String value, String name, String[] profiles) {
80+
return encryptor
81+
.locate(this.helper.getEncryptorKeys(name,
82+
StringUtils.arrayToCommaDelimitedString(profiles), value))
83+
.decrypt(this.helper.stripPrefix(value));
84+
}
85+
86+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright 2013-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.cloud.config.server.encryption;
18+
19+
import java.io.IOException;
20+
import java.util.Arrays;
21+
import java.util.List;
22+
23+
import com.fasterxml.jackson.core.JsonFactory;
24+
25+
import org.springframework.cloud.config.environment.Environment;
26+
import org.springframework.stereotype.Component;
27+
28+
/**
29+
* @{link org.springframework.cloud.config.server.encryption.ResourceEncryptor}
30+
* implementation that can decrypt property values prefixed with {cipher} marker in a JSON
31+
* file.
32+
* @author Sean Stiglitz
33+
*/
34+
@Component
35+
public class CipherResourceJsonEncryptor extends AbstractCipherResourceEncryptor
36+
implements ResourceEncryptor {
37+
38+
private static final List<String> SUPPORTED_EXTENSIONS = Arrays.asList("json");
39+
40+
private final JsonFactory factory;
41+
42+
public CipherResourceJsonEncryptor(TextEncryptorLocator encryptor) {
43+
super(encryptor);
44+
this.factory = new JsonFactory();
45+
}
46+
47+
@Override
48+
public List<String> getSupportedExtensions() {
49+
return SUPPORTED_EXTENSIONS;
50+
}
51+
52+
@Override
53+
public String decrypt(String text, Environment environment) throws IOException {
54+
return decryptWithJacksonParser(text, environment.getName(),
55+
environment.getProfiles(), factory);
56+
}
57+
58+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright 2002-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.cloud.config.server.encryption;
18+
19+
import java.io.ByteArrayInputStream;
20+
import java.io.IOException;
21+
import java.util.Arrays;
22+
import java.util.HashSet;
23+
import java.util.List;
24+
import java.util.Properties;
25+
import java.util.Set;
26+
27+
import org.springframework.cloud.config.environment.Environment;
28+
import org.springframework.stereotype.Component;
29+
30+
/**
31+
* @{link org.springframework.cloud.config.server.encryption.ResourceEncryptor}
32+
* implementation that can decrypt property values prefixed with {cipher} marker in a
33+
* Properties file.
34+
* @author Sean Stiglitz
35+
*/
36+
@Component
37+
public class CipherResourcePropertiesEncryptor extends AbstractCipherResourceEncryptor
38+
implements ResourceEncryptor {
39+
40+
private static final List<String> SUPPORTED_EXTENSIONS = Arrays.asList("properties");
41+
42+
public CipherResourcePropertiesEncryptor(TextEncryptorLocator encryptor) {
43+
super(encryptor);
44+
}
45+
46+
@Override
47+
public List<String> getSupportedExtensions() {
48+
return SUPPORTED_EXTENSIONS;
49+
}
50+
51+
@Override
52+
public String decrypt(String text, Environment environment) throws IOException {
53+
Set<String> valsToDecrpyt = new HashSet<String>();
54+
Properties properties = new Properties();
55+
StringBuffer sb = new StringBuffer();
56+
properties.load(new ByteArrayInputStream(text.getBytes()));
57+
58+
for (Object value : properties.values()) {
59+
String valueStr = value.toString();
60+
if (valueStr.startsWith(CIPHER_MARKER)) {
61+
valsToDecrpyt.add(valueStr);
62+
}
63+
}
64+
65+
for (String value : valsToDecrpyt) {
66+
String decryptedValue = decryptValue(value.replace(CIPHER_MARKER, ""),
67+
environment.getName(), environment.getProfiles());
68+
text = text.replace(value, decryptedValue);
69+
}
70+
71+
return text;
72+
}
73+
74+
}

0 commit comments

Comments
 (0)