Skip to content

Commit 7f2b16d

Browse files
committed
Add option to configure the sanitized metadata keys
fixes codecentric#590
1 parent e15d7d2 commit 7f2b16d

File tree

10 files changed

+431
-292
lines changed

10 files changed

+431
-292
lines changed

spring-boot-admin-docs/src/main/asciidoc/server.adoc

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,12 @@
2727

2828
| spring.boot.admin.routes.endpoints
2929
| The enpoints which will be available via spring boot admin zuul proxy. If you write ui modules using other endpoints you need to add them.
30-
| `"env, metrics, trace, dump, jolokia, info, configprops, activiti, logfile, refresh, flyway, liquibase, loggers"`
30+
| `"env", "metrics", "trace", "dump", "jolokia", "info", "configprops", "activiti", "logfile", "refresh", "flyway", "liquibase", "loggers"`
31+
32+
| spring.boot.admin.metadata-keys-to-sanitize
33+
| Metadata values for the keys matching these regex patterns will be sanitized in all json output.
34+
| `".*password$", ".*secret$", ".*key$", ".*$token$", ".*credentials.*", ".*vcap_services$"`
35+
3136
|===
3237

3338
include::server-discovery.adoc[]

spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/config/AdminServerProperties.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ public class AdminServerProperties {
1616

1717
private RoutesProperties routes = new RoutesProperties();
1818

19+
/**
20+
* The metadata keys which should be sanitized when serializing to json
21+
*/
22+
private String[] metadataKeysToSanitize = new String[]{".*password$", ".*secret$", ".*key$", ".*$token$", ".*credentials.*", ".*vcap_services$"};
23+
1924
public void setContextPath(String pathPrefix) {
2025
if (!pathPrefix.startsWith("/") || pathPrefix.endsWith("/")) {
2126
throw new IllegalArgumentException("ContextPath must start with '/' and not end with '/'");
@@ -27,6 +32,14 @@ public String getContextPath() {
2732
return contextPath;
2833
}
2934

35+
public String[] getMetadataKeysToSanitize() {
36+
return metadataKeysToSanitize;
37+
}
38+
39+
public void setMetadataKeysToSanitize(String[] metadataKeysToSanitize) {
40+
this.metadataKeysToSanitize = metadataKeysToSanitize;
41+
}
42+
3043
public MonitorProperties getMonitor() {
3144
return monitor;
3245
}

spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/config/AdminServerWebConfiguration.java

Lines changed: 112 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,17 @@
3636
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
3737

3838
import com.fasterxml.jackson.databind.ObjectMapper;
39+
import com.fasterxml.jackson.databind.module.SimpleModule;
3940

4041
import de.codecentric.boot.admin.event.ClientApplicationDeregisteredEvent;
4142
import de.codecentric.boot.admin.event.ClientApplicationRegisteredEvent;
4243
import de.codecentric.boot.admin.event.RoutesOutdatedEvent;
44+
import de.codecentric.boot.admin.jackson.ApplicationBeanSerializerModifier;
45+
import de.codecentric.boot.admin.jackson.ApplicationDeserializer;
46+
import de.codecentric.boot.admin.jackson.SanitizingMapSerializer;
4347
import de.codecentric.boot.admin.journal.ApplicationEventJournal;
4448
import de.codecentric.boot.admin.journal.web.JournalController;
49+
import de.codecentric.boot.admin.model.Application;
4550
import de.codecentric.boot.admin.registry.ApplicationRegistry;
4651
import de.codecentric.boot.admin.registry.web.RegistryController;
4752
import de.codecentric.boot.admin.web.AdminController;
@@ -51,107 +56,112 @@
5156
import de.codecentric.boot.admin.web.servlet.resource.ResourcePatternResolvingResourceResolver;
5257

5358
@Configuration
54-
public class AdminServerWebConfiguration extends WebMvcConfigurerAdapter
55-
implements ApplicationContextAware {
56-
private final ApplicationEventPublisher publisher;
57-
private final ServerProperties server;
58-
private final ResourcePatternResolver resourcePatternResolver;
59-
private final AdminServerProperties adminServerProperties;
60-
private ApplicationContext applicationContext;
61-
62-
public AdminServerWebConfiguration(ApplicationEventPublisher publisher, ServerProperties server,
63-
ResourcePatternResolver resourcePatternResolver,
64-
AdminServerProperties adminServerProperties) {
65-
this.publisher = publisher;
66-
this.server = server;
67-
this.resourcePatternResolver = resourcePatternResolver;
68-
this.adminServerProperties = adminServerProperties;
69-
}
70-
71-
@Override
72-
public void setApplicationContext(ApplicationContext applicationContext) {
73-
this.applicationContext = applicationContext;
74-
}
75-
76-
@Override
77-
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
78-
if (!hasConverter(converters, MappingJackson2HttpMessageConverter.class)) {
79-
ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json()
80-
.applicationContext(this.applicationContext).build();
81-
converters.add(new MappingJackson2HttpMessageConverter(objectMapper));
82-
}
83-
}
84-
85-
private boolean hasConverter(List<HttpMessageConverter<?>> converters,
86-
Class<? extends HttpMessageConverter<?>> clazz) {
87-
for (HttpMessageConverter<?> converter : converters) {
88-
if (clazz.isInstance(converter)) {
89-
return true;
90-
}
91-
}
92-
return false;
93-
}
94-
95-
@Override
96-
public void addResourceHandlers(ResourceHandlerRegistry registry) {
97-
registry.addResourceHandler(adminServerProperties.getContextPath() + "/**")
98-
.addResourceLocations("classpath:/META-INF/spring-boot-admin-server-ui/")
99-
.resourceChain(true)
100-
.addResolver(new PreferMinifiedFilteringResourceResolver(".min"));
101-
102-
registry.addResourceHandler(adminServerProperties.getContextPath() + "/all-modules.css")
103-
.resourceChain(true)
104-
.addResolver(new ResourcePatternResolvingResourceResolver(resourcePatternResolver,
105-
"classpath*:/META-INF/spring-boot-admin-server-ui/*/module.css"))
106-
.addResolver(new ConcatenatingResourceResolver("\n".getBytes()));
107-
108-
registry.addResourceHandler(adminServerProperties.getContextPath() + "/all-modules.js")
109-
.resourceChain(true)
110-
.addResolver(new ResourcePatternResolvingResourceResolver(resourcePatternResolver,
111-
"classpath*:/META-INF/spring-boot-admin-server-ui/*/module.js"))
112-
.addResolver(new PreferMinifiedFilteringResourceResolver(".min"))
113-
.addResolver(new ConcatenatingResourceResolver(";\n".getBytes()));
114-
}
115-
116-
@Override
117-
public void addViewControllers(ViewControllerRegistry registry) {
118-
String contextPath = adminServerProperties.getContextPath();
119-
if (StringUtils.hasText(contextPath)) {
120-
registry.addRedirectViewController(contextPath, server.getPath(contextPath) + "/");
121-
}
122-
registry.addViewController(contextPath + "/").setViewName("forward:index.html");
123-
}
124-
125-
@Bean
126-
public PrefixHandlerMapping prefixHandlerMapping() {
127-
Map<String, Object> beans = applicationContext
128-
.getBeansWithAnnotation(AdminController.class);
129-
PrefixHandlerMapping prefixHandlerMapping = new PrefixHandlerMapping(
130-
beans.values().toArray(new Object[beans.size()]));
131-
prefixHandlerMapping.setPrefix(adminServerProperties.getContextPath());
132-
return prefixHandlerMapping;
133-
}
134-
135-
@Bean
136-
@ConditionalOnMissingBean
137-
public RegistryController registryController(ApplicationRegistry applicationRegistry) {
138-
return new RegistryController(applicationRegistry);
139-
}
140-
141-
@Bean
142-
@ConditionalOnMissingBean
143-
public JournalController journalController(ApplicationEventJournal applicationEventJournal) {
144-
return new JournalController(applicationEventJournal);
145-
}
146-
147-
@EventListener
148-
public void onClientApplicationRegistered(ClientApplicationRegisteredEvent event) {
149-
publisher.publishEvent(new RoutesOutdatedEvent());
150-
}
151-
152-
@EventListener
153-
public void onClientApplicationDeregistered(ClientApplicationDeregisteredEvent event) {
154-
publisher.publishEvent(new RoutesOutdatedEvent());
155-
}
59+
public class AdminServerWebConfiguration extends WebMvcConfigurerAdapter implements ApplicationContextAware {
60+
private final ApplicationEventPublisher publisher;
61+
private final ServerProperties server;
62+
private final ResourcePatternResolver resourcePatternResolver;
63+
private final AdminServerProperties adminServerProperties;
64+
private ApplicationContext applicationContext;
65+
66+
public AdminServerWebConfiguration(ApplicationEventPublisher publisher, ServerProperties server, ResourcePatternResolver resourcePatternResolver, AdminServerProperties adminServerProperties) {
67+
this.publisher = publisher;
68+
this.server = server;
69+
this.resourcePatternResolver = resourcePatternResolver;
70+
this.adminServerProperties = adminServerProperties;
71+
}
72+
73+
@Override
74+
public void setApplicationContext(ApplicationContext applicationContext) {
75+
this.applicationContext = applicationContext;
76+
}
77+
78+
@Override
79+
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
80+
if (!hasConverter(converters, MappingJackson2HttpMessageConverter.class)) {
81+
ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json()
82+
.applicationContext(this.applicationContext)
83+
.build();
84+
converters.add(new MappingJackson2HttpMessageConverter(objectMapper));
85+
}
86+
}
87+
88+
private boolean hasConverter(List<HttpMessageConverter<?>> converters, Class<? extends HttpMessageConverter<?>> clazz) {
89+
for (HttpMessageConverter<?> converter : converters) {
90+
if (clazz.isInstance(converter)) {
91+
return true;
92+
}
93+
}
94+
return false;
95+
}
96+
97+
@Override
98+
public void addResourceHandlers(ResourceHandlerRegistry registry) {
99+
registry.addResourceHandler(adminServerProperties.getContextPath() + "/**")
100+
.addResourceLocations("classpath:/META-INF/spring-boot-admin-server-ui/")
101+
.resourceChain(true)
102+
.addResolver(new PreferMinifiedFilteringResourceResolver(".min"));
103+
104+
registry.addResourceHandler(adminServerProperties.getContextPath() + "/all-modules.css")
105+
.resourceChain(true)
106+
.addResolver(new ResourcePatternResolvingResourceResolver(resourcePatternResolver,
107+
"classpath*:/META-INF/spring-boot-admin-server-ui/*/module.css"))
108+
.addResolver(new ConcatenatingResourceResolver("\n".getBytes()));
109+
110+
registry.addResourceHandler(adminServerProperties.getContextPath() + "/all-modules.js")
111+
.resourceChain(true)
112+
.addResolver(new ResourcePatternResolvingResourceResolver(resourcePatternResolver,
113+
"classpath*:/META-INF/spring-boot-admin-server-ui/*/module.js"))
114+
.addResolver(new PreferMinifiedFilteringResourceResolver(".min"))
115+
.addResolver(new ConcatenatingResourceResolver(";\n".getBytes()));
116+
}
117+
118+
@Override
119+
public void addViewControllers(ViewControllerRegistry registry) {
120+
String contextPath = adminServerProperties.getContextPath();
121+
if (StringUtils.hasText(contextPath)) {
122+
registry.addRedirectViewController(contextPath, server.getPath(contextPath) + "/");
123+
}
124+
registry.addViewController(contextPath + "/").setViewName("forward:index.html");
125+
}
126+
127+
@Bean
128+
public SimpleModule adminJacksonModule() {
129+
SimpleModule module = new SimpleModule();
130+
module.addDeserializer(Application.class, new ApplicationDeserializer());
131+
module.setSerializerModifier(new ApplicationBeanSerializerModifier(
132+
new SanitizingMapSerializer(adminServerProperties.getMetadataKeysToSanitize()))) ;
133+
return module;
134+
}
135+
136+
@Bean
137+
public PrefixHandlerMapping prefixHandlerMapping() {
138+
Map<String, Object> beans = applicationContext.getBeansWithAnnotation(AdminController.class);
139+
PrefixHandlerMapping prefixHandlerMapping = new PrefixHandlerMapping(
140+
beans.values().toArray(new Object[beans.size()]));
141+
prefixHandlerMapping.setPrefix(adminServerProperties.getContextPath());
142+
return prefixHandlerMapping;
143+
}
144+
145+
@Bean
146+
@ConditionalOnMissingBean
147+
public RegistryController registryController(ApplicationRegistry applicationRegistry) {
148+
return new RegistryController(applicationRegistry);
149+
}
150+
151+
@Bean
152+
@ConditionalOnMissingBean
153+
public JournalController journalController(ApplicationEventJournal applicationEventJournal) {
154+
return new JournalController(applicationEventJournal);
155+
}
156+
157+
@EventListener
158+
public void onClientApplicationRegistered(ClientApplicationRegisteredEvent event) {
159+
publisher.publishEvent(new RoutesOutdatedEvent());
160+
}
161+
162+
@EventListener
163+
public void onClientApplicationDeregistered(ClientApplicationDeregisteredEvent event) {
164+
publisher.publishEvent(new RoutesOutdatedEvent());
165+
}
156166

157167
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package de.codecentric.boot.admin.jackson;
2+
3+
import de.codecentric.boot.admin.model.Application;
4+
5+
import java.util.List;
6+
import com.fasterxml.jackson.databind.BeanDescription;
7+
import com.fasterxml.jackson.databind.JsonSerializer;
8+
import com.fasterxml.jackson.databind.SerializationConfig;
9+
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
10+
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
11+
12+
public class ApplicationBeanSerializerModifier extends BeanSerializerModifier {
13+
14+
private final JsonSerializer<Object> metadataSerializer;
15+
16+
@SuppressWarnings("unchecked")
17+
public ApplicationBeanSerializerModifier(SanitizingMapSerializer metadataSerializer) {
18+
this.metadataSerializer = (JsonSerializer<Object>) (JsonSerializer) metadataSerializer;
19+
}
20+
21+
@Override
22+
public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) {
23+
if (!Application.class.isAssignableFrom(beanDesc.getBeanClass())) {
24+
return beanProperties;
25+
}
26+
27+
for (BeanPropertyWriter beanProperty : beanProperties) {
28+
if ("metadata".equals(beanProperty.getName())) {
29+
beanProperty.assignSerializer(metadataSerializer);
30+
}
31+
}
32+
return beanProperties;
33+
}
34+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package de.codecentric.boot.admin.jackson;
2+
3+
import de.codecentric.boot.admin.model.Application;
4+
5+
import java.io.IOException;
6+
import java.util.Iterator;
7+
import java.util.Map;
8+
import com.fasterxml.jackson.core.JsonParser;
9+
import com.fasterxml.jackson.core.JsonProcessingException;
10+
import com.fasterxml.jackson.databind.DeserializationContext;
11+
import com.fasterxml.jackson.databind.JsonNode;
12+
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
13+
14+
public class ApplicationDeserializer extends StdDeserializer<Application> {
15+
private static final long serialVersionUID = 1L;
16+
17+
public ApplicationDeserializer() {
18+
super(Application.class);
19+
}
20+
21+
@Override
22+
public Application deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
23+
JsonNode node = p.readValueAsTree();
24+
25+
Application.Builder builder = Application.create(node.get("name").asText());
26+
27+
if (node.has("url")) {
28+
String url = node.get("url").asText();
29+
builder.withHealthUrl(url.replaceFirst("/+$", "") + "/health").withManagementUrl(url);
30+
} else {
31+
if (node.has("healthUrl")) {
32+
builder.withHealthUrl(node.get("healthUrl").asText());
33+
}
34+
if (node.has("managementUrl")) {
35+
builder.withManagementUrl(node.get("managementUrl").asText());
36+
}
37+
if (node.has("serviceUrl")) {
38+
builder.withServiceUrl(node.get("serviceUrl").asText());
39+
}
40+
}
41+
42+
if (node.has("metadata")) {
43+
Iterator<Map.Entry<String, JsonNode>> it = node.get("metadata").fields();
44+
while (it.hasNext()) {
45+
Map.Entry<String, JsonNode> entry = it.next();
46+
builder.addMetadata(entry.getKey(), entry.getValue().asText());
47+
}
48+
}
49+
return builder.build();
50+
}
51+
}

0 commit comments

Comments
 (0)