mirror of
https://github.com/spring-projects/spring-boot.git
synced 2024-07-15 01:07:30 +08:00
Support role-based sanitization for actuator endpoints
Closes gh-32156
This commit is contained in:
parent
ada2450483
commit
47effdcade
@ -16,8 +16,6 @@
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure.context.properties;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.expose.EndpointExposure;
|
||||
@ -50,17 +48,8 @@ public class ConfigurationPropertiesReportEndpointAutoConfiguration {
|
||||
public ConfigurationPropertiesReportEndpoint configurationPropertiesReportEndpoint(
|
||||
ConfigurationPropertiesReportEndpointProperties properties,
|
||||
ObjectProvider<SanitizingFunction> sanitizingFunctions) {
|
||||
ConfigurationPropertiesReportEndpoint endpoint = new ConfigurationPropertiesReportEndpoint(
|
||||
sanitizingFunctions.orderedStream().collect(Collectors.toList()));
|
||||
String[] keysToSanitize = properties.getKeysToSanitize();
|
||||
if (keysToSanitize != null) {
|
||||
endpoint.setKeysToSanitize(keysToSanitize);
|
||||
}
|
||||
String[] additionalKeysToSanitize = properties.getAdditionalKeysToSanitize();
|
||||
if (additionalKeysToSanitize != null) {
|
||||
endpoint.keysToSanitize(additionalKeysToSanitize);
|
||||
}
|
||||
return endpoint;
|
||||
return new ConfigurationPropertiesReportEndpoint(sanitizingFunctions.orderedStream().toList(),
|
||||
properties.getShowValues());
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ -68,8 +57,10 @@ public class ConfigurationPropertiesReportEndpointAutoConfiguration {
|
||||
@ConditionalOnBean(ConfigurationPropertiesReportEndpoint.class)
|
||||
@ConditionalOnAvailableEndpoint(exposure = { EndpointExposure.WEB, EndpointExposure.CLOUD_FOUNDRY })
|
||||
public ConfigurationPropertiesReportEndpointWebExtension configurationPropertiesReportEndpointWebExtension(
|
||||
ConfigurationPropertiesReportEndpoint configurationPropertiesReportEndpoint) {
|
||||
return new ConfigurationPropertiesReportEndpointWebExtension(configurationPropertiesReportEndpoint);
|
||||
ConfigurationPropertiesReportEndpoint configurationPropertiesReportEndpoint,
|
||||
ConfigurationPropertiesReportEndpointProperties properties) {
|
||||
return new ConfigurationPropertiesReportEndpointWebExtension(configurationPropertiesReportEndpoint,
|
||||
properties.getShowValues(), properties.getRoles());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2021 the original author or authors.
|
||||
* Copyright 2012-2022 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.
|
||||
@ -16,44 +16,44 @@
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure.context.properties;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.Show;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* Configuration properties for {@link ConfigurationPropertiesReportEndpoint}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Madhura Bhave
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@ConfigurationProperties("management.endpoint.configprops")
|
||||
public class ConfigurationPropertiesReportEndpointProperties {
|
||||
|
||||
/**
|
||||
* Keys that should be sanitized. Keys can be simple strings that the property ends
|
||||
* with or regular expressions.
|
||||
* When to show unsanitized values.
|
||||
*/
|
||||
private String[] keysToSanitize;
|
||||
private Show showValues = Show.NEVER;
|
||||
|
||||
/**
|
||||
* Keys that should be sanitized in addition to those already configured. Keys can be
|
||||
* simple strings that the property ends with or regular expressions.
|
||||
* Roles used to determine whether a user is authorized to be shown unsanitized
|
||||
* values. When empty, all authenticated users are authorized.
|
||||
*/
|
||||
private String[] additionalKeysToSanitize;
|
||||
private Set<String> roles = new HashSet<>();
|
||||
|
||||
public String[] getKeysToSanitize() {
|
||||
return this.keysToSanitize;
|
||||
public Show getShowValues() {
|
||||
return this.showValues;
|
||||
}
|
||||
|
||||
public void setKeysToSanitize(String[] keysToSanitize) {
|
||||
this.keysToSanitize = keysToSanitize;
|
||||
public void setShowValues(Show showValues) {
|
||||
this.showValues = showValues;
|
||||
}
|
||||
|
||||
public String[] getAdditionalKeysToSanitize() {
|
||||
return this.additionalKeysToSanitize;
|
||||
}
|
||||
|
||||
public void setAdditionalKeysToSanitize(String[] additionalKeysToSanitize) {
|
||||
this.additionalKeysToSanitize = additionalKeysToSanitize;
|
||||
public Set<String> getRoles() {
|
||||
return this.roles;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -16,8 +16,6 @@
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure.env;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.expose.EndpointExposure;
|
||||
@ -48,25 +46,18 @@ public class EnvironmentEndpointAutoConfiguration {
|
||||
@ConditionalOnMissingBean
|
||||
public EnvironmentEndpoint environmentEndpoint(Environment environment, EnvironmentEndpointProperties properties,
|
||||
ObjectProvider<SanitizingFunction> sanitizingFunctions) {
|
||||
EnvironmentEndpoint endpoint = new EnvironmentEndpoint(environment,
|
||||
sanitizingFunctions.orderedStream().collect(Collectors.toList()));
|
||||
String[] keysToSanitize = properties.getKeysToSanitize();
|
||||
if (keysToSanitize != null) {
|
||||
endpoint.setKeysToSanitize(keysToSanitize);
|
||||
}
|
||||
String[] additionalKeysToSanitize = properties.getAdditionalKeysToSanitize();
|
||||
if (additionalKeysToSanitize != null) {
|
||||
endpoint.keysToSanitize(additionalKeysToSanitize);
|
||||
}
|
||||
return endpoint;
|
||||
return new EnvironmentEndpoint(environment, sanitizingFunctions.orderedStream().toList(),
|
||||
properties.getShowValues());
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnBean(EnvironmentEndpoint.class)
|
||||
@ConditionalOnAvailableEndpoint(exposure = { EndpointExposure.WEB, EndpointExposure.CLOUD_FOUNDRY })
|
||||
public EnvironmentEndpointWebExtension environmentEndpointWebExtension(EnvironmentEndpoint environmentEndpoint) {
|
||||
return new EnvironmentEndpointWebExtension(environmentEndpoint);
|
||||
public EnvironmentEndpointWebExtension environmentEndpointWebExtension(EnvironmentEndpoint environmentEndpoint,
|
||||
EnvironmentEndpointProperties properties) {
|
||||
return new EnvironmentEndpointWebExtension(environmentEndpoint, properties.getShowValues(),
|
||||
properties.getRoles());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2021 the original author or authors.
|
||||
* Copyright 2012-2022 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.
|
||||
@ -16,6 +16,10 @@
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure.env;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.Show;
|
||||
import org.springframework.boot.actuate.env.EnvironmentEndpoint;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
@ -29,31 +33,26 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
public class EnvironmentEndpointProperties {
|
||||
|
||||
/**
|
||||
* Keys that should be sanitized. Keys can be simple strings that the property ends
|
||||
* with or regular expressions.
|
||||
* When to show unsanitized values.
|
||||
*/
|
||||
private String[] keysToSanitize;
|
||||
private Show showValues = Show.NEVER;
|
||||
|
||||
/**
|
||||
* Keys that should be sanitized in addition to those already configured. Keys can be
|
||||
* simple strings that the property ends with or regular expressions.
|
||||
* Roles used to determine whether a user is authorized to be shown unsanitized
|
||||
* values. When empty, all authenticated users are authorized.
|
||||
*/
|
||||
private String[] additionalKeysToSanitize;
|
||||
private Set<String> roles = new HashSet<>();
|
||||
|
||||
public String[] getKeysToSanitize() {
|
||||
return this.keysToSanitize;
|
||||
public Show getShowValues() {
|
||||
return this.showValues;
|
||||
}
|
||||
|
||||
public void setKeysToSanitize(String[] keysToSanitize) {
|
||||
this.keysToSanitize = keysToSanitize;
|
||||
public void setShowValues(Show showValues) {
|
||||
this.showValues = showValues;
|
||||
}
|
||||
|
||||
public String[] getAdditionalKeysToSanitize() {
|
||||
return this.additionalKeysToSanitize;
|
||||
}
|
||||
|
||||
public void setAdditionalKeysToSanitize(String[] additionalKeysToSanitize) {
|
||||
this.additionalKeysToSanitize = additionalKeysToSanitize;
|
||||
public Set<String> getRoles() {
|
||||
return this.roles;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -16,20 +16,15 @@
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure.health;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.Collection;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.health.HealthProperties.Show;
|
||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||
import org.springframework.boot.actuate.endpoint.Show;
|
||||
import org.springframework.boot.actuate.health.AdditionalHealthEndpointPath;
|
||||
import org.springframework.boot.actuate.health.HealthEndpointGroup;
|
||||
import org.springframework.boot.actuate.health.HttpCodeStatusMapper;
|
||||
import org.springframework.boot.actuate.health.StatusAggregator;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* Auto-configured {@link HealthEndpointGroup} backed by {@link HealthProperties}.
|
||||
@ -83,54 +78,13 @@ class AutoConfiguredHealthEndpointGroup implements HealthEndpointGroup {
|
||||
|
||||
@Override
|
||||
public boolean showComponents(SecurityContext securityContext) {
|
||||
if (this.showComponents == null) {
|
||||
return showDetails(securityContext);
|
||||
}
|
||||
return getShowResult(securityContext, this.showComponents);
|
||||
Show show = (this.showComponents != null) ? this.showComponents : this.showDetails;
|
||||
return show.isShown(securityContext, this.roles);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean showDetails(SecurityContext securityContext) {
|
||||
return getShowResult(securityContext, this.showDetails);
|
||||
}
|
||||
|
||||
private boolean getShowResult(SecurityContext securityContext, Show show) {
|
||||
return switch (show) {
|
||||
case NEVER -> false;
|
||||
case ALWAYS -> true;
|
||||
case WHEN_AUTHORIZED -> isAuthorized(securityContext);
|
||||
};
|
||||
}
|
||||
|
||||
private boolean isAuthorized(SecurityContext securityContext) {
|
||||
Principal principal = securityContext.getPrincipal();
|
||||
if (principal == null) {
|
||||
return false;
|
||||
}
|
||||
if (CollectionUtils.isEmpty(this.roles)) {
|
||||
return true;
|
||||
}
|
||||
boolean checkAuthorities = isSpringSecurityAuthentication(principal);
|
||||
for (String role : this.roles) {
|
||||
if (securityContext.isUserInRole(role)) {
|
||||
return true;
|
||||
}
|
||||
if (checkAuthorities) {
|
||||
Authentication authentication = (Authentication) principal;
|
||||
for (GrantedAuthority authority : authentication.getAuthorities()) {
|
||||
String name = authority.getAuthority();
|
||||
if (role.equals(name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isSpringSecurityAuthentication(Principal principal) {
|
||||
return ClassUtils.isPresent("org.springframework.security.core.Authentication", null)
|
||||
&& (principal instanceof Authentication);
|
||||
return this.showDetails.isShown(securityContext, this.roles);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -31,8 +31,8 @@ import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils;
|
||||
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointProperties.Group;
|
||||
import org.springframework.boot.actuate.autoconfigure.health.HealthProperties.Show;
|
||||
import org.springframework.boot.actuate.autoconfigure.health.HealthProperties.Status;
|
||||
import org.springframework.boot.actuate.endpoint.Show;
|
||||
import org.springframework.boot.actuate.health.AdditionalHealthEndpointPath;
|
||||
import org.springframework.boot.actuate.health.HealthEndpointGroup;
|
||||
import org.springframework.boot.actuate.health.HealthEndpointGroups;
|
||||
|
@ -21,6 +21,7 @@ import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.Show;
|
||||
import org.springframework.boot.actuate.health.HealthEndpoint;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
|
@ -23,7 +23,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.boot.actuate.health.HealthEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.Show;
|
||||
import org.springframework.boot.context.properties.NestedConfigurationProperty;
|
||||
|
||||
/**
|
||||
@ -103,27 +103,4 @@ public abstract class HealthProperties {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for showing items in responses from the {@link HealthEndpoint} web
|
||||
* extensions.
|
||||
*/
|
||||
public enum Show {
|
||||
|
||||
/**
|
||||
* Never show the item in the response.
|
||||
*/
|
||||
NEVER,
|
||||
|
||||
/**
|
||||
* Show the item in the response when accessed by an authorized user.
|
||||
*/
|
||||
WHEN_AUTHORIZED,
|
||||
|
||||
/**
|
||||
* Always show the item in the response.
|
||||
*/
|
||||
ALWAYS
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -18,8 +18,10 @@ package org.springframework.boot.actuate.autoconfigure.quartz;
|
||||
|
||||
import org.quartz.Scheduler;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.expose.EndpointExposure;
|
||||
import org.springframework.boot.actuate.endpoint.SanitizingFunction;
|
||||
import org.springframework.boot.actuate.quartz.QuartzEndpoint;
|
||||
import org.springframework.boot.actuate.quartz.QuartzEndpointWebExtension;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
@ -28,6 +30,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
/**
|
||||
@ -40,21 +43,23 @@ import org.springframework.context.annotation.Bean;
|
||||
@AutoConfiguration(after = QuartzAutoConfiguration.class)
|
||||
@ConditionalOnClass(Scheduler.class)
|
||||
@ConditionalOnAvailableEndpoint(endpoint = QuartzEndpoint.class)
|
||||
@EnableConfigurationProperties(QuartzEndpointProperties.class)
|
||||
public class QuartzEndpointAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnBean(Scheduler.class)
|
||||
@ConditionalOnMissingBean
|
||||
public QuartzEndpoint quartzEndpoint(Scheduler scheduler) {
|
||||
return new QuartzEndpoint(scheduler);
|
||||
public QuartzEndpoint quartzEndpoint(Scheduler scheduler, ObjectProvider<SanitizingFunction> sanitizingFunctions) {
|
||||
return new QuartzEndpoint(scheduler, sanitizingFunctions.orderedStream().toList());
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnBean(QuartzEndpoint.class)
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnAvailableEndpoint(exposure = { EndpointExposure.WEB, EndpointExposure.CLOUD_FOUNDRY })
|
||||
public QuartzEndpointWebExtension quartzEndpointWebExtension(QuartzEndpoint endpoint) {
|
||||
return new QuartzEndpointWebExtension(endpoint);
|
||||
public QuartzEndpointWebExtension quartzEndpointWebExtension(QuartzEndpoint endpoint,
|
||||
QuartzEndpointProperties properties) {
|
||||
return new QuartzEndpointWebExtension(endpoint, properties.getShowValues(), properties.getRoles());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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.boot.actuate.autoconfigure.quartz;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.Show;
|
||||
import org.springframework.boot.actuate.quartz.QuartzEndpoint;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* Configuration properties for {@link QuartzEndpoint}.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @since 3.0.0
|
||||
*/
|
||||
@ConfigurationProperties("management.endpoint.quartz")
|
||||
public class QuartzEndpointProperties {
|
||||
|
||||
/**
|
||||
* When to show unsanitized job or trigger values.
|
||||
*/
|
||||
private Show showValues = Show.NEVER;
|
||||
|
||||
/**
|
||||
* Roles used to determine whether a user is authorized to be shown unsanitized job or
|
||||
* trigger values. When empty, all authenticated users are authorized.
|
||||
*/
|
||||
private Set<String> roles = new HashSet<>();
|
||||
|
||||
public Show getShowValues() {
|
||||
return this.showValues;
|
||||
}
|
||||
|
||||
public void setShowValues(Show showValues) {
|
||||
this.showValues = showValues;
|
||||
}
|
||||
|
||||
public Set<String> getRoles() {
|
||||
return this.roles;
|
||||
}
|
||||
|
||||
}
|
@ -30,30 +30,6 @@
|
||||
"description": "Whether to enable default metrics exporters.",
|
||||
"defaultValue": true
|
||||
},
|
||||
{
|
||||
"name": "management.endpoint.configprops.keys-to-sanitize",
|
||||
"defaultValue": [
|
||||
"password",
|
||||
"secret",
|
||||
"key",
|
||||
"token",
|
||||
".*credentials.*",
|
||||
"vcap_services",
|
||||
"sun.java.command"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "management.endpoint.env.keys-to-sanitize",
|
||||
"defaultValue": [
|
||||
"password",
|
||||
"secret",
|
||||
"key",
|
||||
"token",
|
||||
".*credentials.*",
|
||||
"vcap_services",
|
||||
"sun.java.command"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "management.endpoint.health.probes.add-additional-paths",
|
||||
"type": "java.lang.Boolean",
|
||||
|
@ -17,6 +17,7 @@
|
||||
package org.springframework.boot.actuate.autoconfigure.context.properties;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@ -24,6 +25,7 @@ import org.springframework.boot.actuate.context.properties.ConfigurationProperti
|
||||
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ApplicationConfigurationProperties;
|
||||
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpointWebExtension;
|
||||
import org.springframework.boot.actuate.endpoint.SanitizingFunction;
|
||||
import org.springframework.boot.actuate.endpoint.Show;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
@ -33,6 +35,7 @@ import org.springframework.boot.test.context.runner.ContextConsumer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@ -50,7 +53,7 @@ class ConfigurationPropertiesReportEndpointAutoConfigurationTests {
|
||||
void runShouldHaveEndpointBean() {
|
||||
this.contextRunner.withUserConfiguration(Config.class)
|
||||
.withPropertyValues("management.endpoints.web.exposure.include=configprops")
|
||||
.run(validateTestProperties("******", "654321"));
|
||||
.run(validateTestProperties("******", "******"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -60,24 +63,42 @@ class ConfigurationPropertiesReportEndpointAutoConfigurationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void keysToSanitizeCanBeConfiguredViaTheEnvironment() {
|
||||
@SuppressWarnings("unchecked")
|
||||
void rolesCanBeConfiguredViaTheEnvironment() {
|
||||
this.contextRunner.withUserConfiguration(Config.class)
|
||||
.withPropertyValues("management.endpoint.configprops.keys-to-sanitize: .*pass.*, property")
|
||||
.withPropertyValues("management.endpoints.web.exposure.include=configprops")
|
||||
.run(validateTestProperties("******", "******"));
|
||||
.withPropertyValues("management.endpoint.configprops.roles: test")
|
||||
.withPropertyValues("management.endpoints.web.exposure.include=configprops").run((context) -> {
|
||||
assertThat(context).hasSingleBean(ConfigurationPropertiesReportEndpointWebExtension.class);
|
||||
ConfigurationPropertiesReportEndpointWebExtension endpoint = context
|
||||
.getBean(ConfigurationPropertiesReportEndpointWebExtension.class);
|
||||
Set<String> roles = (Set<String>) ReflectionTestUtils.getField(endpoint, "roles");
|
||||
assertThat(roles.contains("test")).isTrue();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void additionalKeysToSanitizeCanBeConfiguredViaTheEnvironment() {
|
||||
@SuppressWarnings("unchecked")
|
||||
void showValuesCanBeConfiguredViaTheEnvironment() {
|
||||
this.contextRunner.withUserConfiguration(Config.class)
|
||||
.withPropertyValues("management.endpoint.configprops.additional-keys-to-sanitize: property")
|
||||
.withPropertyValues("management.endpoints.web.exposure.include=configprops")
|
||||
.run(validateTestProperties("******", "******"));
|
||||
.withPropertyValues("management.endpoint.configprops.show-values: WHEN_AUTHORIZED")
|
||||
.withPropertyValues("management.endpoints.web.exposure.include=configprops").run((context) -> {
|
||||
assertThat(context).hasSingleBean(ConfigurationPropertiesReportEndpoint.class);
|
||||
assertThat(context).hasSingleBean(ConfigurationPropertiesReportEndpointWebExtension.class);
|
||||
ConfigurationPropertiesReportEndpointWebExtension webExtension = context
|
||||
.getBean(ConfigurationPropertiesReportEndpointWebExtension.class);
|
||||
ConfigurationPropertiesReportEndpoint endpoint = context
|
||||
.getBean(ConfigurationPropertiesReportEndpoint.class);
|
||||
Show showValuesWebExtension = (Show) ReflectionTestUtils.getField(webExtension, "showValues");
|
||||
assertThat(showValuesWebExtension).isEqualTo(Show.WHEN_AUTHORIZED);
|
||||
Show showValues = (Show) ReflectionTestUtils.getField(endpoint, "showValues");
|
||||
assertThat(showValues).isEqualTo(Show.WHEN_AUTHORIZED);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void customSanitizingFunctionsAreAppliedInOrder() {
|
||||
this.contextRunner.withUserConfiguration(Config.class, SanitizingFunctionConfiguration.class)
|
||||
this.contextRunner.withPropertyValues("management.endpoint.configprops.show-values: ALWAYS")
|
||||
.withUserConfiguration(Config.class, SanitizingFunctionConfiguration.class)
|
||||
.withPropertyValues("management.endpoints.web.exposure.include=configprops",
|
||||
"test.my-test-property=abc")
|
||||
.run(validateTestProperties("$$$111$$$", "$$$222$$$"));
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2021 the original author or authors.
|
||||
* Copyright 2012-2022 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.
|
||||
@ -16,9 +16,12 @@
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure.endpoint.web.documentation;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.Show;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
@ -80,7 +83,7 @@ class ConfigurationPropertiesReportEndpointDocumentationTests extends MockMvcEnd
|
||||
|
||||
@Bean
|
||||
ConfigurationPropertiesReportEndpoint endpoint() {
|
||||
return new ConfigurationPropertiesReportEndpoint();
|
||||
return new ConfigurationPropertiesReportEndpoint(Collections.emptyList(), Show.ALWAYS);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2022 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.
|
||||
@ -17,6 +17,7 @@
|
||||
package org.springframework.boot.actuate.autoconfigure.endpoint.web.documentation;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@ -27,6 +28,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.Show;
|
||||
import org.springframework.boot.actuate.env.EnvironmentEndpoint;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@ -148,7 +150,7 @@ class EnvironmentEndpointDocumentationTests extends MockMvcEndpointDocumentation
|
||||
&& !"Inlined Test Properties".equals(propertySource.getName());
|
||||
}
|
||||
|
||||
});
|
||||
}, Collections.emptyList(), Show.ALWAYS);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2021 the original author or authors.
|
||||
* Copyright 2012-2022 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.
|
||||
@ -51,6 +51,7 @@ import org.quartz.TriggerKey;
|
||||
import org.quartz.impl.matchers.GroupMatcher;
|
||||
import org.quartz.spi.OperableTrigger;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.Show;
|
||||
import org.springframework.boot.actuate.quartz.QuartzEndpoint;
|
||||
import org.springframework.boot.actuate.quartz.QuartzEndpointWebExtension;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
@ -456,12 +457,12 @@ class QuartzEndpointDocumentationTests extends MockMvcEndpointDocumentationTests
|
||||
|
||||
@Bean
|
||||
QuartzEndpoint endpoint(Scheduler scheduler) {
|
||||
return new QuartzEndpoint(scheduler);
|
||||
return new QuartzEndpoint(scheduler, Collections.emptyList());
|
||||
}
|
||||
|
||||
@Bean
|
||||
QuartzEndpointWebExtension endpointWebExtension(QuartzEndpoint endpoint) {
|
||||
return new QuartzEndpointWebExtension(endpoint);
|
||||
return new QuartzEndpointWebExtension(endpoint, Show.ALWAYS, Collections.emptySet());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -17,10 +17,12 @@
|
||||
package org.springframework.boot.actuate.autoconfigure.env;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.SanitizingFunction;
|
||||
import org.springframework.boot.actuate.endpoint.Show;
|
||||
import org.springframework.boot.actuate.env.EnvironmentEndpoint;
|
||||
import org.springframework.boot.actuate.env.EnvironmentEndpoint.EnvironmentDescriptor;
|
||||
import org.springframework.boot.actuate.env.EnvironmentEndpoint.PropertySourceDescriptor;
|
||||
@ -33,6 +35,7 @@ import org.springframework.boot.test.context.runner.ContextConsumer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@ -64,17 +67,10 @@ class EnvironmentEndpointAutoConfigurationTests {
|
||||
this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(EnvironmentEndpoint.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void keysToSanitizeCanBeConfiguredViaTheEnvironment() {
|
||||
this.contextRunner.withPropertyValues("management.endpoints.web.exposure.include=env")
|
||||
.withSystemProperties("dbPassword=123456", "apiKey=123456")
|
||||
.withPropertyValues("management.endpoint.env.keys-to-sanitize=.*pass.*")
|
||||
.run(validateSystemProperties("******", "123456"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void customSanitizingFunctionsAreAppliedInOrder() {
|
||||
this.contextRunner.withUserConfiguration(SanitizingFunctionConfiguration.class)
|
||||
.withPropertyValues("management.endpoint.env.show-values: WHEN_AUTHORIZED")
|
||||
.withPropertyValues("management.endpoints.web.exposure.include=env")
|
||||
.withSystemProperties("custom=123456", "password=123456").run((context) -> {
|
||||
assertThat(context).hasSingleBean(EnvironmentEndpoint.class);
|
||||
@ -88,11 +84,34 @@ class EnvironmentEndpointAutoConfigurationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void additionalKeysToSanitizeCanBeConfiguredViaTheEnvironment() {
|
||||
this.contextRunner.withPropertyValues("management.endpoints.web.exposure.include=env")
|
||||
.withSystemProperties("dbPassword=123456", "apiKey=123456")
|
||||
.withPropertyValues("management.endpoint.env.additional-keys-to-sanitize=key")
|
||||
.run(validateSystemProperties("******", "******"));
|
||||
@SuppressWarnings("unchecked")
|
||||
void rolesCanBeConfiguredViaTheEnvironment() {
|
||||
this.contextRunner.withPropertyValues("management.endpoint.env.roles: test")
|
||||
.withPropertyValues("management.endpoints.web.exposure.include=env")
|
||||
.withSystemProperties("dbPassword=123456", "apiKey=123456").run((context) -> {
|
||||
assertThat(context).hasSingleBean(EnvironmentEndpointWebExtension.class);
|
||||
EnvironmentEndpointWebExtension endpoint = context.getBean(EnvironmentEndpointWebExtension.class);
|
||||
Set<String> roles = (Set<String>) ReflectionTestUtils.getField(endpoint, "roles");
|
||||
assertThat(roles.contains("test")).isTrue();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void showValuesCanBeConfiguredViaTheEnvironment() {
|
||||
this.contextRunner.withPropertyValues("management.endpoint.env.show-values: WHEN_AUTHORIZED")
|
||||
.withPropertyValues("management.endpoints.web.exposure.include=env")
|
||||
.withSystemProperties("dbPassword=123456", "apiKey=123456").run((context) -> {
|
||||
assertThat(context).hasSingleBean(EnvironmentEndpoint.class);
|
||||
assertThat(context).hasSingleBean(EnvironmentEndpointWebExtension.class);
|
||||
EnvironmentEndpointWebExtension webExtension = context
|
||||
.getBean(EnvironmentEndpointWebExtension.class);
|
||||
EnvironmentEndpoint endpoint = context.getBean(EnvironmentEndpoint.class);
|
||||
Show showValuesWebExtension = (Show) ReflectionTestUtils.getField(webExtension, "showValues");
|
||||
assertThat(showValuesWebExtension).isEqualTo(Show.WHEN_AUTHORIZED);
|
||||
Show showValues = (Show) ReflectionTestUtils.getField(endpoint, "showValues");
|
||||
assertThat(showValues).isEqualTo(Show.WHEN_AUTHORIZED);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2021 the original author or authors.
|
||||
* Copyright 2012-2022 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.
|
||||
@ -25,8 +25,8 @@ import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.health.HealthProperties.Show;
|
||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||
import org.springframework.boot.actuate.endpoint.Show;
|
||||
import org.springframework.boot.actuate.health.HttpCodeStatusMapper;
|
||||
import org.springframework.boot.actuate.health.StatusAggregator;
|
||||
import org.springframework.security.core.Authentication;
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2021 the original author or authors.
|
||||
* Copyright 2012-2022 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.
|
||||
@ -16,15 +16,20 @@
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure.quartz;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.quartz.Scheduler;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.Show;
|
||||
import org.springframework.boot.actuate.quartz.QuartzEndpoint;
|
||||
import org.springframework.boot.actuate.quartz.QuartzEndpointWebExtension;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
@ -81,6 +86,34 @@ class QuartzEndpointAutoConfigurationTests {
|
||||
.doesNotHaveBean(QuartzEndpointWebExtension.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void rolesCanBeConfiguredViaTheEnvironment() {
|
||||
this.contextRunner.withBean(Scheduler.class, () -> mock(Scheduler.class))
|
||||
.withPropertyValues("management.endpoint.quartz.roles: test")
|
||||
.withPropertyValues("management.endpoints.web.exposure.include=quartz")
|
||||
.withSystemProperties("dbPassword=123456", "apiKey=123456").run((context) -> {
|
||||
assertThat(context).hasSingleBean(QuartzEndpointWebExtension.class);
|
||||
QuartzEndpointWebExtension endpoint = context.getBean(QuartzEndpointWebExtension.class);
|
||||
Set<String> roles = (Set<String>) ReflectionTestUtils.getField(endpoint, "roles");
|
||||
assertThat(roles.contains("test")).isTrue();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void showValuesCanBeConfiguredViaTheEnvironment() {
|
||||
this.contextRunner.withBean(Scheduler.class, () -> mock(Scheduler.class))
|
||||
.withPropertyValues("management.endpoint.quartz.show-values: WHEN_AUTHORIZED")
|
||||
.withPropertyValues("management.endpoints.web.exposure.include=quartz")
|
||||
.withSystemProperties("dbPassword=123456", "apiKey=123456").run((context) -> {
|
||||
assertThat(context).hasSingleBean(QuartzEndpointWebExtension.class);
|
||||
QuartzEndpointWebExtension webExtension = context.getBean(QuartzEndpointWebExtension.class);
|
||||
Show showValuesWebExtension = (Show) ReflectionTestUtils.getField(webExtension, "showValues");
|
||||
assertThat(showValuesWebExtension).isEqualTo(Show.WHEN_AUTHORIZED);
|
||||
});
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class CustomEndpointConfiguration {
|
||||
|
||||
@ -94,7 +127,7 @@ class QuartzEndpointAutoConfigurationTests {
|
||||
private static final class CustomEndpoint extends QuartzEndpoint {
|
||||
|
||||
private CustomEndpoint() {
|
||||
super(mock(Scheduler.class));
|
||||
super(mock(Scheduler.class), Collections.emptyList());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -55,6 +55,7 @@ import org.springframework.beans.BeansException;
|
||||
import org.springframework.boot.actuate.endpoint.SanitizableData;
|
||||
import org.springframework.boot.actuate.endpoint.Sanitizer;
|
||||
import org.springframework.boot.actuate.endpoint.SanitizingFunction;
|
||||
import org.springframework.boot.actuate.endpoint.Show;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Selector;
|
||||
@ -83,11 +84,11 @@ import org.springframework.util.StringUtils;
|
||||
* {@link ConfigurationProperties @ConfigurationProperties} annotated beans.
|
||||
*
|
||||
* <p>
|
||||
* To protect sensitive information from being exposed, certain property values are masked
|
||||
* if their names end with a set of configurable values (default "password" and "secret").
|
||||
* Configure property names by using
|
||||
* {@code management.endpoint.configprops.keys-to-sanitize} in your Spring Boot
|
||||
* application configuration.
|
||||
* To protect sensitive information from being exposed, all property values are masked by
|
||||
* default. To configure when property values should be shown, use
|
||||
* {@code management.endpoint.configprops.show-values} and
|
||||
* {@code management.endpoint.configprops.roles} in your Spring Boot application
|
||||
* configuration.
|
||||
*
|
||||
* @author Christian Dupuis
|
||||
* @author Dave Syer
|
||||
@ -104,16 +105,15 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
|
||||
|
||||
private final Sanitizer sanitizer;
|
||||
|
||||
private final Show showValues;
|
||||
|
||||
private ApplicationContext context;
|
||||
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
public ConfigurationPropertiesReportEndpoint() {
|
||||
this(Collections.emptyList());
|
||||
}
|
||||
|
||||
public ConfigurationPropertiesReportEndpoint(Iterable<SanitizingFunction> sanitizingFunctions) {
|
||||
public ConfigurationPropertiesReportEndpoint(Iterable<SanitizingFunction> sanitizingFunctions, Show showValues) {
|
||||
this.sanitizer = new Sanitizer(sanitizingFunctions);
|
||||
this.showValues = showValues;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -121,31 +121,35 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public void setKeysToSanitize(String... keysToSanitize) {
|
||||
this.sanitizer.setKeysToSanitize(keysToSanitize);
|
||||
}
|
||||
|
||||
public void keysToSanitize(String... keysToSanitize) {
|
||||
this.sanitizer.keysToSanitize(keysToSanitize);
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public ApplicationConfigurationProperties configurationProperties() {
|
||||
return extract(this.context, (bean) -> true);
|
||||
boolean showUnsanitized = this.showValues.isShown(true);
|
||||
return getConfigurationProperties(showUnsanitized);
|
||||
}
|
||||
|
||||
ApplicationConfigurationProperties getConfigurationProperties(boolean showUnsanitized) {
|
||||
return getConfigurationProperties(this.context, (bean) -> true, showUnsanitized);
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public ApplicationConfigurationProperties configurationPropertiesWithPrefix(@Selector String prefix) {
|
||||
return extract(this.context, (bean) -> bean.getAnnotation().prefix().startsWith(prefix));
|
||||
boolean showUnsanitized = this.showValues.isShown(true);
|
||||
return getConfigurationProperties(prefix, showUnsanitized);
|
||||
}
|
||||
|
||||
private ApplicationConfigurationProperties extract(ApplicationContext context,
|
||||
Predicate<ConfigurationPropertiesBean> beanFilterPredicate) {
|
||||
ApplicationConfigurationProperties getConfigurationProperties(String prefix, boolean showUnsanitized) {
|
||||
return getConfigurationProperties(this.context, (bean) -> bean.getAnnotation().prefix().startsWith(prefix),
|
||||
showUnsanitized);
|
||||
}
|
||||
|
||||
private ApplicationConfigurationProperties getConfigurationProperties(ApplicationContext context,
|
||||
Predicate<ConfigurationPropertiesBean> beanFilterPredicate, boolean showUnsanitized) {
|
||||
ObjectMapper mapper = getObjectMapper();
|
||||
Map<String, ContextConfigurationProperties> contexts = new HashMap<>();
|
||||
ApplicationContext target = context;
|
||||
|
||||
while (target != null) {
|
||||
contexts.put(target.getId(), describeBeans(mapper, target, beanFilterPredicate));
|
||||
contexts.put(target.getId(), describeBeans(mapper, target, beanFilterPredicate, showUnsanitized));
|
||||
target = target.getParent();
|
||||
}
|
||||
return new ApplicationConfigurationProperties(contexts);
|
||||
@ -196,20 +200,21 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
|
||||
}
|
||||
|
||||
private ContextConfigurationProperties describeBeans(ObjectMapper mapper, ApplicationContext context,
|
||||
Predicate<ConfigurationPropertiesBean> beanFilterPredicate) {
|
||||
Predicate<ConfigurationPropertiesBean> beanFilterPredicate, boolean showUnsanitized) {
|
||||
Map<String, ConfigurationPropertiesBean> beans = ConfigurationPropertiesBean.getAll(context);
|
||||
Map<String, ConfigurationPropertiesBeanDescriptor> descriptors = beans.values().stream()
|
||||
.filter(beanFilterPredicate)
|
||||
.collect(Collectors.toMap(ConfigurationPropertiesBean::getName, (bean) -> describeBean(mapper, bean)));
|
||||
.filter(beanFilterPredicate).collect(Collectors.toMap(ConfigurationPropertiesBean::getName,
|
||||
(bean) -> describeBean(mapper, bean, showUnsanitized)));
|
||||
return new ContextConfigurationProperties(descriptors,
|
||||
(context.getParent() != null) ? context.getParent().getId() : null);
|
||||
}
|
||||
|
||||
private ConfigurationPropertiesBeanDescriptor describeBean(ObjectMapper mapper, ConfigurationPropertiesBean bean) {
|
||||
private ConfigurationPropertiesBeanDescriptor describeBean(ObjectMapper mapper, ConfigurationPropertiesBean bean,
|
||||
boolean showUnsanitized) {
|
||||
String prefix = bean.getAnnotation().prefix();
|
||||
Map<String, Object> serialized = safeSerialize(mapper, bean.getInstance(), prefix);
|
||||
Map<String, Object> properties = sanitize(prefix, serialized);
|
||||
Map<String, Object> inputs = getInputs(prefix, serialized);
|
||||
Map<String, Object> properties = sanitize(prefix, serialized, showUnsanitized);
|
||||
Map<String, Object> inputs = getInputs(prefix, serialized, showUnsanitized);
|
||||
return new ConfigurationPropertiesBeanDescriptor(prefix, properties, inputs);
|
||||
}
|
||||
|
||||
@ -236,35 +241,36 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
|
||||
* information.
|
||||
* @param prefix the property prefix
|
||||
* @param map the source map
|
||||
* @param showUnsanitized whether to show the unsanitized values
|
||||
* @return the sanitized map
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<String, Object> sanitize(String prefix, Map<String, Object> map) {
|
||||
private Map<String, Object> sanitize(String prefix, Map<String, Object> map, boolean showUnsanitized) {
|
||||
map.forEach((key, value) -> {
|
||||
String qualifiedKey = getQualifiedKey(prefix, key);
|
||||
if (value instanceof Map) {
|
||||
map.put(key, sanitize(qualifiedKey, (Map<String, Object>) value));
|
||||
map.put(key, sanitize(qualifiedKey, (Map<String, Object>) value, showUnsanitized));
|
||||
}
|
||||
else if (value instanceof List) {
|
||||
map.put(key, sanitize(qualifiedKey, (List<Object>) value));
|
||||
map.put(key, sanitize(qualifiedKey, (List<Object>) value, showUnsanitized));
|
||||
}
|
||||
else {
|
||||
map.put(key, sanitizeWithPropertySourceIfPresent(qualifiedKey, value));
|
||||
map.put(key, sanitizeWithPropertySourceIfPresent(qualifiedKey, value, showUnsanitized));
|
||||
}
|
||||
});
|
||||
return map;
|
||||
}
|
||||
|
||||
private Object sanitizeWithPropertySourceIfPresent(String qualifiedKey, Object value) {
|
||||
private Object sanitizeWithPropertySourceIfPresent(String qualifiedKey, Object value, boolean showUnsanitized) {
|
||||
ConfigurationPropertyName currentName = getCurrentName(qualifiedKey);
|
||||
ConfigurationProperty candidate = getCandidate(currentName);
|
||||
PropertySource<?> propertySource = getPropertySource(candidate);
|
||||
if (propertySource != null) {
|
||||
SanitizableData data = new SanitizableData(propertySource, qualifiedKey, value);
|
||||
return this.sanitizer.sanitize(data);
|
||||
return this.sanitizer.sanitize(data, showUnsanitized);
|
||||
}
|
||||
SanitizableData data = new SanitizableData(null, qualifiedKey, value);
|
||||
return this.sanitizer.sanitize(data);
|
||||
return this.sanitizer.sanitize(data, showUnsanitized);
|
||||
}
|
||||
|
||||
private PropertySource<?> getPropertySource(ConfigurationProperty configurationProperty) {
|
||||
@ -293,69 +299,69 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<Object> sanitize(String prefix, List<Object> list) {
|
||||
private List<Object> sanitize(String prefix, List<Object> list, boolean showUnsanitized) {
|
||||
List<Object> sanitized = new ArrayList<>();
|
||||
int index = 0;
|
||||
for (Object item : list) {
|
||||
String name = prefix + "[" + index++ + "]";
|
||||
if (item instanceof Map) {
|
||||
sanitized.add(sanitize(name, (Map<String, Object>) item));
|
||||
sanitized.add(sanitize(name, (Map<String, Object>) item, showUnsanitized));
|
||||
}
|
||||
else if (item instanceof List) {
|
||||
sanitized.add(sanitize(name, (List<Object>) item));
|
||||
sanitized.add(sanitize(name, (List<Object>) item, showUnsanitized));
|
||||
}
|
||||
else {
|
||||
sanitized.add(sanitizeWithPropertySourceIfPresent(name, item));
|
||||
sanitized.add(sanitizeWithPropertySourceIfPresent(name, item, showUnsanitized));
|
||||
}
|
||||
}
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<String, Object> getInputs(String prefix, Map<String, Object> map) {
|
||||
private Map<String, Object> getInputs(String prefix, Map<String, Object> map, boolean showUnsanitized) {
|
||||
Map<String, Object> augmented = new LinkedHashMap<>(map);
|
||||
map.forEach((key, value) -> {
|
||||
String qualifiedKey = getQualifiedKey(prefix, key);
|
||||
if (value instanceof Map) {
|
||||
augmented.put(key, getInputs(qualifiedKey, (Map<String, Object>) value));
|
||||
augmented.put(key, getInputs(qualifiedKey, (Map<String, Object>) value, showUnsanitized));
|
||||
}
|
||||
else if (value instanceof List) {
|
||||
augmented.put(key, getInputs(qualifiedKey, (List<Object>) value));
|
||||
augmented.put(key, getInputs(qualifiedKey, (List<Object>) value, showUnsanitized));
|
||||
}
|
||||
else {
|
||||
augmented.put(key, applyInput(qualifiedKey));
|
||||
augmented.put(key, applyInput(qualifiedKey, showUnsanitized));
|
||||
}
|
||||
});
|
||||
return augmented;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<Object> getInputs(String prefix, List<Object> list) {
|
||||
private List<Object> getInputs(String prefix, List<Object> list, boolean showUnsanitized) {
|
||||
List<Object> augmented = new ArrayList<>();
|
||||
int index = 0;
|
||||
for (Object item : list) {
|
||||
String name = prefix + "[" + index++ + "]";
|
||||
if (item instanceof Map) {
|
||||
augmented.add(getInputs(name, (Map<String, Object>) item));
|
||||
augmented.add(getInputs(name, (Map<String, Object>) item, showUnsanitized));
|
||||
}
|
||||
else if (item instanceof List) {
|
||||
augmented.add(getInputs(name, (List<Object>) item));
|
||||
augmented.add(getInputs(name, (List<Object>) item, showUnsanitized));
|
||||
}
|
||||
else {
|
||||
augmented.add(applyInput(name));
|
||||
augmented.add(applyInput(name, showUnsanitized));
|
||||
}
|
||||
}
|
||||
return augmented;
|
||||
}
|
||||
|
||||
private Map<String, Object> applyInput(String qualifiedKey) {
|
||||
private Map<String, Object> applyInput(String qualifiedKey, boolean showUnsanitized) {
|
||||
ConfigurationPropertyName currentName = getCurrentName(qualifiedKey);
|
||||
ConfigurationProperty candidate = getCandidate(currentName);
|
||||
PropertySource<?> propertySource = getPropertySource(candidate);
|
||||
if (propertySource != null) {
|
||||
Object value = stringifyIfNecessary(candidate.getValue());
|
||||
SanitizableData data = new SanitizableData(propertySource, currentName.toString(), value);
|
||||
return getInput(candidate, this.sanitizer.sanitize(data));
|
||||
return getInput(candidate, this.sanitizer.sanitize(data, showUnsanitized));
|
||||
}
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
@ -550,7 +556,7 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
|
||||
|
||||
private final Map<String, ContextConfigurationProperties> contexts;
|
||||
|
||||
private ApplicationConfigurationProperties(Map<String, ContextConfigurationProperties> contexts) {
|
||||
ApplicationConfigurationProperties(Map<String, ContextConfigurationProperties> contexts) {
|
||||
this.contexts = contexts;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2021 the original author or authors.
|
||||
* Copyright 2012-2022 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.
|
||||
@ -16,7 +16,11 @@
|
||||
|
||||
package org.springframework.boot.actuate.context.properties;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ApplicationConfigurationProperties;
|
||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||
import org.springframework.boot.actuate.endpoint.Show;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Selector;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
||||
@ -34,15 +38,29 @@ public class ConfigurationPropertiesReportEndpointWebExtension {
|
||||
|
||||
private final ConfigurationPropertiesReportEndpoint delegate;
|
||||
|
||||
public ConfigurationPropertiesReportEndpointWebExtension(ConfigurationPropertiesReportEndpoint delegate) {
|
||||
private final Show showValues;
|
||||
|
||||
private final Set<String> roles;
|
||||
|
||||
public ConfigurationPropertiesReportEndpointWebExtension(ConfigurationPropertiesReportEndpoint delegate,
|
||||
Show showValues, Set<String> roles) {
|
||||
this.delegate = delegate;
|
||||
this.showValues = showValues;
|
||||
this.roles = roles;
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public ApplicationConfigurationProperties configurationProperties(SecurityContext securityContext) {
|
||||
boolean showUnsanitized = this.showValues.isShown(securityContext, this.roles);
|
||||
return this.delegate.getConfigurationProperties(showUnsanitized);
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public WebEndpointResponse<ApplicationConfigurationProperties> configurationPropertiesWithPrefix(
|
||||
@Selector String prefix) {
|
||||
ApplicationConfigurationProperties configurationProperties = this.delegate
|
||||
.configurationPropertiesWithPrefix(prefix);
|
||||
SecurityContext securityContext, @Selector String prefix) {
|
||||
boolean showUnsanitized = this.showValues.isShown(securityContext, this.roles);
|
||||
ApplicationConfigurationProperties configurationProperties = this.delegate.getConfigurationProperties(prefix,
|
||||
showUnsanitized);
|
||||
boolean foundMatchingBeans = configurationProperties.getContexts().values().stream()
|
||||
.anyMatch((context) -> !context.getBeans().isEmpty());
|
||||
return (foundMatchingBeans) ? new WebEndpointResponse<>(configurationProperties, WebEndpointResponse.STATUS_OK)
|
||||
|
@ -17,17 +17,8 @@
|
||||
package org.springframework.boot.actuate.endpoint;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Strategy that should be used by endpoint implementations to sanitize potentially
|
||||
@ -46,148 +37,40 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
public class Sanitizer {
|
||||
|
||||
private static final String[] REGEX_PARTS = { "*", "$", "^", "+" };
|
||||
|
||||
private static final Set<String> DEFAULT_KEYS_TO_SANITIZE = new LinkedHashSet<>(
|
||||
Arrays.asList("password", "secret", "key", "token", ".*credentials.*", "vcap_services",
|
||||
"^vcap\\.services.*$", "sun.java.command", "^spring[._]application[._]json$"));
|
||||
|
||||
private static final Set<String> URI_USERINFO_KEYS = new LinkedHashSet<>(
|
||||
Arrays.asList("uri", "uris", "url", "urls", "address", "addresses"));
|
||||
|
||||
private static final Pattern URI_USERINFO_PATTERN = Pattern
|
||||
.compile("^\\[?[A-Za-z][A-Za-z0-9\\+\\.\\-]+://.+:(.*)@.+$");
|
||||
|
||||
private Pattern[] keysToSanitize;
|
||||
|
||||
private final List<SanitizingFunction> sanitizingFunctions = new ArrayList<>();
|
||||
|
||||
static {
|
||||
DEFAULT_KEYS_TO_SANITIZE.addAll(URI_USERINFO_KEYS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link Sanitizer} instance with a default set of keys to sanitize.
|
||||
* Create a new {@link Sanitizer} instance.
|
||||
*/
|
||||
public Sanitizer() {
|
||||
this(DEFAULT_KEYS_TO_SANITIZE.toArray(new String[0]));
|
||||
this(Collections.emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link Sanitizer} instance with specific keys to sanitize.
|
||||
* @param keysToSanitize the keys to sanitize
|
||||
*/
|
||||
public Sanitizer(String... keysToSanitize) {
|
||||
this(Collections.emptyList(), keysToSanitize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link Sanitizer} instance with a default set of keys to sanitize and
|
||||
* additional sanitizing functions.
|
||||
* Create a new {@link Sanitizer} instance with sanitizing functions.
|
||||
* @param sanitizingFunctions the sanitizing functions to apply
|
||||
* @since 2.6.0
|
||||
*/
|
||||
public Sanitizer(Iterable<SanitizingFunction> sanitizingFunctions) {
|
||||
this(sanitizingFunctions, DEFAULT_KEYS_TO_SANITIZE.toArray(new String[0]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link Sanitizer} instance with specific keys to sanitize and
|
||||
* additional sanitizing functions.
|
||||
* @param sanitizingFunctions the sanitizing functions to apply
|
||||
* @param keysToSanitize the keys to sanitize
|
||||
* @since 2.6.0
|
||||
*/
|
||||
public Sanitizer(Iterable<SanitizingFunction> sanitizingFunctions, String... keysToSanitize) {
|
||||
sanitizingFunctions.forEach(this.sanitizingFunctions::add);
|
||||
this.sanitizingFunctions.add(getDefaultSanitizingFunction());
|
||||
setKeysToSanitize(keysToSanitize);
|
||||
}
|
||||
|
||||
private SanitizingFunction getDefaultSanitizingFunction() {
|
||||
return (data) -> {
|
||||
Object sanitizedValue = sanitize(data.getKey(), data.getValue());
|
||||
return data.withValue(sanitizedValue);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the keys that should be sanitized, overwriting any existing configuration. Keys
|
||||
* can be simple strings that the property ends with or regular expressions.
|
||||
* @param keysToSanitize the keys to sanitize
|
||||
*/
|
||||
public void setKeysToSanitize(String... keysToSanitize) {
|
||||
Assert.notNull(keysToSanitize, "KeysToSanitize must not be null");
|
||||
this.keysToSanitize = new Pattern[keysToSanitize.length];
|
||||
for (int i = 0; i < keysToSanitize.length; i++) {
|
||||
this.keysToSanitize[i] = getPattern(keysToSanitize[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds keys that should be sanitized. Keys can be simple strings that the property
|
||||
* ends with or regular expressions.
|
||||
* @param keysToSanitize the keys to sanitize
|
||||
* @since 2.5.0
|
||||
*/
|
||||
public void keysToSanitize(String... keysToSanitize) {
|
||||
Assert.notNull(keysToSanitize, "KeysToSanitize must not be null");
|
||||
int existingKeys = this.keysToSanitize.length;
|
||||
this.keysToSanitize = Arrays.copyOf(this.keysToSanitize, this.keysToSanitize.length + keysToSanitize.length);
|
||||
for (int i = 0; i < keysToSanitize.length; i++) {
|
||||
this.keysToSanitize[i + existingKeys] = getPattern(keysToSanitize[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private Pattern getPattern(String value) {
|
||||
if (isRegex(value)) {
|
||||
return Pattern.compile(value, Pattern.CASE_INSENSITIVE);
|
||||
}
|
||||
return Pattern.compile(".*" + value + "$", Pattern.CASE_INSENSITIVE);
|
||||
}
|
||||
|
||||
private boolean isRegex(String value) {
|
||||
for (String part : REGEX_PARTS) {
|
||||
if (value.contains(part)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize the given value if necessary.
|
||||
* @param key the key to sanitize
|
||||
* @param value the value
|
||||
* @return the potentially sanitized value
|
||||
*/
|
||||
public Object sanitize(String key, Object value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
for (Pattern pattern : this.keysToSanitize) {
|
||||
if (pattern.matcher(key).matches()) {
|
||||
if (keyIsUriWithUserInfo(pattern)) {
|
||||
return sanitizeUris(value.toString());
|
||||
}
|
||||
return SanitizableData.SANITIZED_VALUE;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize the value from the given {@link SanitizableData} using the available
|
||||
* {@link SanitizingFunction}s.
|
||||
* @param data the sanitizable data
|
||||
* @param showUnsanitized whether to show the unsanitized values or not
|
||||
* @return the potentially updated data
|
||||
* @since 2.6.0
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public Object sanitize(SanitizableData data) {
|
||||
public Object sanitize(SanitizableData data, boolean showUnsanitized) {
|
||||
Object value = data.getValue();
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
if (!showUnsanitized) {
|
||||
return SanitizableData.SANITIZED_VALUE;
|
||||
}
|
||||
for (SanitizingFunction sanitizingFunction : this.sanitizingFunctions) {
|
||||
data = sanitizingFunction.apply(data);
|
||||
Object sanitizedValue = data.getValue();
|
||||
@ -198,26 +81,4 @@ public class Sanitizer {
|
||||
return value;
|
||||
}
|
||||
|
||||
private boolean keyIsUriWithUserInfo(Pattern pattern) {
|
||||
for (String uriKey : URI_USERINFO_KEYS) {
|
||||
if (pattern.matcher(uriKey).matches()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private Object sanitizeUris(String value) {
|
||||
return Arrays.stream(value.split(",")).map(this::sanitizeUri).collect(Collectors.joining(","));
|
||||
}
|
||||
|
||||
private String sanitizeUri(String value) {
|
||||
Matcher matcher = URI_USERINFO_PATTERN.matcher(value);
|
||||
String password = matcher.matches() ? matcher.group(1) : null;
|
||||
if (password != null) {
|
||||
return StringUtils.replace(value, ":" + password + "@", ":" + SanitizableData.SANITIZED_VALUE + "@");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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.boot.actuate.endpoint;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* Options for showing data in endpoint responses.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public enum Show {
|
||||
|
||||
/**
|
||||
* Never show the item in the response.
|
||||
*/
|
||||
NEVER,
|
||||
|
||||
/**
|
||||
* Show the item in the response when accessed by an authorized user.
|
||||
*/
|
||||
WHEN_AUTHORIZED,
|
||||
|
||||
/**
|
||||
* Always show the item in the response.
|
||||
*/
|
||||
ALWAYS;
|
||||
|
||||
/**
|
||||
* Return if data should be shown when no {@link SecurityContext} is available.
|
||||
* @param unauthorizedResult the result to used for an unauthorized user
|
||||
* @return if data should be shown
|
||||
*/
|
||||
public boolean isShown(boolean unauthorizedResult) {
|
||||
return switch (this) {
|
||||
case NEVER -> false;
|
||||
case ALWAYS -> true;
|
||||
case WHEN_AUTHORIZED -> unauthorizedResult;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if data should be shown.
|
||||
* @param securityContext the security context
|
||||
* @param roles the required roles
|
||||
* @return if data should be shown
|
||||
*/
|
||||
public boolean isShown(SecurityContext securityContext, Collection<String> roles) {
|
||||
return switch (this) {
|
||||
case NEVER -> false;
|
||||
case ALWAYS -> true;
|
||||
case WHEN_AUTHORIZED -> isAuthorized(securityContext, roles);
|
||||
};
|
||||
}
|
||||
|
||||
private boolean isAuthorized(SecurityContext securityContext, Collection<String> roles) {
|
||||
Principal principal = securityContext.getPrincipal();
|
||||
if (principal == null) {
|
||||
return false;
|
||||
}
|
||||
if (CollectionUtils.isEmpty(roles)) {
|
||||
return true;
|
||||
}
|
||||
boolean checkAuthorities = isSpringSecurityAuthentication(principal);
|
||||
for (String role : roles) {
|
||||
if (securityContext.isUserInRole(role)) {
|
||||
return true;
|
||||
}
|
||||
if (checkAuthorities) {
|
||||
Authentication authentication = (Authentication) principal;
|
||||
for (GrantedAuthority authority : authentication.getAuthorities()) {
|
||||
String name = authority.getAuthority();
|
||||
if (role.equals(name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isSpringSecurityAuthentication(Principal principal) {
|
||||
return ClassUtils.isPresent("org.springframework.security.core.Authentication", null)
|
||||
&& (principal instanceof Authentication);
|
||||
}
|
||||
|
||||
}
|
@ -18,7 +18,6 @@ package org.springframework.boot.actuate.env;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -31,6 +30,7 @@ import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import org.springframework.boot.actuate.endpoint.SanitizableData;
|
||||
import org.springframework.boot.actuate.endpoint.Sanitizer;
|
||||
import org.springframework.boot.actuate.endpoint.SanitizingFunction;
|
||||
import org.springframework.boot.actuate.endpoint.Show;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Selector;
|
||||
@ -48,9 +48,7 @@ import org.springframework.core.env.PropertySource;
|
||||
import org.springframework.core.env.StandardEnvironment;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.PropertyPlaceholderHelper;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.util.SystemPropertyUtils;
|
||||
|
||||
/**
|
||||
* {@link Endpoint @Endpoint} to expose {@link ConfigurableEnvironment environment}
|
||||
@ -71,50 +69,48 @@ public class EnvironmentEndpoint {
|
||||
|
||||
private final Environment environment;
|
||||
|
||||
public EnvironmentEndpoint(Environment environment) {
|
||||
this(environment, Collections.emptyList());
|
||||
}
|
||||
private final Show showValues;
|
||||
|
||||
public EnvironmentEndpoint(Environment environment, Iterable<SanitizingFunction> sanitizingFunctions) {
|
||||
public EnvironmentEndpoint(Environment environment, Iterable<SanitizingFunction> sanitizingFunctions,
|
||||
Show showValues) {
|
||||
this.environment = environment;
|
||||
this.sanitizer = new Sanitizer(sanitizingFunctions);
|
||||
}
|
||||
|
||||
public void setKeysToSanitize(String... keysToSanitize) {
|
||||
this.sanitizer.setKeysToSanitize(keysToSanitize);
|
||||
}
|
||||
|
||||
public void keysToSanitize(String... keysToSanitize) {
|
||||
this.sanitizer.keysToSanitize(keysToSanitize);
|
||||
this.showValues = showValues;
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public EnvironmentDescriptor environment(@Nullable String pattern) {
|
||||
boolean showUnsanitized = this.showValues.isShown(true);
|
||||
return getEnvironmentDescriptor(pattern, showUnsanitized);
|
||||
}
|
||||
|
||||
EnvironmentDescriptor getEnvironmentDescriptor(String pattern, boolean showUnsanitized) {
|
||||
if (StringUtils.hasText(pattern)) {
|
||||
return getEnvironmentDescriptor(Pattern.compile(pattern).asPredicate());
|
||||
return getEnvironmentDescriptor(Pattern.compile(pattern).asPredicate(), showUnsanitized);
|
||||
}
|
||||
return getEnvironmentDescriptor((name) -> true);
|
||||
return getEnvironmentDescriptor((name) -> true, showUnsanitized);
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public EnvironmentEntryDescriptor environmentEntry(@Selector String toMatch) {
|
||||
return getEnvironmentEntryDescriptor(toMatch);
|
||||
}
|
||||
|
||||
private EnvironmentDescriptor getEnvironmentDescriptor(Predicate<String> propertyNamePredicate) {
|
||||
PlaceholdersResolver resolver = getResolver();
|
||||
private EnvironmentDescriptor getEnvironmentDescriptor(Predicate<String> propertyNamePredicate,
|
||||
boolean showUnsanitized) {
|
||||
List<PropertySourceDescriptor> propertySources = new ArrayList<>();
|
||||
getPropertySourcesAsMap().forEach((sourceName, source) -> {
|
||||
if (source instanceof EnumerablePropertySource) {
|
||||
propertySources.add(describeSource(sourceName, (EnumerablePropertySource<?>) source, resolver,
|
||||
propertyNamePredicate));
|
||||
propertySources.add(describeSource(sourceName, (EnumerablePropertySource<?>) source,
|
||||
propertyNamePredicate, showUnsanitized));
|
||||
}
|
||||
});
|
||||
return new EnvironmentDescriptor(Arrays.asList(this.environment.getActiveProfiles()), propertySources);
|
||||
}
|
||||
|
||||
private EnvironmentEntryDescriptor getEnvironmentEntryDescriptor(String propertyName) {
|
||||
Map<String, PropertyValueDescriptor> descriptors = getPropertySourceDescriptors(propertyName);
|
||||
@ReadOperation
|
||||
public EnvironmentEntryDescriptor environmentEntry(@Selector String toMatch) {
|
||||
boolean showUnsanitized = this.showValues.isShown(true);
|
||||
return getEnvironmentEntryDescriptor(toMatch, showUnsanitized);
|
||||
}
|
||||
|
||||
EnvironmentEntryDescriptor getEnvironmentEntryDescriptor(String propertyName, boolean showUnsanitized) {
|
||||
Map<String, PropertyValueDescriptor> descriptors = getPropertySourceDescriptors(propertyName, showUnsanitized);
|
||||
PropertySummaryDescriptor summary = getPropertySummaryDescriptor(descriptors);
|
||||
return new EnvironmentEntryDescriptor(summary, Arrays.asList(this.environment.getActiveProfiles()),
|
||||
toPropertySourceDescriptors(descriptors));
|
||||
@ -136,35 +132,31 @@ public class EnvironmentEndpoint {
|
||||
return null;
|
||||
}
|
||||
|
||||
private Map<String, PropertyValueDescriptor> getPropertySourceDescriptors(String propertyName) {
|
||||
private Map<String, PropertyValueDescriptor> getPropertySourceDescriptors(String propertyName,
|
||||
boolean showUnsanitized) {
|
||||
Map<String, PropertyValueDescriptor> propertySources = new LinkedHashMap<>();
|
||||
PlaceholdersResolver resolver = getResolver();
|
||||
getPropertySourcesAsMap().forEach((sourceName, source) -> propertySources.put(sourceName,
|
||||
source.containsProperty(propertyName) ? describeValueOf(propertyName, source, resolver) : null));
|
||||
source.containsProperty(propertyName) ? describeValueOf(propertyName, source, showUnsanitized) : null));
|
||||
return propertySources;
|
||||
}
|
||||
|
||||
private PropertySourceDescriptor describeSource(String sourceName, EnumerablePropertySource<?> source,
|
||||
PlaceholdersResolver resolver, Predicate<String> namePredicate) {
|
||||
Predicate<String> namePredicate, boolean showUnsanitized) {
|
||||
Map<String, PropertyValueDescriptor> properties = new LinkedHashMap<>();
|
||||
Stream.of(source.getPropertyNames()).filter(namePredicate)
|
||||
.forEach((name) -> properties.put(name, describeValueOf(name, source, resolver)));
|
||||
.forEach((name) -> properties.put(name, describeValueOf(name, source, showUnsanitized)));
|
||||
return new PropertySourceDescriptor(sourceName, properties);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private PropertyValueDescriptor describeValueOf(String name, PropertySource<?> source,
|
||||
PlaceholdersResolver resolver) {
|
||||
private PropertyValueDescriptor describeValueOf(String name, PropertySource<?> source, boolean showUnsanitized) {
|
||||
PlaceholdersResolver resolver = new PropertySourcesPlaceholdersResolver(getPropertySources());
|
||||
Object resolved = resolver.resolvePlaceholders(source.getProperty(name));
|
||||
Origin origin = ((source instanceof OriginLookup) ? ((OriginLookup<Object>) source).getOrigin(name) : null);
|
||||
Object sanitizedValue = sanitize(source, name, resolved);
|
||||
Object sanitizedValue = sanitize(source, name, resolved, showUnsanitized);
|
||||
return new PropertyValueDescriptor(stringifyIfNecessary(sanitizedValue), origin);
|
||||
}
|
||||
|
||||
private PlaceholdersResolver getResolver() {
|
||||
return new PropertySourcesPlaceholdersSanitizingResolver(getPropertySources(), this.sanitizer);
|
||||
}
|
||||
|
||||
private Map<String, PropertySource<?>> getPropertySourcesAsMap() {
|
||||
Map<String, PropertySource<?>> map = new LinkedHashMap<>();
|
||||
for (PropertySource<?> source : getPropertySources()) {
|
||||
@ -193,8 +185,8 @@ public class EnvironmentEndpoint {
|
||||
}
|
||||
}
|
||||
|
||||
private Object sanitize(PropertySource<?> source, String name, Object value) {
|
||||
return this.sanitizer.sanitize(new SanitizableData(source, name, value));
|
||||
private Object sanitize(PropertySource<?> source, String name, Object value, boolean showUnsanitized) {
|
||||
return this.sanitizer.sanitize(new SanitizableData(source, name, value), showUnsanitized);
|
||||
}
|
||||
|
||||
protected Object stringifyIfNecessary(Object value) {
|
||||
@ -208,40 +200,6 @@ public class EnvironmentEndpoint {
|
||||
return "Complex property type " + value.getClass().getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link PropertySourcesPlaceholdersResolver} that sanitizes sensitive placeholders
|
||||
* if present.
|
||||
*/
|
||||
private static class PropertySourcesPlaceholdersSanitizingResolver extends PropertySourcesPlaceholdersResolver {
|
||||
|
||||
private final Sanitizer sanitizer;
|
||||
|
||||
private final Iterable<PropertySource<?>> sources;
|
||||
|
||||
PropertySourcesPlaceholdersSanitizingResolver(Iterable<PropertySource<?>> sources, Sanitizer sanitizer) {
|
||||
super(sources, new PropertyPlaceholderHelper(SystemPropertyUtils.PLACEHOLDER_PREFIX,
|
||||
SystemPropertyUtils.PLACEHOLDER_SUFFIX, SystemPropertyUtils.VALUE_SEPARATOR, true));
|
||||
this.sources = sources;
|
||||
this.sanitizer = sanitizer;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String resolvePlaceholder(String placeholder) {
|
||||
if (this.sources != null) {
|
||||
for (PropertySource<?> source : this.sources) {
|
||||
Object value = source.getProperty(placeholder);
|
||||
if (value != null) {
|
||||
SanitizableData data = new SanitizableData(source, placeholder, value);
|
||||
Object sanitized = this.sanitizer.sanitize(data);
|
||||
return (sanitized != null) ? String.valueOf(sanitized) : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A description of an {@link Environment}.
|
||||
*/
|
||||
@ -278,7 +236,7 @@ public class EnvironmentEndpoint {
|
||||
|
||||
private final List<PropertySourceEntryDescriptor> propertySources;
|
||||
|
||||
private EnvironmentEntryDescriptor(PropertySummaryDescriptor property, List<String> activeProfiles,
|
||||
EnvironmentEntryDescriptor(PropertySummaryDescriptor property, List<String> activeProfiles,
|
||||
List<PropertySourceEntryDescriptor> propertySources) {
|
||||
this.property = property;
|
||||
this.activeProfiles = activeProfiles;
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
* Copyright 2012-2022 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.
|
||||
@ -16,11 +16,17 @@
|
||||
|
||||
package org.springframework.boot.actuate.env;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||
import org.springframework.boot.actuate.endpoint.Show;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Selector;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension;
|
||||
import org.springframework.boot.actuate.env.EnvironmentEndpoint.EnvironmentDescriptor;
|
||||
import org.springframework.boot.actuate.env.EnvironmentEndpoint.EnvironmentEntryDescriptor;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* {@link EndpointWebExtension @EndpointWebExtension} for the {@link EnvironmentEndpoint}.
|
||||
@ -34,13 +40,27 @@ public class EnvironmentEndpointWebExtension {
|
||||
|
||||
private final EnvironmentEndpoint delegate;
|
||||
|
||||
public EnvironmentEndpointWebExtension(EnvironmentEndpoint delegate) {
|
||||
private final Show showValues;
|
||||
|
||||
private final Set<String> roles;
|
||||
|
||||
public EnvironmentEndpointWebExtension(EnvironmentEndpoint delegate, Show showValues, Set<String> roles) {
|
||||
this.delegate = delegate;
|
||||
this.showValues = showValues;
|
||||
this.roles = roles;
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public WebEndpointResponse<EnvironmentEntryDescriptor> environmentEntry(@Selector String toMatch) {
|
||||
EnvironmentEntryDescriptor descriptor = this.delegate.environmentEntry(toMatch);
|
||||
public EnvironmentDescriptor environment(SecurityContext securityContext, @Nullable String pattern) {
|
||||
boolean showUnsanitized = this.showValues.isShown(securityContext, this.roles);
|
||||
return this.delegate.getEnvironmentDescriptor(pattern, showUnsanitized);
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public WebEndpointResponse<EnvironmentEntryDescriptor> environmentEntry(SecurityContext securityContext,
|
||||
@Selector String toMatch) {
|
||||
boolean showUnsanitized = this.showValues.isShown(securityContext, this.roles);
|
||||
EnvironmentEntryDescriptor descriptor = this.delegate.getEnvironmentEntryDescriptor(toMatch, showUnsanitized);
|
||||
return (descriptor.getProperty() != null) ? new WebEndpointResponse<>(descriptor, WebEndpointResponse.STATUS_OK)
|
||||
: new WebEndpointResponse<>(WebEndpointResponse.STATUS_NOT_FOUND);
|
||||
}
|
||||
|
@ -48,7 +48,9 @@ import org.quartz.Trigger.TriggerState;
|
||||
import org.quartz.TriggerKey;
|
||||
import org.quartz.impl.matchers.GroupMatcher;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.SanitizableData;
|
||||
import org.springframework.boot.actuate.endpoint.Sanitizer;
|
||||
import org.springframework.boot.actuate.endpoint.SanitizingFunction;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||
import org.springframework.util.Assert;
|
||||
@ -71,25 +73,10 @@ public class QuartzEndpoint {
|
||||
|
||||
private final Sanitizer sanitizer;
|
||||
|
||||
/**
|
||||
* Create an instance for the specified {@link Scheduler} using a default
|
||||
* {@link Sanitizer}.
|
||||
* @param scheduler the scheduler to use to retrieve jobs and triggers details
|
||||
*/
|
||||
public QuartzEndpoint(Scheduler scheduler) {
|
||||
this(scheduler, new Sanitizer());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance for the specified {@link Scheduler} and {@link Sanitizer}.
|
||||
* @param scheduler the scheduler to use to retrieve jobs and triggers details
|
||||
* @param sanitizer the sanitizer to use to sanitize data maps
|
||||
*/
|
||||
public QuartzEndpoint(Scheduler scheduler, Sanitizer sanitizer) {
|
||||
public QuartzEndpoint(Scheduler scheduler, Iterable<SanitizingFunction> sanitizingFunctions) {
|
||||
Assert.notNull(scheduler, "Scheduler must not be null");
|
||||
Assert.notNull(sanitizer, "Sanitizer must not be null");
|
||||
this.scheduler = scheduler;
|
||||
this.sanitizer = sanitizer;
|
||||
this.sanitizer = new Sanitizer(sanitizingFunctions);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -201,17 +188,19 @@ public class QuartzEndpoint {
|
||||
* group name and job name.
|
||||
* @param groupName the name of the group
|
||||
* @param jobName the name of the job
|
||||
* @param showUnsanitized whether to sanitize values in data map
|
||||
* @return the details of the job or {@code null} if such job does not exist
|
||||
* @throws SchedulerException if retrieving the information from the scheduler failed
|
||||
*/
|
||||
public QuartzJobDetails quartzJob(String groupName, String jobName) throws SchedulerException {
|
||||
public QuartzJobDetails quartzJob(String groupName, String jobName, boolean showUnsanitized)
|
||||
throws SchedulerException {
|
||||
JobKey jobKey = JobKey.jobKey(jobName, groupName);
|
||||
JobDetail jobDetail = this.scheduler.getJobDetail(jobKey);
|
||||
if (jobDetail != null) {
|
||||
List<? extends Trigger> triggers = this.scheduler.getTriggersOfJob(jobKey);
|
||||
return new QuartzJobDetails(jobDetail.getKey().getGroup(), jobDetail.getKey().getName(),
|
||||
jobDetail.getDescription(), jobDetail.getJobClass().getName(), jobDetail.isDurable(),
|
||||
jobDetail.requestsRecovery(), sanitizeJobDataMap(jobDetail.getJobDataMap()),
|
||||
jobDetail.requestsRecovery(), sanitizeJobDataMap(jobDetail.getJobDataMap(), showUnsanitized),
|
||||
extractTriggersSummary(triggers));
|
||||
}
|
||||
return null;
|
||||
@ -236,14 +225,18 @@ public class QuartzEndpoint {
|
||||
* name.
|
||||
* @param groupName the name of the group
|
||||
* @param triggerName the name of the trigger
|
||||
* @param showUnsanitized whether to sanitize values in data map
|
||||
* @return the details of the trigger or {@code null} if such trigger does not exist
|
||||
* @throws SchedulerException if retrieving the information from the scheduler failed
|
||||
*/
|
||||
public Map<String, Object> quartzTrigger(String groupName, String triggerName) throws SchedulerException {
|
||||
Map<String, Object> quartzTrigger(String groupName, String triggerName, boolean showUnsanitized)
|
||||
throws SchedulerException {
|
||||
TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, groupName);
|
||||
Trigger trigger = this.scheduler.getTrigger(triggerKey);
|
||||
return (trigger != null) ? TriggerDescription.of(trigger).buildDetails(
|
||||
this.scheduler.getTriggerState(triggerKey), sanitizeJobDataMap(trigger.getJobDataMap())) : null;
|
||||
return (trigger != null)
|
||||
? TriggerDescription.of(trigger).buildDetails(this.scheduler.getTriggerState(triggerKey),
|
||||
sanitizeJobDataMap(trigger.getJobDataMap(), showUnsanitized))
|
||||
: null;
|
||||
}
|
||||
|
||||
private static Duration getIntervalDuration(long amount, IntervalUnit unit) {
|
||||
@ -255,15 +248,20 @@ public class QuartzEndpoint {
|
||||
: null;
|
||||
}
|
||||
|
||||
private Map<String, Object> sanitizeJobDataMap(JobDataMap dataMap) {
|
||||
private Map<String, Object> sanitizeJobDataMap(JobDataMap dataMap, boolean showUnsanitized) {
|
||||
if (dataMap != null) {
|
||||
Map<String, Object> map = new LinkedHashMap<>(dataMap.getWrappedMap());
|
||||
map.replaceAll(this.sanitizer::sanitize);
|
||||
map.replaceAll((key, value) -> getSanitizedValue(showUnsanitized, key, value));
|
||||
return map;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Object getSanitizedValue(boolean showUnsanitized, String key, Object value) {
|
||||
SanitizableData data = new SanitizableData(null, key, value);
|
||||
return this.sanitizer.sanitize(data, showUnsanitized);
|
||||
}
|
||||
|
||||
private static TemporalUnit temporalUnit(IntervalUnit unit) {
|
||||
return switch (unit) {
|
||||
case DAY -> ChronoUnit.DAYS;
|
||||
|
@ -16,10 +16,14 @@
|
||||
|
||||
package org.springframework.boot.actuate.quartz;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.quartz.SchedulerException;
|
||||
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.aot.hint.RuntimeHintsRegistrar;
|
||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||
import org.springframework.boot.actuate.endpoint.Show;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Selector;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
||||
@ -44,8 +48,14 @@ public class QuartzEndpointWebExtension {
|
||||
|
||||
private final QuartzEndpoint delegate;
|
||||
|
||||
public QuartzEndpointWebExtension(QuartzEndpoint delegate) {
|
||||
private final Show showValues;
|
||||
|
||||
private final Set<String> roles;
|
||||
|
||||
public QuartzEndpointWebExtension(QuartzEndpoint delegate, Show showValues, Set<String> roles) {
|
||||
this.delegate = delegate;
|
||||
this.showValues = showValues;
|
||||
this.roles = roles;
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
@ -62,10 +72,11 @@ public class QuartzEndpointWebExtension {
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public WebEndpointResponse<Object> quartzJobOrTrigger(@Selector String jobsOrTriggers, @Selector String group,
|
||||
@Selector String name) throws SchedulerException {
|
||||
return handle(jobsOrTriggers, () -> this.delegate.quartzJob(group, name),
|
||||
() -> this.delegate.quartzTrigger(group, name));
|
||||
public WebEndpointResponse<Object> quartzJobOrTrigger(SecurityContext securityContext,
|
||||
@Selector String jobsOrTriggers, @Selector String group, @Selector String name) throws SchedulerException {
|
||||
boolean showUnsanitized = this.showValues.isShown(securityContext, this.roles);
|
||||
return handle(jobsOrTriggers, () -> this.delegate.quartzJob(group, name, showUnsanitized),
|
||||
() -> this.delegate.quartzTrigger(group, name, showUnsanitized));
|
||||
}
|
||||
|
||||
private <T> WebEndpointResponse<T> handle(String jobsOrTriggers, ResponseSupplier<T> jobAction,
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2021 the original author or authors.
|
||||
* Copyright 2012-2022 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.
|
||||
@ -16,15 +16,21 @@
|
||||
|
||||
package org.springframework.boot.actuate.context.properties;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ApplicationConfigurationProperties;
|
||||
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ConfigurationPropertiesBeanDescriptor;
|
||||
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ContextConfigurationProperties;
|
||||
import org.springframework.boot.actuate.endpoint.Show;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@ -39,16 +45,7 @@ class ConfigurationPropertiesReportEndpointFilteringTests {
|
||||
void filterByPrefixSingleMatch() {
|
||||
ApplicationContextRunner contextRunner = new ApplicationContextRunner().withUserConfiguration(Config.class)
|
||||
.withPropertyValues("foo.primary.name:foo1", "foo.secondary.name:foo2", "only.bar.name:solo1");
|
||||
contextRunner.run((context) -> {
|
||||
ConfigurationPropertiesReportEndpoint endpoint = context
|
||||
.getBean(ConfigurationPropertiesReportEndpoint.class);
|
||||
ApplicationConfigurationProperties applicationProperties = endpoint
|
||||
.configurationPropertiesWithPrefix("only.bar");
|
||||
assertThat(applicationProperties.getContexts()).containsOnlyKeys(context.getId());
|
||||
ContextConfigurationProperties contextProperties = applicationProperties.getContexts().get(context.getId());
|
||||
assertThat(contextProperties.getBeans().values()).singleElement().hasFieldOrPropertyWithValue("prefix",
|
||||
"only.bar");
|
||||
});
|
||||
assertProperties(contextRunner, "solo1");
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -81,15 +78,84 @@ class ConfigurationPropertiesReportEndpointFilteringTests {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void noSanitizationWhenShowAlways() {
|
||||
ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withUserConfiguration(ConfigWithAlways.class)
|
||||
.withPropertyValues("foo.primary.name:foo1", "foo.secondary.name:foo2", "only.bar.name:solo1");
|
||||
assertProperties(contextRunner, "solo1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void sanitizationWhenShowNever() {
|
||||
ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withUserConfiguration(ConfigWithNever.class)
|
||||
.withPropertyValues("foo.primary.name:foo1", "foo.secondary.name:foo2", "only.bar.name:solo1");
|
||||
assertProperties(contextRunner, "******");
|
||||
}
|
||||
|
||||
private void assertProperties(ApplicationContextRunner contextRunner, String value) {
|
||||
contextRunner.run((context) -> {
|
||||
ConfigurationPropertiesReportEndpoint endpoint = context
|
||||
.getBean(ConfigurationPropertiesReportEndpoint.class);
|
||||
ApplicationConfigurationProperties applicationProperties = endpoint
|
||||
.configurationPropertiesWithPrefix("only.bar");
|
||||
assertThat(applicationProperties.getContexts()).containsOnlyKeys(context.getId());
|
||||
ContextConfigurationProperties contextProperties = applicationProperties.getContexts().get(context.getId());
|
||||
Optional<String> key = contextProperties.getBeans().keySet().stream()
|
||||
.filter((id) -> findIdFromPrefix("only.bar", id)).findAny();
|
||||
ConfigurationPropertiesBeanDescriptor descriptor = contextProperties.getBeans().get(key.get());
|
||||
assertThat(descriptor.getPrefix()).isEqualTo("only.bar");
|
||||
assertThat(descriptor.getProperties().get("name")).isEqualTo(value);
|
||||
});
|
||||
}
|
||||
|
||||
private boolean findIdFromPrefix(String prefix, String id) {
|
||||
int separator = id.indexOf("-");
|
||||
String candidate = (separator != -1) ? id.substring(0, separator) : id;
|
||||
return prefix.equals(candidate);
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Import(BaseConfiguration.class)
|
||||
@EnableConfigurationProperties(Bar.class)
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
ConfigurationPropertiesReportEndpoint endpoint() {
|
||||
return new ConfigurationPropertiesReportEndpoint();
|
||||
return new ConfigurationPropertiesReportEndpoint(Collections.emptyList(), Show.WHEN_AUTHORIZED);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Import(BaseConfiguration.class)
|
||||
@EnableConfigurationProperties(Bar.class)
|
||||
static class ConfigWithNever {
|
||||
|
||||
@Bean
|
||||
ConfigurationPropertiesReportEndpoint endpoint() {
|
||||
return new ConfigurationPropertiesReportEndpoint(Collections.emptyList(), Show.NEVER);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Import(BaseConfiguration.class)
|
||||
@EnableConfigurationProperties(Bar.class)
|
||||
static class ConfigWithAlways {
|
||||
|
||||
@Bean
|
||||
ConfigurationPropertiesReportEndpoint endpoint() {
|
||||
return new ConfigurationPropertiesReportEndpoint(Collections.emptyList(), Show.ALWAYS);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableConfigurationProperties(Bar.class)
|
||||
static class BaseConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConfigurationProperties(prefix = "foo.primary")
|
||||
Foo primaryFoo() {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2022 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.
|
||||
@ -16,11 +16,14 @@
|
||||
|
||||
package org.springframework.boot.actuate.context.properties;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ApplicationConfigurationProperties;
|
||||
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ConfigurationPropertiesBeanDescriptor;
|
||||
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ContextConfigurationProperties;
|
||||
import org.springframework.boot.actuate.endpoint.Show;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
@ -79,7 +82,7 @@ class ConfigurationPropertiesReportEndpointMethodAnnotationsTests {
|
||||
|
||||
@Bean
|
||||
ConfigurationPropertiesReportEndpoint endpoint() {
|
||||
return new ConfigurationPropertiesReportEndpoint();
|
||||
return new ConfigurationPropertiesReportEndpoint(Collections.emptyList(), Show.ALWAYS);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ -102,7 +105,7 @@ class ConfigurationPropertiesReportEndpointMethodAnnotationsTests {
|
||||
|
||||
@Bean
|
||||
ConfigurationPropertiesReportEndpoint endpoint() {
|
||||
return new ConfigurationPropertiesReportEndpoint();
|
||||
return new ConfigurationPropertiesReportEndpoint(Collections.emptyList(), Show.ALWAYS);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2022 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.
|
||||
@ -16,9 +16,12 @@
|
||||
|
||||
package org.springframework.boot.actuate.context.properties;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ApplicationConfigurationProperties;
|
||||
import org.springframework.boot.actuate.endpoint.Show;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
@ -86,7 +89,7 @@ class ConfigurationPropertiesReportEndpointParentTests {
|
||||
|
||||
@Bean
|
||||
ConfigurationPropertiesReportEndpoint endpoint() {
|
||||
return new ConfigurationPropertiesReportEndpoint();
|
||||
return new ConfigurationPropertiesReportEndpoint(Collections.emptyList(), Show.ALWAYS);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ -102,7 +105,7 @@ class ConfigurationPropertiesReportEndpointParentTests {
|
||||
|
||||
@Bean
|
||||
ConfigurationPropertiesReportEndpoint endpoint() {
|
||||
return new ConfigurationPropertiesReportEndpoint();
|
||||
return new ConfigurationPropertiesReportEndpoint(Collections.emptyList(), Show.ALWAYS);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2022 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.
|
||||
@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.boot.actuate.context.properties;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
@ -24,6 +25,7 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ApplicationConfigurationProperties;
|
||||
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ConfigurationPropertiesBeanDescriptor;
|
||||
import org.springframework.boot.actuate.endpoint.Show;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
@ -85,7 +87,7 @@ class ConfigurationPropertiesReportEndpointProxyTests {
|
||||
|
||||
@Bean
|
||||
ConfigurationPropertiesReportEndpoint endpoint() {
|
||||
return new ConfigurationPropertiesReportEndpoint();
|
||||
return new ConfigurationPropertiesReportEndpoint(Collections.emptyList(), Show.ALWAYS);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
@ -31,6 +31,7 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ApplicationConfigurationProperties;
|
||||
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ConfigurationPropertiesBeanDescriptor;
|
||||
import org.springframework.boot.actuate.endpoint.Show;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.ConfigurationPropertiesBinding;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
@ -290,7 +291,7 @@ class ConfigurationPropertiesReportEndpointSerializationTests {
|
||||
|
||||
@Bean
|
||||
ConfigurationPropertiesReportEndpoint endpoint() {
|
||||
return new ConfigurationPropertiesReportEndpoint();
|
||||
return new ConfigurationPropertiesReportEndpoint(Collections.emptyList(), Show.ALWAYS);
|
||||
}
|
||||
|
||||
}
|
||||
@ -553,7 +554,7 @@ class ConfigurationPropertiesReportEndpointSerializationTests {
|
||||
|
||||
@Bean
|
||||
ConfigurationPropertiesReportEndpoint endpoint() {
|
||||
return new ConfigurationPropertiesReportEndpoint();
|
||||
return new ConfigurationPropertiesReportEndpoint(Collections.emptyList(), Show.ALWAYS);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
@ -32,6 +32,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ConfigurationPropertiesBeanDescriptor;
|
||||
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ContextConfigurationProperties;
|
||||
import org.springframework.boot.actuate.endpoint.SanitizingFunction;
|
||||
import org.springframework.boot.actuate.endpoint.Show;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.ConstructorBinding;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
@ -45,7 +46,6 @@ import org.springframework.boot.test.context.runner.ContextConsumer;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.mock.env.MockPropertySource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@ -175,82 +175,10 @@ class ConfigurationPropertiesReportEndpointTests {
|
||||
(properties) -> assertThat(properties.get("mixedBoolean")).isEqualTo(true)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void sanitizeWithDefaultSettings() {
|
||||
this.contextRunner.withUserConfiguration(TestPropertiesConfiguration.class)
|
||||
.run(assertProperties("test", (properties) -> {
|
||||
assertThat(properties.get("dbPassword")).isEqualTo("******");
|
||||
assertThat(properties.get("myTestProperty")).isEqualTo("654321");
|
||||
}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void sanitizeWithCustomKey() {
|
||||
this.contextRunner.withUserConfiguration(TestPropertiesConfiguration.class)
|
||||
.withPropertyValues("test.keys-to-sanitize=property").run(assertProperties("test", (properties) -> {
|
||||
assertThat(properties.get("dbPassword")).isEqualTo("123456");
|
||||
assertThat(properties.get("myTestProperty")).isEqualTo("******");
|
||||
}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void sanitizeWithCustomKeyPattern() {
|
||||
this.contextRunner.withUserConfiguration(TestPropertiesConfiguration.class)
|
||||
.withPropertyValues("test.keys-to-sanitize=.*pass.*").run(assertProperties("test", (properties) -> {
|
||||
assertThat(properties.get("dbPassword")).isEqualTo("******");
|
||||
assertThat(properties.get("myTestProperty")).isEqualTo("654321");
|
||||
}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void sanitizeWithCustomPatternUsingCompositeKeys() {
|
||||
this.contextRunner.withUserConfiguration(Gh4415PropertiesConfiguration.class)
|
||||
.withPropertyValues("test.keys-to-sanitize=.*\\.secrets\\..*,.*\\.hidden\\..*")
|
||||
.run(assertProperties("gh4415", (properties) -> {
|
||||
Map<String, Object> secrets = (Map<String, Object>) properties.get("secrets");
|
||||
Map<String, Object> hidden = (Map<String, Object>) properties.get("hidden");
|
||||
assertThat(secrets.get("mine")).isEqualTo("******");
|
||||
assertThat(secrets.get("yours")).isEqualTo("******");
|
||||
assertThat(hidden.get("mine")).isEqualTo("******");
|
||||
}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void sanitizeUriWithSensitiveInfo() {
|
||||
this.contextRunner.withUserConfiguration(SensiblePropertiesConfiguration.class)
|
||||
.withPropertyValues("sensible.sensitiveUri=http://user:password@localhost:8080")
|
||||
.run(assertProperties("sensible", (properties) -> assertThat(properties.get("sensitiveUri"))
|
||||
.isEqualTo("http://user:******@localhost:8080"), (inputs) -> {
|
||||
Map<String, Object> sensitiveUri = (Map<String, Object>) inputs.get("sensitiveUri");
|
||||
assertThat(sensitiveUri.get("value")).isEqualTo("http://user:******@localhost:8080");
|
||||
assertThat(sensitiveUri.get("origin"))
|
||||
.isEqualTo("\"sensible.sensitiveUri\" from property source \"test\"");
|
||||
}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void sanitizeUriWithNoPassword() {
|
||||
this.contextRunner.withUserConfiguration(SensiblePropertiesConfiguration.class)
|
||||
.withPropertyValues("sensible.noPasswordUri=http://user:@localhost:8080")
|
||||
.run(assertProperties("sensible", (properties) -> assertThat(properties.get("noPasswordUri"))
|
||||
.isEqualTo("http://user:******@localhost:8080"), (inputs) -> {
|
||||
Map<String, Object> noPasswordUri = (Map<String, Object>) inputs.get("noPasswordUri");
|
||||
assertThat(noPasswordUri.get("value")).isEqualTo("http://user:******@localhost:8080");
|
||||
assertThat(noPasswordUri.get("origin"))
|
||||
.isEqualTo("\"sensible.noPasswordUri\" from property source \"test\"");
|
||||
}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void sanitizeAddressesFieldContainingMultipleRawSensitiveUris() {
|
||||
this.contextRunner.withUserConfiguration(SensiblePropertiesConfiguration.class)
|
||||
.run(assertProperties("sensible", (properties) -> assertThat(properties.get("rawSensitiveAddresses"))
|
||||
.isEqualTo("http://user:******@localhost:8080,http://user2:******@localhost:8082")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void sanitizeLists() {
|
||||
this.contextRunner.withUserConfiguration(SensiblePropertiesConfiguration.class)
|
||||
new ApplicationContextRunner()
|
||||
.withUserConfiguration(EndpointConfigWithShowNever.class, SensiblePropertiesConfiguration.class)
|
||||
.withPropertyValues("sensible.listItems[0].some-password=password")
|
||||
.run(assertProperties("sensible", (properties) -> {
|
||||
assertThat(properties.get("listItems")).isInstanceOf(List.class);
|
||||
@ -271,7 +199,8 @@ class ConfigurationPropertiesReportEndpointTests {
|
||||
|
||||
@Test
|
||||
void listsOfListsAreSanitized() {
|
||||
this.contextRunner.withUserConfiguration(SensiblePropertiesConfiguration.class)
|
||||
new ApplicationContextRunner()
|
||||
.withUserConfiguration(EndpointConfigWithShowNever.class, SensiblePropertiesConfiguration.class)
|
||||
.withPropertyValues("sensible.listOfListItems[0][0].some-password=password")
|
||||
.run(assertProperties("sensible", (properties) -> {
|
||||
assertThat(properties.get("listOfListItems")).isInstanceOf(List.class);
|
||||
@ -311,7 +240,7 @@ class ConfigurationPropertiesReportEndpointTests {
|
||||
.withUserConfiguration(CustomSanitizingEndpointConfig.class,
|
||||
PropertySourceBasedSanitizingFunctionConfiguration.class, TestPropertiesConfiguration.class)
|
||||
.withPropertyValues("test.my-test-property=abcde").run(assertProperties("test", (properties) -> {
|
||||
assertThat(properties.get("dbPassword")).isEqualTo("******");
|
||||
assertThat(properties.get("dbPassword")).isEqualTo("123456");
|
||||
assertThat(properties.get("myTestProperty")).isEqualTo("$$$");
|
||||
}));
|
||||
}
|
||||
@ -339,6 +268,26 @@ class ConfigurationPropertiesReportEndpointTests {
|
||||
}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void noSanitizationWhenShowAlways() {
|
||||
new ApplicationContextRunner()
|
||||
.withUserConfiguration(EndpointConfigWithShowAlways.class, TestPropertiesConfiguration.class)
|
||||
.run(assertProperties("test", (properties) -> {
|
||||
assertThat(properties.get("dbPassword")).isEqualTo("123456");
|
||||
assertThat(properties.get("myTestProperty")).isEqualTo("654321");
|
||||
}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void sanitizationWhenShowNever() {
|
||||
new ApplicationContextRunner()
|
||||
.withUserConfiguration(EndpointConfigWithShowNever.class, TestPropertiesConfiguration.class)
|
||||
.run(assertProperties("test", (properties) -> {
|
||||
assertThat(properties.get("dbPassword")).isEqualTo("******");
|
||||
assertThat(properties.get("myTestProperty")).isEqualTo("******");
|
||||
}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void originParents() {
|
||||
this.contextRunner.withUserConfiguration(SensiblePropertiesConfiguration.class)
|
||||
@ -367,8 +316,9 @@ class ConfigurationPropertiesReportEndpointTests {
|
||||
return (context) -> {
|
||||
ConfigurationPropertiesReportEndpoint endpoint = context
|
||||
.getBean(ConfigurationPropertiesReportEndpoint.class);
|
||||
ContextConfigurationProperties allProperties = endpoint.configurationProperties().getContexts()
|
||||
.get(context.getId());
|
||||
ConfigurationPropertiesReportEndpoint.ApplicationConfigurationProperties configurationProperties = endpoint
|
||||
.configurationProperties();
|
||||
ContextConfigurationProperties allProperties = configurationProperties.getContexts().get(context.getId());
|
||||
Optional<String> key = allProperties.getBeans().keySet().stream()
|
||||
.filter((id) -> findIdFromPrefix(prefix, id)).findAny();
|
||||
assertThat(key).describedAs("No configuration properties with prefix '%s' found", prefix).isPresent();
|
||||
@ -421,12 +371,33 @@ class ConfigurationPropertiesReportEndpointTests {
|
||||
static class EndpointConfig {
|
||||
|
||||
@Bean
|
||||
ConfigurationPropertiesReportEndpoint endpoint(Environment environment) {
|
||||
ConfigurationPropertiesReportEndpoint endpoint = new ConfigurationPropertiesReportEndpoint();
|
||||
String[] keys = environment.getProperty("test.keys-to-sanitize", String[].class);
|
||||
if (keys != null) {
|
||||
endpoint.setKeysToSanitize(keys);
|
||||
}
|
||||
ConfigurationPropertiesReportEndpoint endpoint() {
|
||||
ConfigurationPropertiesReportEndpoint endpoint = new ConfigurationPropertiesReportEndpoint(
|
||||
Collections.emptyList(), Show.WHEN_AUTHORIZED);
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class EndpointConfigWithShowAlways {
|
||||
|
||||
@Bean
|
||||
ConfigurationPropertiesReportEndpoint endpoint() {
|
||||
ConfigurationPropertiesReportEndpoint endpoint = new ConfigurationPropertiesReportEndpoint(
|
||||
Collections.emptyList(), Show.ALWAYS);
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class EndpointConfigWithShowNever {
|
||||
|
||||
@Bean
|
||||
ConfigurationPropertiesReportEndpoint endpoint() {
|
||||
ConfigurationPropertiesReportEndpoint endpoint = new ConfigurationPropertiesReportEndpoint(
|
||||
Collections.emptyList(), Show.NEVER);
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
@ -891,13 +862,9 @@ class ConfigurationPropertiesReportEndpointTests {
|
||||
static class CustomSanitizingEndpointConfig {
|
||||
|
||||
@Bean
|
||||
ConfigurationPropertiesReportEndpoint endpoint(Environment environment, SanitizingFunction sanitizingFunction) {
|
||||
ConfigurationPropertiesReportEndpoint endpoint(SanitizingFunction sanitizingFunction) {
|
||||
ConfigurationPropertiesReportEndpoint endpoint = new ConfigurationPropertiesReportEndpoint(
|
||||
Collections.singletonList(sanitizingFunction));
|
||||
String[] keys = environment.getProperty("test.keys-to-sanitize", String[].class);
|
||||
if (keys != null) {
|
||||
endpoint.setKeysToSanitize(keys);
|
||||
}
|
||||
Collections.singletonList(sanitizingFunction), Show.ALWAYS);
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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.boot.actuate.context.properties;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ApplicationConfigurationProperties;
|
||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||
import org.springframework.boot.actuate.endpoint.Show;
|
||||
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link ConfigurationPropertiesReportEndpointWebExtension}.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
class ConfigurationPropertiesReportEndpointWebExtensionTests {
|
||||
|
||||
private ConfigurationPropertiesReportEndpointWebExtension webExtension;
|
||||
|
||||
private ConfigurationPropertiesReportEndpoint delegate;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
this.delegate = mock(ConfigurationPropertiesReportEndpoint.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenShowValuesIsNever() {
|
||||
this.webExtension = new ConfigurationPropertiesReportEndpointWebExtension(this.delegate, Show.NEVER,
|
||||
Collections.emptySet());
|
||||
this.webExtension.configurationProperties(null);
|
||||
then(this.delegate).should().getConfigurationProperties(false);
|
||||
verifyPrefixed(null, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenShowValuesIsAlways() {
|
||||
this.webExtension = new ConfigurationPropertiesReportEndpointWebExtension(this.delegate, Show.ALWAYS,
|
||||
Collections.emptySet());
|
||||
this.webExtension.configurationProperties(null);
|
||||
then(this.delegate).should().getConfigurationProperties(true);
|
||||
verifyPrefixed(null, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenShowValuesIsWhenAuthorizedAndSecurityContextIsAuthorized() {
|
||||
SecurityContext securityContext = mock(SecurityContext.class);
|
||||
given(securityContext.getPrincipal()).willReturn(mock(Principal.class));
|
||||
this.webExtension = new ConfigurationPropertiesReportEndpointWebExtension(this.delegate, Show.WHEN_AUTHORIZED,
|
||||
Collections.emptySet());
|
||||
this.webExtension.configurationProperties(securityContext);
|
||||
then(this.delegate).should().getConfigurationProperties(true);
|
||||
verifyPrefixed(securityContext, true);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenShowValuesIsWhenAuthorizedAndSecurityContextIsNotAuthorized() {
|
||||
SecurityContext securityContext = mock(SecurityContext.class);
|
||||
this.webExtension = new ConfigurationPropertiesReportEndpointWebExtension(this.delegate, Show.WHEN_AUTHORIZED,
|
||||
Collections.emptySet());
|
||||
this.webExtension.configurationProperties(securityContext);
|
||||
then(this.delegate).should().getConfigurationProperties(false);
|
||||
verifyPrefixed(securityContext, false);
|
||||
}
|
||||
|
||||
private void verifyPrefixed(SecurityContext securityContext, boolean showUnsanitized) {
|
||||
given(this.delegate.getConfigurationProperties("test", showUnsanitized))
|
||||
.willReturn(new ApplicationConfigurationProperties(Collections.emptyMap()));
|
||||
this.webExtension.configurationPropertiesWithPrefix(securityContext, "test");
|
||||
then(this.delegate).should().getConfigurationProperties("test", showUnsanitized);
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2021 the original author or authors.
|
||||
* Copyright 2012-2022 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.
|
||||
@ -16,8 +16,11 @@
|
||||
|
||||
package org.springframework.boot.actuate.context.properties;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.Show;
|
||||
import org.springframework.boot.actuate.endpoint.web.test.WebEndpointTest;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
@ -77,13 +80,13 @@ class ConfigurationPropertiesReportEndpointWebIntegrationTests {
|
||||
|
||||
@Bean
|
||||
ConfigurationPropertiesReportEndpoint endpoint() {
|
||||
return new ConfigurationPropertiesReportEndpoint();
|
||||
return new ConfigurationPropertiesReportEndpoint(Collections.emptyList(), null);
|
||||
}
|
||||
|
||||
@Bean
|
||||
ConfigurationPropertiesReportEndpointWebExtension endpointWebExtension(
|
||||
ConfigurationPropertiesReportEndpoint endpoint) {
|
||||
return new ConfigurationPropertiesReportEndpointWebExtension(endpoint);
|
||||
return new ConfigurationPropertiesReportEndpointWebExtension(endpoint, Show.ALWAYS, Collections.emptySet());
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
@ -19,11 +19,8 @@ package org.springframework.boot.actuate.endpoint;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@ -39,42 +36,21 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
class SanitizerTests {
|
||||
|
||||
@Test
|
||||
void defaultNonUriKeys() {
|
||||
void whenNoSanitizationFunctionAndShowUnsanitizedIsFalse() {
|
||||
Sanitizer sanitizer = new Sanitizer();
|
||||
assertThat(sanitizer.sanitize("password", "secret")).isEqualTo("******");
|
||||
assertThat(sanitizer.sanitize("my-password", "secret")).isEqualTo("******");
|
||||
assertThat(sanitizer.sanitize("my-OTHER.paSSword", "secret")).isEqualTo("******");
|
||||
assertThat(sanitizer.sanitize("somesecret", "secret")).isEqualTo("******");
|
||||
assertThat(sanitizer.sanitize("somekey", "secret")).isEqualTo("******");
|
||||
assertThat(sanitizer.sanitize("token", "secret")).isEqualTo("******");
|
||||
assertThat(sanitizer.sanitize("sometoken", "secret")).isEqualTo("******");
|
||||
assertThat(sanitizer.sanitize("find", "secret")).isEqualTo("secret");
|
||||
assertThat(sanitizer.sanitize("sun.java.command", "--spring.data.redis.password=pa55w0rd")).isEqualTo("******");
|
||||
assertThat(sanitizer.sanitize("SPRING_APPLICATION_JSON", "{password:123}")).isEqualTo("******");
|
||||
assertThat(sanitizer.sanitize("spring.application.json", "{password:123}")).isEqualTo("******");
|
||||
assertThat(sanitizer.sanitize("VCAP_SERVICES", "{json}")).isEqualTo("******");
|
||||
assertThat(sanitizer.sanitize("vcap.services.db.codeword", "secret")).isEqualTo("******");
|
||||
assertThat(sanitizer.sanitize(new SanitizableData(null, "password", "secret"), false)).isEqualTo("******");
|
||||
assertThat(sanitizer.sanitize(new SanitizableData(null, "other", "something"), false)).isEqualTo("******");
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenAdditionalKeysAreAddedValuesOfBothThemAndTheDefaultKeysAreSanitized() {
|
||||
void whenNoSanitizationFunctionAndShowUnsanitizedIsTrue() {
|
||||
Sanitizer sanitizer = new Sanitizer();
|
||||
sanitizer.keysToSanitize("find", "confidential");
|
||||
assertThat(sanitizer.sanitize("password", "secret")).isEqualTo("******");
|
||||
assertThat(sanitizer.sanitize("my-password", "secret")).isEqualTo("******");
|
||||
assertThat(sanitizer.sanitize("my-OTHER.paSSword", "secret")).isEqualTo("******");
|
||||
assertThat(sanitizer.sanitize("somesecret", "secret")).isEqualTo("******");
|
||||
assertThat(sanitizer.sanitize("somekey", "secret")).isEqualTo("******");
|
||||
assertThat(sanitizer.sanitize("token", "secret")).isEqualTo("******");
|
||||
assertThat(sanitizer.sanitize("sometoken", "secret")).isEqualTo("******");
|
||||
assertThat(sanitizer.sanitize("find", "secret")).isEqualTo("******");
|
||||
assertThat(sanitizer.sanitize("sun.java.command", "--spring.data.redis.password=pa55w0rd")).isEqualTo("******");
|
||||
assertThat(sanitizer.sanitize("confidential", "secret")).isEqualTo("******");
|
||||
assertThat(sanitizer.sanitize("private", "secret")).isEqualTo("secret");
|
||||
assertThat(sanitizer.sanitize(new SanitizableData(null, "password", "secret"), true)).isEqualTo("secret");
|
||||
assertThat(sanitizer.sanitize(new SanitizableData(null, "other", "something"), true)).isEqualTo("something");
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenCustomSanitizingFunctionPresentValueShouldBeSanitized() {
|
||||
void whenCustomSanitizationFunctionAndShowUnsanitizedIsFalse() {
|
||||
Sanitizer sanitizer = new Sanitizer(Collections.singletonList((data) -> {
|
||||
if (data.getKey().equals("custom")) {
|
||||
return data.withValue("$$$$$$");
|
||||
@ -82,11 +58,27 @@ class SanitizerTests {
|
||||
return data;
|
||||
}));
|
||||
SanitizableData secret = new SanitizableData(null, "secret", "xyz");
|
||||
assertThat(sanitizer.sanitize(secret)).isEqualTo("******");
|
||||
assertThat(sanitizer.sanitize(secret, false)).isEqualTo("******");
|
||||
SanitizableData custom = new SanitizableData(null, "custom", "abcde");
|
||||
assertThat(sanitizer.sanitize(custom)).isEqualTo("$$$$$$");
|
||||
assertThat(sanitizer.sanitize(custom, false)).isEqualTo("******");
|
||||
SanitizableData hello = new SanitizableData(null, "hello", "abc");
|
||||
assertThat(sanitizer.sanitize(hello)).isEqualTo("abc");
|
||||
assertThat(sanitizer.sanitize(hello, false)).isEqualTo("******");
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenCustomSanitizationFunctionAndShowUnsanitizedIsTrue() {
|
||||
Sanitizer sanitizer = new Sanitizer(Collections.singletonList((data) -> {
|
||||
if (data.getKey().equals("custom")) {
|
||||
return data.withValue("$$$$$$");
|
||||
}
|
||||
return data;
|
||||
}));
|
||||
SanitizableData secret = new SanitizableData(null, "secret", "xyz");
|
||||
assertThat(sanitizer.sanitize(secret, true)).isEqualTo("xyz");
|
||||
SanitizableData custom = new SanitizableData(null, "custom", "abcde");
|
||||
assertThat(sanitizer.sanitize(custom, true)).isEqualTo("$$$$$$");
|
||||
SanitizableData hello = new SanitizableData(null, "hello", "abc");
|
||||
assertThat(sanitizer.sanitize(hello, true)).isEqualTo("abc");
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -98,7 +90,7 @@ class SanitizerTests {
|
||||
return data;
|
||||
}));
|
||||
SanitizableData password = new SanitizableData(null, "password", "123456");
|
||||
assertThat(sanitizer.sanitize(password)).isEqualTo("------");
|
||||
assertThat(sanitizer.sanitize(password, true)).isEqualTo("------");
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -119,101 +111,7 @@ class SanitizerTests {
|
||||
});
|
||||
Sanitizer sanitizer = new Sanitizer(sanitizingFunctions);
|
||||
SanitizableData custom = new SanitizableData(null, sameKey, "123456");
|
||||
assertThat(sanitizer.sanitize(custom)).isEqualTo("------");
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "key = {0}")
|
||||
@MethodSource("matchingUriUserInfoKeys")
|
||||
void uriWithSingleValueWithPasswordShouldBeSanitized(String key) {
|
||||
Sanitizer sanitizer = new Sanitizer();
|
||||
assertThat(sanitizer.sanitize(key, "http://user:password@localhost:8080"))
|
||||
.isEqualTo("http://user:******@localhost:8080");
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "key = {0}")
|
||||
@MethodSource("matchingUriUserInfoKeys")
|
||||
void uriWithNonAlphaSchemeCharactersAndSingleValueWithPasswordShouldBeSanitized(String key) {
|
||||
Sanitizer sanitizer = new Sanitizer();
|
||||
assertThat(sanitizer.sanitize(key, "s-ch3m.+-e://user:password@localhost:8080"))
|
||||
.isEqualTo("s-ch3m.+-e://user:******@localhost:8080");
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "key = {0}")
|
||||
@MethodSource("matchingUriUserInfoKeys")
|
||||
void uriWithSingleValueWithNoPasswordShouldNotBeSanitized(String key) {
|
||||
Sanitizer sanitizer = new Sanitizer();
|
||||
assertThat(sanitizer.sanitize(key, "http://localhost:8080")).isEqualTo("http://localhost:8080");
|
||||
assertThat(sanitizer.sanitize(key, "http://user@localhost:8080")).isEqualTo("http://user@localhost:8080");
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "key = {0}")
|
||||
@MethodSource("matchingUriUserInfoKeys")
|
||||
void uriWithSingleValueWithPasswordMatchingOtherPartsOfStringShouldBeSanitized(String key) {
|
||||
Sanitizer sanitizer = new Sanitizer();
|
||||
assertThat(sanitizer.sanitize(key, "http://user://@localhost:8080"))
|
||||
.isEqualTo("http://user:******@localhost:8080");
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "key = {0}")
|
||||
@MethodSource("matchingUriUserInfoKeys")
|
||||
void uriWithMultipleValuesEachWithPasswordShouldHaveAllSanitized(String key) {
|
||||
Sanitizer sanitizer = new Sanitizer();
|
||||
assertThat(
|
||||
sanitizer.sanitize(key, "http://user1:password1@localhost:8080,http://user2:password2@localhost:8082"))
|
||||
.isEqualTo("http://user1:******@localhost:8080,http://user2:******@localhost:8082");
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "key = {0}")
|
||||
@MethodSource("matchingUriUserInfoKeys")
|
||||
void uriWithMultipleValuesNoneWithPasswordShouldHaveNoneSanitized(String key) {
|
||||
Sanitizer sanitizer = new Sanitizer();
|
||||
assertThat(sanitizer.sanitize(key, "http://user@localhost:8080,http://localhost:8082"))
|
||||
.isEqualTo("http://user@localhost:8080,http://localhost:8082");
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "key = {0}")
|
||||
@MethodSource("matchingUriUserInfoKeys")
|
||||
void uriWithMultipleValuesSomeWithPasswordShouldHaveThoseSanitized(String key) {
|
||||
Sanitizer sanitizer = new Sanitizer();
|
||||
assertThat(sanitizer.sanitize(key,
|
||||
"http://user1:password1@localhost:8080,http://user2@localhost:8082,http://localhost:8083")).isEqualTo(
|
||||
"http://user1:******@localhost:8080,http://user2@localhost:8082,http://localhost:8083");
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "key = {0}")
|
||||
@MethodSource("matchingUriUserInfoKeys")
|
||||
void uriWithMultipleValuesWithPasswordMatchingOtherPartsOfStringShouldBeSanitized(String key) {
|
||||
Sanitizer sanitizer = new Sanitizer();
|
||||
assertThat(sanitizer.sanitize(key, "http://user1://@localhost:8080,http://user2://@localhost:8082"))
|
||||
.isEqualTo("http://user1:******@localhost:8080,http://user2:******@localhost:8082");
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "key = {0}")
|
||||
@MethodSource("matchingUriUserInfoKeys")
|
||||
void uriKeyWithUserProvidedListLiteralShouldBeSanitized(String key) {
|
||||
Sanitizer sanitizer = new Sanitizer();
|
||||
assertThat(sanitizer.sanitize(key, "[amqp://username:password@host/]"))
|
||||
.isEqualTo("[amqp://username:******@host/]");
|
||||
assertThat(sanitizer.sanitize(key,
|
||||
"[http://user1:password1@localhost:8080,http://user2@localhost:8082,http://localhost:8083]")).isEqualTo(
|
||||
"[http://user1:******@localhost:8080,http://user2@localhost:8082,http://localhost:8083]");
|
||||
assertThat(sanitizer.sanitize(key,
|
||||
"[http://user1:password1@localhost:8080,http://user2:password2@localhost:8082]"))
|
||||
.isEqualTo("[http://user1:******@localhost:8080,http://user2:******@localhost:8082]");
|
||||
assertThat(sanitizer.sanitize(key, "[http://user1@localhost:8080,http://user2@localhost:8082]"))
|
||||
.isEqualTo("[http://user1@localhost:8080,http://user2@localhost:8082]");
|
||||
}
|
||||
|
||||
private static Stream<String> matchingUriUserInfoKeys() {
|
||||
return Stream.of("uri", "my.uri", "myuri", "uris", "my.uris", "myuris", "url", "my.url", "myurl", "urls",
|
||||
"my.urls", "myurls", "address", "my.address", "myaddress", "addresses", "my.addresses", "myaddresses");
|
||||
}
|
||||
|
||||
@Test
|
||||
void regex() {
|
||||
Sanitizer sanitizer = new Sanitizer(".*lock.*");
|
||||
assertThat(sanitizer.sanitize("verylOCkish", "secret")).isEqualTo("******");
|
||||
assertThat(sanitizer.sanitize("veryokish", "secret")).isEqualTo("secret");
|
||||
assertThat(sanitizer.sanitize(custom, true)).isEqualTo("------");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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.boot.actuate.endpoint;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link Show}.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
class ShowTests {
|
||||
|
||||
@Test
|
||||
void isShownWhenNever() {
|
||||
assertThat(Show.NEVER.isShown(null, Collections.emptySet())).isFalse();
|
||||
assertThat(Show.NEVER.isShown(true)).isFalse();
|
||||
assertThat(Show.NEVER.isShown(false)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isShownWhenAlways() {
|
||||
assertThat(Show.ALWAYS.isShown(null, Collections.emptySet())).isTrue();
|
||||
assertThat(Show.ALWAYS.isShown(true)).isTrue();
|
||||
assertThat(Show.ALWAYS.isShown(true)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isShownWithUnauthorizedResult() {
|
||||
assertThat(Show.WHEN_AUTHORIZED.isShown(true)).isTrue();
|
||||
assertThat(Show.WHEN_AUTHORIZED.isShown(false)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isShownWhenUserNotInRole() {
|
||||
SecurityContext securityContext = mock(SecurityContext.class);
|
||||
given(securityContext.getPrincipal()).willReturn(mock(Principal.class));
|
||||
given(securityContext.isUserInRole("admin")).willReturn(false);
|
||||
assertThat(Show.WHEN_AUTHORIZED.isShown(securityContext, Collections.singleton("admin"))).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isShownWhenUserInRole() {
|
||||
SecurityContext securityContext = mock(SecurityContext.class);
|
||||
given(securityContext.getPrincipal()).willReturn(mock(Principal.class));
|
||||
given(securityContext.isUserInRole("admin")).willReturn(true);
|
||||
assertThat(Show.WHEN_AUTHORIZED.isShown(securityContext, Collections.singleton("admin"))).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isShownWhenPrincipalNull() {
|
||||
SecurityContext securityContext = mock(SecurityContext.class);
|
||||
given(securityContext.isUserInRole("admin")).willReturn(true);
|
||||
assertThat(Show.WHEN_AUTHORIZED.isShown(securityContext, Collections.singleton("admin"))).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isShownWhenRolesEmpty() {
|
||||
SecurityContext securityContext = mock(SecurityContext.class);
|
||||
given(securityContext.getPrincipal()).willReturn(mock(Principal.class));
|
||||
assertThat(Show.WHEN_AUTHORIZED.isShown(securityContext, Collections.emptySet())).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isShownWhenSpringSecurityAuthenticationAndUnauthorized() {
|
||||
SecurityContext securityContext = mock(SecurityContext.class);
|
||||
Authentication authentication = mock(Authentication.class);
|
||||
given(securityContext.getPrincipal()).willReturn(authentication);
|
||||
given(authentication.getAuthorities())
|
||||
.willAnswer((invocation) -> Collections.singleton(new SimpleGrantedAuthority("other")));
|
||||
assertThat(Show.WHEN_AUTHORIZED.isShown(securityContext, Collections.singleton("admin"))).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isShownWhenSpringSecurityAuthenticationAndAuthorized() {
|
||||
SecurityContext securityContext = mock(SecurityContext.class);
|
||||
Authentication authentication = mock(Authentication.class);
|
||||
given(securityContext.getPrincipal()).willReturn(authentication);
|
||||
given(authentication.getAuthorities())
|
||||
.willAnswer((invocation) -> Collections.singleton(new SimpleGrantedAuthority("admin")));
|
||||
assertThat(Show.WHEN_AUTHORIZED.isShown(securityContext, Collections.singleton("admin"))).isTrue();
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2021 the original author or authors.
|
||||
* Copyright 2012-2022 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.
|
||||
@ -27,6 +27,7 @@ import java.util.Map;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.Show;
|
||||
import org.springframework.boot.actuate.env.EnvironmentEndpoint.EnvironmentDescriptor;
|
||||
import org.springframework.boot.actuate.env.EnvironmentEndpoint.EnvironmentEntryDescriptor;
|
||||
import org.springframework.boot.actuate.env.EnvironmentEndpoint.PropertySourceDescriptor;
|
||||
@ -73,7 +74,8 @@ class EnvironmentEndpointTests {
|
||||
ConfigurableEnvironment environment = emptyEnvironment();
|
||||
environment.getPropertySources().addLast(singleKeyPropertySource("one", "my.key", "first"));
|
||||
environment.getPropertySources().addLast(singleKeyPropertySource("two", "my.key", "second"));
|
||||
EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment).environment(null);
|
||||
EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment, Collections.emptyList(), Show.ALWAYS)
|
||||
.environment(null);
|
||||
assertThat(descriptor.getActiveProfiles()).isEmpty();
|
||||
Map<String, PropertySourceDescriptor> sources = propertySources(descriptor);
|
||||
assertThat(sources.keySet()).containsExactly("one", "two");
|
||||
@ -81,6 +83,38 @@ class EnvironmentEndpointTests {
|
||||
assertThat(sources.get("two").getProperties()).containsOnlyKeys("my.key");
|
||||
}
|
||||
|
||||
@Test
|
||||
void responseWhenShowNever() {
|
||||
ConfigurableEnvironment environment = new StandardEnvironment();
|
||||
TestPropertyValues.of("other.service=abcde").applyTo(environment);
|
||||
TestPropertyValues.of("system.service=123456").applyToSystemProperties(() -> {
|
||||
EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment, Collections.emptyList(), Show.NEVER)
|
||||
.environment(null);
|
||||
assertThat(propertySources(descriptor).get("test").getProperties().get("other.service").getValue())
|
||||
.isEqualTo("******");
|
||||
Map<String, PropertyValueDescriptor> systemProperties = propertySources(descriptor).get("systemProperties")
|
||||
.getProperties();
|
||||
assertThat(systemProperties.get("system.service").getValue()).isEqualTo("******");
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void responseWhenShowWhenAuthorized() {
|
||||
ConfigurableEnvironment environment = new StandardEnvironment();
|
||||
TestPropertyValues.of("other.service=abcde").applyTo(environment);
|
||||
TestPropertyValues.of("system.service=123456").applyToSystemProperties(() -> {
|
||||
EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment, Collections.emptyList(),
|
||||
Show.WHEN_AUTHORIZED).environment(null);
|
||||
assertThat(propertySources(descriptor).get("test").getProperties().get("other.service").getValue())
|
||||
.isEqualTo("abcde");
|
||||
Map<String, PropertyValueDescriptor> systemProperties = propertySources(descriptor).get("systemProperties")
|
||||
.getProperties();
|
||||
assertThat(systemProperties.get("system.service").getValue()).isEqualTo("123456");
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void compositeSourceIsHandledCorrectly() {
|
||||
ConfigurableEnvironment environment = emptyEnvironment();
|
||||
@ -88,82 +122,14 @@ class EnvironmentEndpointTests {
|
||||
source.addPropertySource(new MapPropertySource("one", Collections.singletonMap("foo", "bar")));
|
||||
source.addPropertySource(new MapPropertySource("two", Collections.singletonMap("foo", "spam")));
|
||||
environment.getPropertySources().addFirst(source);
|
||||
EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment).environment(null);
|
||||
EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment, Collections.emptyList(), Show.ALWAYS)
|
||||
.environment(null);
|
||||
Map<String, PropertySourceDescriptor> sources = propertySources(descriptor);
|
||||
assertThat(sources.keySet()).containsExactly("composite:one", "composite:two");
|
||||
assertThat(sources.get("composite:one").getProperties().get("foo").getValue()).isEqualTo("bar");
|
||||
assertThat(sources.get("composite:two").getProperties().get("foo").getValue()).isEqualTo("spam");
|
||||
}
|
||||
|
||||
@Test
|
||||
void sensitiveKeysHaveTheirValuesSanitized() {
|
||||
TestPropertyValues.of("dbPassword=123456", "apiKey=123456", "mySecret=123456", "myCredentials=123456",
|
||||
"VCAP_SERVICES=123456").applyToSystemProperties(() -> {
|
||||
EnvironmentDescriptor descriptor = new EnvironmentEndpoint(new StandardEnvironment())
|
||||
.environment(null);
|
||||
Map<String, PropertyValueDescriptor> systemProperties = propertySources(descriptor)
|
||||
.get("systemProperties").getProperties();
|
||||
assertThat(systemProperties.get("dbPassword").getValue()).isEqualTo("******");
|
||||
assertThat(systemProperties.get("apiKey").getValue()).isEqualTo("******");
|
||||
assertThat(systemProperties.get("mySecret").getValue()).isEqualTo("******");
|
||||
assertThat(systemProperties.get("myCredentials").getValue()).isEqualTo("******");
|
||||
assertThat(systemProperties.get("VCAP_SERVICES").getValue()).isEqualTo("******");
|
||||
PropertyValueDescriptor command = systemProperties.get("sun.java.command");
|
||||
if (command != null) {
|
||||
assertThat(command.getValue()).isEqualTo("******");
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void sensitiveKeysMatchingCredentialsPatternHaveTheirValuesSanitized() {
|
||||
TestPropertyValues
|
||||
.of("my.services.amqp-free.credentials.uri=123456", "credentials.http_api_uri=123456",
|
||||
"my.services.cleardb-free.credentials=123456", "foo.mycredentials.uri=123456")
|
||||
.applyToSystemProperties(() -> {
|
||||
EnvironmentDescriptor descriptor = new EnvironmentEndpoint(new StandardEnvironment())
|
||||
.environment(null);
|
||||
Map<String, PropertyValueDescriptor> systemProperties = propertySources(descriptor)
|
||||
.get("systemProperties").getProperties();
|
||||
assertThat(systemProperties.get("my.services.amqp-free.credentials.uri").getValue())
|
||||
.isEqualTo("******");
|
||||
assertThat(systemProperties.get("credentials.http_api_uri").getValue()).isEqualTo("******");
|
||||
assertThat(systemProperties.get("my.services.cleardb-free.credentials").getValue())
|
||||
.isEqualTo("******");
|
||||
assertThat(systemProperties.get("foo.mycredentials.uri").getValue()).isEqualTo("******");
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void sensitiveKeysMatchingCustomNameHaveTheirValuesSanitized() {
|
||||
TestPropertyValues.of("dbPassword=123456", "apiKey=123456").applyToSystemProperties(() -> {
|
||||
EnvironmentEndpoint endpoint = new EnvironmentEndpoint(new StandardEnvironment());
|
||||
endpoint.setKeysToSanitize("key");
|
||||
EnvironmentDescriptor descriptor = endpoint.environment(null);
|
||||
Map<String, PropertyValueDescriptor> systemProperties = propertySources(descriptor).get("systemProperties")
|
||||
.getProperties();
|
||||
assertThat(systemProperties.get("dbPassword").getValue()).isEqualTo("123456");
|
||||
assertThat(systemProperties.get("apiKey").getValue()).isEqualTo("******");
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void sensitiveKeysMatchingCustomPatternHaveTheirValuesSanitized() {
|
||||
TestPropertyValues.of("dbPassword=123456", "apiKey=123456").applyToSystemProperties(() -> {
|
||||
EnvironmentEndpoint endpoint = new EnvironmentEndpoint(new StandardEnvironment());
|
||||
endpoint.setKeysToSanitize(".*pass.*");
|
||||
EnvironmentDescriptor descriptor = endpoint.environment(null);
|
||||
Map<String, PropertyValueDescriptor> systemProperties = propertySources(descriptor).get("systemProperties")
|
||||
.getProperties();
|
||||
assertThat(systemProperties.get("dbPassword").getValue()).isEqualTo("******");
|
||||
assertThat(systemProperties.get("apiKey").getValue()).isEqualTo("123456");
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void keysMatchingCustomSanitizingFunctionHaveTheirValuesSanitized() {
|
||||
ConfigurableEnvironment environment = new StandardEnvironment();
|
||||
@ -176,7 +142,7 @@ class EnvironmentEndpointTests {
|
||||
return data.withValue("******");
|
||||
}
|
||||
return data;
|
||||
})).environment(null);
|
||||
}), Show.ALWAYS).environment(null);
|
||||
assertThat(propertySources(descriptor).get("test").getProperties().get("other.service").getValue())
|
||||
.isEqualTo("abcde");
|
||||
Map<String, PropertyValueDescriptor> systemProperties = propertySources(descriptor).get("systemProperties")
|
||||
@ -190,7 +156,8 @@ class EnvironmentEndpointTests {
|
||||
void propertyWithPlaceholderResolved() {
|
||||
ConfigurableEnvironment environment = emptyEnvironment();
|
||||
TestPropertyValues.of("my.foo: ${bar.blah}", "bar.blah: hello").applyTo(environment);
|
||||
EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment).environment(null);
|
||||
EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment, Collections.emptyList(), Show.ALWAYS)
|
||||
.environment(null);
|
||||
assertThat(propertySources(descriptor).get("test").getProperties().get("my.foo").getValue()).isEqualTo("hello");
|
||||
}
|
||||
|
||||
@ -198,46 +165,19 @@ class EnvironmentEndpointTests {
|
||||
void propertyWithPlaceholderNotResolved() {
|
||||
ConfigurableEnvironment environment = emptyEnvironment();
|
||||
TestPropertyValues.of("my.foo: ${bar.blah}").applyTo(environment);
|
||||
EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment).environment(null);
|
||||
EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment, Collections.emptyList(), Show.ALWAYS)
|
||||
.environment(null);
|
||||
assertThat(propertySources(descriptor).get("test").getProperties().get("my.foo").getValue())
|
||||
.isEqualTo("${bar.blah}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void propertyWithSensitivePlaceholderResolved() {
|
||||
ConfigurableEnvironment environment = emptyEnvironment();
|
||||
TestPropertyValues.of("my.foo: http://${bar.password}://hello", "bar.password: hello").applyTo(environment);
|
||||
EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment).environment(null);
|
||||
assertThat(propertySources(descriptor).get("test").getProperties().get("my.foo").getValue())
|
||||
.isEqualTo("http://******://hello");
|
||||
}
|
||||
|
||||
@Test
|
||||
void propertyWithSensitivePlaceholderNotResolved() {
|
||||
ConfigurableEnvironment environment = emptyEnvironment();
|
||||
TestPropertyValues.of("my.foo: http://${bar.password}://hello").applyTo(environment);
|
||||
EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment).environment(null);
|
||||
assertThat(propertySources(descriptor).get("test").getProperties().get("my.foo").getValue())
|
||||
.isEqualTo("http://${bar.password}://hello");
|
||||
}
|
||||
|
||||
@Test
|
||||
void propertyWithSensitivePlaceholderWithCustomFunctionResolved() {
|
||||
ConfigurableEnvironment environment = emptyEnvironment();
|
||||
TestPropertyValues.of("my.foo: http://${bar.password}://hello", "bar.password: hello").applyTo(environment);
|
||||
EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment,
|
||||
Collections.singletonList((data) -> data.withValue(data.getPropertySource().getName() + "******")))
|
||||
.environment(null);
|
||||
assertThat(propertySources(descriptor).get("test").getProperties().get("my.foo").getValue())
|
||||
.isEqualTo("test******");
|
||||
}
|
||||
|
||||
@Test
|
||||
void propertyWithComplexTypeShouldNotFail() {
|
||||
ConfigurableEnvironment environment = emptyEnvironment();
|
||||
environment.getPropertySources()
|
||||
.addFirst(singleKeyPropertySource("test", "foo", Collections.singletonMap("bar", "baz")));
|
||||
EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment).environment(null);
|
||||
EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment, Collections.emptyList(), Show.ALWAYS)
|
||||
.environment(null);
|
||||
String value = (String) propertySources(descriptor).get("test").getProperties().get("foo").getValue();
|
||||
assertThat(value).isEqualTo("Complex property type java.util.Collections$SingletonMap");
|
||||
}
|
||||
@ -251,7 +191,8 @@ class EnvironmentEndpointTests {
|
||||
map.put("boolean", true);
|
||||
map.put("biginteger", BigInteger.valueOf(200));
|
||||
environment.getPropertySources().addFirst(new MapPropertySource("test", map));
|
||||
EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment).environment(null);
|
||||
EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment, Collections.emptyList(), Show.ALWAYS)
|
||||
.environment(null);
|
||||
Map<String, PropertyValueDescriptor> properties = propertySources(descriptor).get("test").getProperties();
|
||||
assertThat(properties.get("char").getValue()).isEqualTo('a');
|
||||
assertThat(properties.get("integer").getValue()).isEqualTo(100);
|
||||
@ -263,26 +204,42 @@ class EnvironmentEndpointTests {
|
||||
void propertyWithCharSequenceTypeIsConvertedToString() {
|
||||
ConfigurableEnvironment environment = emptyEnvironment();
|
||||
environment.getPropertySources().addFirst(singleKeyPropertySource("test", "foo", new CharSequenceProperty()));
|
||||
EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment).environment(null);
|
||||
EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment, Collections.emptyList(), Show.ALWAYS)
|
||||
.environment(null);
|
||||
String value = (String) propertySources(descriptor).get("test").getProperties().get("foo").getValue();
|
||||
assertThat(value).isEqualTo("test value");
|
||||
}
|
||||
|
||||
@Test
|
||||
void propertyEntry() {
|
||||
testPropertyEntry(Show.ALWAYS, "bar", "another");
|
||||
}
|
||||
|
||||
@Test
|
||||
void propertyEntryWhenShowNever() {
|
||||
testPropertyEntry(Show.NEVER, "******", "******");
|
||||
}
|
||||
|
||||
@Test
|
||||
void propertyEntryWhenShowWhenAuthorized() {
|
||||
testPropertyEntry(Show.ALWAYS, "bar", "another");
|
||||
}
|
||||
|
||||
private void testPropertyEntry(Show always, String bar, String another) {
|
||||
TestPropertyValues.of("my.foo=another").applyToSystemProperties(() -> {
|
||||
StandardEnvironment environment = new StandardEnvironment();
|
||||
TestPropertyValues.of("my.foo=bar", "my.foo2=bar2").applyTo(environment, TestPropertyValues.Type.MAP,
|
||||
"test");
|
||||
EnvironmentEntryDescriptor descriptor = new EnvironmentEndpoint(environment).environmentEntry("my.foo");
|
||||
EnvironmentEntryDescriptor descriptor = new EnvironmentEndpoint(environment, Collections.emptyList(),
|
||||
always).environmentEntry("my.foo");
|
||||
assertThat(descriptor).isNotNull();
|
||||
assertThat(descriptor.getProperty()).isNotNull();
|
||||
assertThat(descriptor.getProperty().getSource()).isEqualTo("test");
|
||||
assertThat(descriptor.getProperty().getValue()).isEqualTo("bar");
|
||||
assertThat(descriptor.getProperty().getValue()).isEqualTo(bar);
|
||||
Map<String, PropertySourceEntryDescriptor> sources = propertySources(descriptor);
|
||||
assertThat(sources.keySet()).containsExactly("test", "systemProperties", "systemEnvironment");
|
||||
assertPropertySourceEntryDescriptor(sources.get("test"), "bar", null);
|
||||
assertPropertySourceEntryDescriptor(sources.get("systemProperties"), "another", null);
|
||||
assertPropertySourceEntryDescriptor(sources.get("test"), bar, null);
|
||||
assertPropertySourceEntryDescriptor(sources.get("systemProperties"), another, null);
|
||||
assertPropertySourceEntryDescriptor(sources.get("systemEnvironment"), null, null);
|
||||
return null;
|
||||
});
|
||||
@ -294,7 +251,8 @@ class EnvironmentEndpointTests {
|
||||
OriginParentMockPropertySource propertySource = new OriginParentMockPropertySource();
|
||||
propertySource.setProperty("name", "test");
|
||||
environment.getPropertySources().addFirst(propertySource);
|
||||
EnvironmentEntryDescriptor descriptor = new EnvironmentEndpoint(environment).environmentEntry("name");
|
||||
EnvironmentEntryDescriptor descriptor = new EnvironmentEndpoint(environment, Collections.emptyList(),
|
||||
Show.ALWAYS).environmentEntry("name");
|
||||
PropertySourceEntryDescriptor entryDescriptor = propertySources(descriptor).get("mockProperties");
|
||||
assertThat(entryDescriptor.getProperty().getOrigin()).isEqualTo("name");
|
||||
assertThat(entryDescriptor.getProperty().getOriginParents()).containsExactly("spring", "boot");
|
||||
@ -304,7 +262,8 @@ class EnvironmentEndpointTests {
|
||||
void propertyEntryNotFound() {
|
||||
ConfigurableEnvironment environment = emptyEnvironment();
|
||||
environment.getPropertySources().addFirst(singleKeyPropertySource("test", "foo", "bar"));
|
||||
EnvironmentEntryDescriptor descriptor = new EnvironmentEndpoint(environment).environmentEntry("does.not.exist");
|
||||
EnvironmentEntryDescriptor descriptor = new EnvironmentEndpoint(environment, Collections.emptyList(),
|
||||
Show.ALWAYS).environmentEntry("does.not.exist");
|
||||
assertThat(descriptor).isNotNull();
|
||||
assertThat(descriptor.getProperty()).isNull();
|
||||
Map<String, PropertySourceEntryDescriptor> sources = propertySources(descriptor);
|
||||
@ -317,33 +276,14 @@ class EnvironmentEndpointTests {
|
||||
ConfigurableEnvironment environment = emptyEnvironment();
|
||||
environment.getPropertySources().addFirst(singleKeyPropertySource("one", "a", "alpha"));
|
||||
environment.getPropertySources().addFirst(singleKeyPropertySource("two", "a", "apple"));
|
||||
EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment).environment(null);
|
||||
EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment, Collections.emptyList(), Show.ALWAYS)
|
||||
.environment(null);
|
||||
Map<String, PropertySourceDescriptor> sources = propertySources(descriptor);
|
||||
assertThat(sources.keySet()).containsExactly("two", "one");
|
||||
assertThat(sources.get("one").getProperties().get("a").getValue()).isEqualTo("alpha");
|
||||
assertThat(sources.get("two").getProperties().get("a").getValue()).isEqualTo("apple");
|
||||
}
|
||||
|
||||
@Test
|
||||
void uriPropertyWithSensitiveInfo() {
|
||||
ConfigurableEnvironment environment = new StandardEnvironment();
|
||||
TestPropertyValues.of("sensitive.uri=http://user:password@localhost:8080").applyTo(environment);
|
||||
EnvironmentEntryDescriptor descriptor = new EnvironmentEndpoint(environment).environmentEntry("sensitive.uri");
|
||||
assertThat(descriptor.getProperty().getValue()).isEqualTo("http://user:******@localhost:8080");
|
||||
}
|
||||
|
||||
@Test
|
||||
void addressesPropertyWithMultipleEntriesEachWithSensitiveInfo() {
|
||||
ConfigurableEnvironment environment = new StandardEnvironment();
|
||||
TestPropertyValues
|
||||
.of("sensitive.addresses=http://user:password@localhost:8080,http://user2:password2@localhost:8082")
|
||||
.applyTo(environment);
|
||||
EnvironmentEntryDescriptor descriptor = new EnvironmentEndpoint(environment)
|
||||
.environmentEntry("sensitive.addresses");
|
||||
assertThat(descriptor.getProperty().getValue())
|
||||
.isEqualTo("http://user:******@localhost:8080,http://user2:******@localhost:8082");
|
||||
}
|
||||
|
||||
private static ConfigurableEnvironment emptyEnvironment() {
|
||||
StandardEnvironment environment = new StandardEnvironment();
|
||||
environment.getPropertySources().remove(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME);
|
||||
@ -418,7 +358,7 @@ class EnvironmentEndpointTests {
|
||||
|
||||
@Bean
|
||||
EnvironmentEndpoint environmentEndpoint(Environment environment) {
|
||||
return new EnvironmentEndpoint(environment);
|
||||
return new EnvironmentEndpoint(environment, Collections.emptyList(), Show.ALWAYS);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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.boot.actuate.env;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||
import org.springframework.boot.actuate.endpoint.Show;
|
||||
import org.springframework.boot.actuate.env.EnvironmentEndpoint.EnvironmentEntryDescriptor;
|
||||
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link EnvironmentEndpointWebExtension}.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
class EnvironmentEndpointWebExtensionTests {
|
||||
|
||||
private EnvironmentEndpointWebExtension webExtension;
|
||||
|
||||
private EnvironmentEndpoint delegate;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
this.delegate = mock(EnvironmentEndpoint.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenShowValuesIsNever() {
|
||||
this.webExtension = new EnvironmentEndpointWebExtension(this.delegate, Show.NEVER, Collections.emptySet());
|
||||
this.webExtension.environment(null, null);
|
||||
then(this.delegate).should().getEnvironmentDescriptor(null, false);
|
||||
verifyPrefixed(null, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenShowValuesIsAlways() {
|
||||
this.webExtension = new EnvironmentEndpointWebExtension(this.delegate, Show.ALWAYS, Collections.emptySet());
|
||||
this.webExtension.environment(null, null);
|
||||
then(this.delegate).should().getEnvironmentDescriptor(null, true);
|
||||
verifyPrefixed(null, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenShowValuesIsWhenAuthorizedAndSecurityContextIsAuthorized() {
|
||||
SecurityContext securityContext = mock(SecurityContext.class);
|
||||
given(securityContext.getPrincipal()).willReturn(mock(Principal.class));
|
||||
this.webExtension = new EnvironmentEndpointWebExtension(this.delegate, Show.WHEN_AUTHORIZED,
|
||||
Collections.emptySet());
|
||||
this.webExtension.environment(securityContext, null);
|
||||
then(this.delegate).should().getEnvironmentDescriptor(null, true);
|
||||
verifyPrefixed(securityContext, true);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenShowValuesIsWhenAuthorizedAndSecurityContextIsNotAuthorized() {
|
||||
SecurityContext securityContext = mock(SecurityContext.class);
|
||||
this.webExtension = new EnvironmentEndpointWebExtension(this.delegate, Show.WHEN_AUTHORIZED,
|
||||
Collections.emptySet());
|
||||
this.webExtension.environment(securityContext, null);
|
||||
then(this.delegate).should().getEnvironmentDescriptor(null, false);
|
||||
verifyPrefixed(securityContext, false);
|
||||
}
|
||||
|
||||
private void verifyPrefixed(SecurityContext securityContext, boolean showUnsanitized) {
|
||||
given(this.delegate.getEnvironmentEntryDescriptor("test", showUnsanitized))
|
||||
.willReturn(new EnvironmentEntryDescriptor(null, Collections.emptyList(), Collections.emptyList()));
|
||||
this.webExtension.environmentEntry(securityContext, "test");
|
||||
then(this.delegate).should().getEnvironmentEntryDescriptor("test", showUnsanitized);
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
* Copyright 2012-2022 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.
|
||||
@ -16,11 +16,13 @@
|
||||
|
||||
package org.springframework.boot.actuate.env;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.Show;
|
||||
import org.springframework.boot.actuate.endpoint.web.test.WebEndpointTest;
|
||||
import org.springframework.boot.test.util.TestPropertyValues;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
@ -76,17 +78,6 @@ class EnvironmentEndpointWebIntegrationTests {
|
||||
.isEqualTo("${my.bar}");
|
||||
}
|
||||
|
||||
@WebEndpointTest
|
||||
void nestedPathWithSensitivePlaceholderShouldSanitize() {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("my.foo", "${my.password}");
|
||||
map.put("my.password", "hello");
|
||||
this.context.getEnvironment().getPropertySources().addFirst(new MapPropertySource("placeholder", map));
|
||||
this.client.get().uri("/actuator/env/my.foo").exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("property.value").isEqualTo("******").jsonPath(forPropertyEntry("placeholder"))
|
||||
.isEqualTo("******");
|
||||
}
|
||||
|
||||
@WebEndpointTest
|
||||
void nestedPathForUnknownKeyShouldReturn404() {
|
||||
this.client.get().uri("/actuator/env/this.does.not.exist").exchange().expectStatus().isNotFound();
|
||||
@ -103,16 +94,6 @@ class EnvironmentEndpointWebIntegrationTests {
|
||||
.isEqualTo("${my.bar}");
|
||||
}
|
||||
|
||||
@WebEndpointTest
|
||||
void nestedPathMatchedByRegexWithSensitivePlaceholderShouldSanitize() {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("my.foo", "${my.password}");
|
||||
map.put("my.password", "hello");
|
||||
this.context.getEnvironment().getPropertySources().addFirst(new MapPropertySource("placeholder", map));
|
||||
this.client.get().uri("/actuator/env?pattern=my.*").exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath(forProperty("placeholder", "my.foo")).isEqualTo("******");
|
||||
}
|
||||
|
||||
private String forProperty(String source, String name) {
|
||||
return "propertySources[?(@.name=='" + source + "')].properties.['" + name + "'].value";
|
||||
}
|
||||
@ -126,12 +107,12 @@ class EnvironmentEndpointWebIntegrationTests {
|
||||
|
||||
@Bean
|
||||
EnvironmentEndpoint endpoint(Environment environment) {
|
||||
return new EnvironmentEndpoint(environment);
|
||||
return new EnvironmentEndpoint(environment, Collections.emptyList(), Show.ALWAYS);
|
||||
}
|
||||
|
||||
@Bean
|
||||
EnvironmentEndpointWebExtension environmentEndpointWebExtension(EnvironmentEndpoint endpoint) {
|
||||
return new EnvironmentEndpointWebExtension(endpoint);
|
||||
return new EnvironmentEndpointWebExtension(endpoint, Show.ALWAYS, Collections.emptySet());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -62,7 +62,6 @@ import org.quartz.TriggerKey;
|
||||
import org.quartz.impl.matchers.GroupMatcher;
|
||||
import org.quartz.spi.OperableTrigger;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.Sanitizer;
|
||||
import org.springframework.boot.actuate.quartz.QuartzEndpoint.QuartzJobDetails;
|
||||
import org.springframework.boot.actuate.quartz.QuartzEndpoint.QuartzJobGroupSummary;
|
||||
import org.springframework.boot.actuate.quartz.QuartzEndpoint.QuartzJobSummary;
|
||||
@ -107,7 +106,7 @@ class QuartzEndpointTests {
|
||||
|
||||
QuartzEndpointTests() {
|
||||
this.scheduler = mock(Scheduler.class);
|
||||
this.endpoint = new QuartzEndpoint(this.scheduler);
|
||||
this.endpoint = new QuartzEndpoint(this.scheduler, Collections.emptyList());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -417,7 +416,7 @@ class QuartzEndpointTests {
|
||||
mockTriggers(trigger);
|
||||
given(this.scheduler.getTriggerState(TriggerKey.triggerKey("3am-every-day", "samples")))
|
||||
.willReturn(TriggerState.NORMAL);
|
||||
Map<String, Object> triggerDetails = this.endpoint.quartzTrigger("samples", "3am-every-day");
|
||||
Map<String, Object> triggerDetails = this.endpoint.quartzTrigger("samples", "3am-every-day", true);
|
||||
assertThat(triggerDetails).contains(entry("group", "samples"), entry("name", "3am-every-day"),
|
||||
entry("description", "Sample description"), entry("type", "cron"), entry("state", TriggerState.NORMAL),
|
||||
entry("priority", 3));
|
||||
@ -443,7 +442,7 @@ class QuartzEndpointTests {
|
||||
mockTriggers(trigger);
|
||||
given(this.scheduler.getTriggerState(TriggerKey.triggerKey("every-hour", "samples")))
|
||||
.willReturn(TriggerState.COMPLETE);
|
||||
Map<String, Object> triggerDetails = this.endpoint.quartzTrigger("samples", "every-hour");
|
||||
Map<String, Object> triggerDetails = this.endpoint.quartzTrigger("samples", "every-hour", true);
|
||||
assertThat(triggerDetails).contains(entry("group", "samples"), entry("name", "every-hour"),
|
||||
entry("description", "Every hour"), entry("type", "simple"), entry("state", TriggerState.COMPLETE),
|
||||
entry("priority", 20));
|
||||
@ -470,7 +469,7 @@ class QuartzEndpointTests {
|
||||
mockTriggers(trigger);
|
||||
given(this.scheduler.getTriggerState(TriggerKey.triggerKey("every-hour-mon-wed", "samples")))
|
||||
.willReturn(TriggerState.NORMAL);
|
||||
Map<String, Object> triggerDetails = this.endpoint.quartzTrigger("samples", "every-hour-mon-wed");
|
||||
Map<String, Object> triggerDetails = this.endpoint.quartzTrigger("samples", "every-hour-mon-wed", true);
|
||||
assertThat(triggerDetails).contains(entry("group", "samples"), entry("name", "every-hour-mon-wed"),
|
||||
entry("description", "Every working hour Mon Wed"), entry("type", "dailyTimeInterval"),
|
||||
entry("state", TriggerState.NORMAL), entry("priority", 4));
|
||||
@ -499,7 +498,7 @@ class QuartzEndpointTests {
|
||||
mockTriggers(trigger);
|
||||
given(this.scheduler.getTriggerState(TriggerKey.triggerKey("once-a-week", "samples")))
|
||||
.willReturn(TriggerState.BLOCKED);
|
||||
Map<String, Object> triggerDetails = this.endpoint.quartzTrigger("samples", "once-a-week");
|
||||
Map<String, Object> triggerDetails = this.endpoint.quartzTrigger("samples", "once-a-week", true);
|
||||
assertThat(triggerDetails).contains(entry("group", "samples"), entry("name", "once-a-week"),
|
||||
entry("description", "Once a week"), entry("type", "calendarInterval"),
|
||||
entry("state", TriggerState.BLOCKED), entry("priority", 8));
|
||||
@ -524,7 +523,7 @@ class QuartzEndpointTests {
|
||||
mockTriggers(trigger);
|
||||
given(this.scheduler.getTriggerState(TriggerKey.triggerKey("custom", "samples")))
|
||||
.willReturn(TriggerState.ERROR);
|
||||
Map<String, Object> triggerDetails = this.endpoint.quartzTrigger("samples", "custom");
|
||||
Map<String, Object> triggerDetails = this.endpoint.quartzTrigger("samples", "custom", true);
|
||||
assertThat(triggerDetails).contains(entry("group", "samples"), entry("name", "custom"), entry("type", "custom"),
|
||||
entry("state", TriggerState.ERROR), entry("priority", 9));
|
||||
assertThat(triggerDetails).contains(entry("previousFireTime", previousFireTime),
|
||||
@ -542,9 +541,22 @@ class QuartzEndpointTests {
|
||||
mockTriggers(trigger);
|
||||
given(this.scheduler.getTriggerState(TriggerKey.triggerKey("3am-every-day", "samples")))
|
||||
.willReturn(TriggerState.NORMAL);
|
||||
Map<String, Object> triggerDetails = this.endpoint.quartzTrigger("samples", "3am-every-day");
|
||||
Map<String, Object> triggerDetails = this.endpoint.quartzTrigger("samples", "3am-every-day", true);
|
||||
assertThat(triggerDetails).extractingByKey("data", nestedMap()).containsOnly(entry("user", "user"),
|
||||
entry("password", "******"), entry("url", "https://user:******@example.com"));
|
||||
entry("password", "secret"), entry("url", "https://user:secret@example.com"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void quartzTriggerWithDataMapAndShowUnsanitizedFalse() throws SchedulerException {
|
||||
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity("3am-every-day", "samples")
|
||||
.withSchedule(CronScheduleBuilder.dailyAtHourAndMinute(3, 0)).usingJobData("user", "user")
|
||||
.usingJobData("password", "secret").usingJobData("url", "https://user:secret@example.com").build();
|
||||
mockTriggers(trigger);
|
||||
given(this.scheduler.getTriggerState(TriggerKey.triggerKey("3am-every-day", "samples")))
|
||||
.willReturn(TriggerState.NORMAL);
|
||||
Map<String, Object> triggerDetails = this.endpoint.quartzTrigger("samples", "3am-every-day", false);
|
||||
assertThat(triggerDetails).extractingByKey("data", nestedMap()).containsOnly(entry("user", "******"),
|
||||
entry("password", "******"), entry("url", "******"));
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "unit {1}")
|
||||
@ -554,7 +566,7 @@ class QuartzEndpointTests {
|
||||
.withSchedule(CalendarIntervalScheduleBuilder.calendarIntervalSchedule().withInterval(amount, unit))
|
||||
.build();
|
||||
mockTriggers(trigger);
|
||||
Map<String, Object> triggerDetails = this.endpoint.quartzTrigger("samples", "trigger");
|
||||
Map<String, Object> triggerDetails = this.endpoint.quartzTrigger("samples", "trigger", true);
|
||||
assertThat(triggerDetails).extractingByKey("calendarInterval", nestedMap())
|
||||
.contains(entry("interval", expectedDuration.toMillis()));
|
||||
}
|
||||
@ -575,7 +587,7 @@ class QuartzEndpointTests {
|
||||
JobDetail job = JobBuilder.newJob(Job.class).withIdentity("hello", "samples").withDescription("A sample job")
|
||||
.storeDurably().requestRecovery(false).build();
|
||||
mockJobs(job);
|
||||
QuartzJobDetails jobDetails = this.endpoint.quartzJob("samples", "hello");
|
||||
QuartzJobDetails jobDetails = this.endpoint.quartzJob("samples", "hello", true);
|
||||
assertThat(jobDetails.getGroup()).isEqualTo("samples");
|
||||
assertThat(jobDetails.getName()).isEqualTo("hello");
|
||||
assertThat(jobDetails.getDescription()).isEqualTo("A sample job");
|
||||
@ -600,7 +612,7 @@ class QuartzEndpointTests {
|
||||
mockTriggers(trigger);
|
||||
given(this.scheduler.getTriggersOfJob(JobKey.jobKey("hello", "samples")))
|
||||
.willAnswer((invocation) -> Collections.singletonList(trigger));
|
||||
QuartzJobDetails jobDetails = this.endpoint.quartzJob("samples", "hello");
|
||||
QuartzJobDetails jobDetails = this.endpoint.quartzJob("samples", "hello", true);
|
||||
assertThat(jobDetails.getTriggers()).hasSize(1);
|
||||
Map<String, Object> triggerDetails = jobDetails.getTriggers().get(0);
|
||||
assertThat(triggerDetails).containsOnly(entry("group", "samples"), entry("name", "3am-every-day"),
|
||||
@ -622,7 +634,7 @@ class QuartzEndpointTests {
|
||||
mockTriggers(triggerOne, triggerTwo);
|
||||
given(this.scheduler.getTriggersOfJob(JobKey.jobKey("hello", "samples")))
|
||||
.willAnswer((invocation) -> Arrays.asList(triggerOne, triggerTwo));
|
||||
QuartzJobDetails jobDetails = this.endpoint.quartzJob("samples", "hello");
|
||||
QuartzJobDetails jobDetails = this.endpoint.quartzJob("samples", "hello", true);
|
||||
assertThat(jobDetails.getTriggers()).hasSize(2);
|
||||
assertThat(jobDetails.getTriggers().get(0)).containsEntry("name", "two");
|
||||
assertThat(jobDetails.getTriggers().get(1)).containsEntry("name", "one");
|
||||
@ -642,35 +654,30 @@ class QuartzEndpointTests {
|
||||
mockTriggers(triggerOne, triggerTwo);
|
||||
given(this.scheduler.getTriggersOfJob(JobKey.jobKey("hello", "samples")))
|
||||
.willAnswer((invocation) -> Arrays.asList(triggerOne, triggerTwo));
|
||||
QuartzJobDetails jobDetails = this.endpoint.quartzJob("samples", "hello");
|
||||
QuartzJobDetails jobDetails = this.endpoint.quartzJob("samples", "hello", true);
|
||||
assertThat(jobDetails.getTriggers()).hasSize(2);
|
||||
assertThat(jobDetails.getTriggers().get(0)).containsEntry("name", "two");
|
||||
assertThat(jobDetails.getTriggers().get(1)).containsEntry("name", "one");
|
||||
}
|
||||
|
||||
@Test
|
||||
void quartzJobWithSensitiveDataMap() throws SchedulerException {
|
||||
void quartzJobWithDataMap() throws SchedulerException {
|
||||
JobDetail job = JobBuilder.newJob(Job.class).withIdentity("hello", "samples").usingJobData("user", "user")
|
||||
.usingJobData("password", "secret").usingJobData("url", "https://user:secret@example.com").build();
|
||||
mockJobs(job);
|
||||
QuartzJobDetails jobDetails = this.endpoint.quartzJob("samples", "hello");
|
||||
assertThat(jobDetails.getData()).containsOnly(entry("user", "user"), entry("password", "******"),
|
||||
entry("url", "https://user:******@example.com"));
|
||||
QuartzJobDetails jobDetails = this.endpoint.quartzJob("samples", "hello", true);
|
||||
assertThat(jobDetails.getData()).containsOnly(entry("user", "user"), entry("password", "secret"),
|
||||
entry("url", "https://user:secret@example.com"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void quartzJobWithSensitiveDataMapAndCustomSanitizer() throws SchedulerException {
|
||||
JobDetail job = JobBuilder.newJob(Job.class).withIdentity("hello", "samples").usingJobData("test", "value")
|
||||
.usingJobData("secret", "value").build();
|
||||
void quartzJobWithDataMapAndShowUnsanitizedFalse() throws SchedulerException {
|
||||
JobDetail job = JobBuilder.newJob(Job.class).withIdentity("hello", "samples").usingJobData("user", "user")
|
||||
.usingJobData("password", "secret").usingJobData("url", "https://user:secret@example.com").build();
|
||||
mockJobs(job);
|
||||
Sanitizer sanitizer = mock(Sanitizer.class);
|
||||
given(sanitizer.sanitize("test", "value")).willReturn("value");
|
||||
given(sanitizer.sanitize("secret", "value")).willReturn("----");
|
||||
QuartzJobDetails jobDetails = new QuartzEndpoint(this.scheduler, sanitizer).quartzJob("samples", "hello");
|
||||
assertThat(jobDetails.getData()).containsOnly(entry("test", "value"), entry("secret", "----"));
|
||||
then(sanitizer).should().sanitize("test", "value");
|
||||
then(sanitizer).should().sanitize("secret", "value");
|
||||
then(sanitizer).shouldHaveNoMoreInteractions();
|
||||
QuartzJobDetails jobDetails = this.endpoint.quartzJob("samples", "hello", false);
|
||||
assertThat(jobDetails.getData()).containsOnly(entry("user", "******"), entry("password", "******"),
|
||||
entry("url", "******"));
|
||||
}
|
||||
|
||||
private void mockJobs(JobDetail... jobs) throws SchedulerException {
|
||||
|
@ -16,13 +16,18 @@
|
||||
|
||||
package org.springframework.boot.actuate.quartz;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aot.hint.MemberCategory;
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
|
||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||
import org.springframework.boot.actuate.endpoint.Show;
|
||||
import org.springframework.boot.actuate.quartz.QuartzEndpoint.QuartzGroups;
|
||||
import org.springframework.boot.actuate.quartz.QuartzEndpoint.QuartzJobDetails;
|
||||
import org.springframework.boot.actuate.quartz.QuartzEndpoint.QuartzJobGroupSummary;
|
||||
@ -30,14 +35,67 @@ import org.springframework.boot.actuate.quartz.QuartzEndpoint.QuartzTriggerGroup
|
||||
import org.springframework.boot.actuate.quartz.QuartzEndpointWebExtension.QuartzEndpointWebExtensionRuntimeHints;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link QuartzEndpointWebExtension}.
|
||||
*
|
||||
* @author Moritz Halbritter
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
class QuartzEndpointWebExtensionTests {
|
||||
|
||||
private QuartzEndpointWebExtension webExtension;
|
||||
|
||||
private QuartzEndpoint delegate;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
this.delegate = mock(QuartzEndpoint.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenShowValuesIsNever() throws Exception {
|
||||
this.webExtension = new QuartzEndpointWebExtension(this.delegate, Show.NEVER, Collections.emptySet());
|
||||
this.webExtension.quartzJobOrTrigger(null, "jobs", "a", "b");
|
||||
this.webExtension.quartzJobOrTrigger(null, "triggers", "a", "b");
|
||||
then(this.delegate).should().quartzJob("a", "b", false);
|
||||
then(this.delegate).should().quartzTrigger("a", "b", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenShowValuesIsAlways() throws Exception {
|
||||
this.webExtension = new QuartzEndpointWebExtension(this.delegate, Show.ALWAYS, Collections.emptySet());
|
||||
this.webExtension.quartzJobOrTrigger(null, "a", "b", "c");
|
||||
this.webExtension.quartzJobOrTrigger(null, "jobs", "a", "b");
|
||||
this.webExtension.quartzJobOrTrigger(null, "triggers", "a", "b");
|
||||
then(this.delegate).should().quartzJob("a", "b", true);
|
||||
then(this.delegate).should().quartzTrigger("a", "b", true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenShowValuesIsWhenAuthorizedAndSecurityContextIsAuthorized() throws Exception {
|
||||
SecurityContext securityContext = mock(SecurityContext.class);
|
||||
given(securityContext.getPrincipal()).willReturn(mock(Principal.class));
|
||||
this.webExtension = new QuartzEndpointWebExtension(this.delegate, Show.WHEN_AUTHORIZED, Collections.emptySet());
|
||||
this.webExtension.quartzJobOrTrigger(securityContext, "jobs", "a", "b");
|
||||
this.webExtension.quartzJobOrTrigger(securityContext, "triggers", "a", "b");
|
||||
then(this.delegate).should().quartzJob("a", "b", true);
|
||||
then(this.delegate).should().quartzTrigger("a", "b", true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenShowValuesIsWhenAuthorizedAndSecurityContextIsNotAuthorized() throws Exception {
|
||||
SecurityContext securityContext = mock(SecurityContext.class);
|
||||
this.webExtension = new QuartzEndpointWebExtension(this.delegate, Show.WHEN_AUTHORIZED, Collections.emptySet());
|
||||
this.webExtension.quartzJobOrTrigger(securityContext, "jobs", "a", "b");
|
||||
this.webExtension.quartzJobOrTrigger(securityContext, "triggers", "a", "b");
|
||||
then(this.delegate).should().quartzJob("a", "b", false);
|
||||
then(this.delegate).should().quartzTrigger("a", "b", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRegisterHints() {
|
||||
RuntimeHints runtimeHints = new RuntimeHints();
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2021 the original author or authors.
|
||||
* Copyright 2012-2022 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.
|
||||
@ -42,6 +42,7 @@ import org.quartz.TriggerBuilder;
|
||||
import org.quartz.TriggerKey;
|
||||
import org.quartz.impl.matchers.GroupMatcher;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.Show;
|
||||
import org.springframework.boot.actuate.endpoint.web.test.WebEndpointTest;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@ -175,12 +176,12 @@ class QuartzEndpointWebIntegrationTests {
|
||||
|
||||
@Bean
|
||||
QuartzEndpoint endpoint(Scheduler scheduler) {
|
||||
return new QuartzEndpoint(scheduler);
|
||||
return new QuartzEndpoint(scheduler, Collections.emptyList());
|
||||
}
|
||||
|
||||
@Bean
|
||||
QuartzEndpointWebExtension quartzEndpointWebExtension(QuartzEndpoint endpoint) {
|
||||
return new QuartzEndpointWebExtension(endpoint);
|
||||
return new QuartzEndpointWebExtension(endpoint, Show.ALWAYS, Collections.emptySet());
|
||||
}
|
||||
|
||||
private void mockJobs(Scheduler scheduler, JobDetail... jobs) throws SchedulerException {
|
||||
|
@ -36,22 +36,31 @@ See also the section on "`<<web#web.servlet.spring-mvc.error-handling, Error Han
|
||||
|
||||
[[howto.actuator.sanitize-sensitive-values]]
|
||||
=== Sanitize Sensitive Values
|
||||
Information returned by the `env` and `configprops` endpoints can be somewhat sensitive so keys matching certain patterns are sanitized by default (that is their values are replaced by `+******+`).
|
||||
Spring Boot uses sensible defaults for such keys: any key ending with the word "password", "secret", "key", "token", "vcap_services", "sun.java.command" is entirely sanitized.
|
||||
Additionally, any key that holds the word `credentials` (configured as a regular expression, that is `+.*credentials.*+`) as part of the key is also entirely sanitized.
|
||||
Information returned by the `/env`, `/configprops` and `/quartz` endpoints can be somewhat sensitive.
|
||||
All values are sanitized by default (that is replaced by `+******+`).
|
||||
Viewing original values in the unsanitized form can be configured per endpoint using the `showValues` property for that endpoint.
|
||||
This property can be configured to have the following values:
|
||||
|
||||
Furthermore, Spring Boot sanitizes the sensitive portion of URI-like values for keys with one of the following endings:
|
||||
- `ALWAYS` - all values are shown in their unsanitized form to all users
|
||||
- `NEVER - all values are always sanitized (that is replaced by `+******+`)
|
||||
- `WHEN_AUTHORIZED` - all values are shown in their unsanitized form to authorized users
|
||||
|
||||
- `address`
|
||||
- `addresses`
|
||||
- `uri`
|
||||
- `uris`
|
||||
- `url`
|
||||
- `urls`
|
||||
For HTTP endpoints, a user is considered to be authorized if they have authenticated and have the roles configured by the endpoint's roles property.
|
||||
By default, any authenticated user is authorized.
|
||||
For JMX endpoints, all users are always authorized.
|
||||
|
||||
The sensitive portion of the URI is identified using the format `<scheme>://<username>:<password>@<host>:<port>/`.
|
||||
For example, for the property `myclient.uri=http://user1:password1@localhost:8081`, the resulting sanitized value is
|
||||
`++http://user1:******@localhost:8081++`.
|
||||
[source,yaml,indent=0,subs="verbatim",configprops,configblocks]
|
||||
----
|
||||
management:
|
||||
endpoint:
|
||||
env:
|
||||
show-values: WHEN_AUTHORIZED
|
||||
roles: "admin"
|
||||
----
|
||||
|
||||
The configuration above enables the ability for all users with the `admin` role to view all values in their original form from the `/env` endpoint.
|
||||
|
||||
NOTE: When `show-values` is set to `ALWAYS` or `WHEN_AUTHORIZED` any sanitization applied by a `<<howto#howto.actuator.sanitize-sensitive-values.customizing-sanitization, SanitizingFunction>>` will still be applied.
|
||||
|
||||
|
||||
|
||||
@ -59,9 +68,6 @@ For example, for the property `myclient.uri=http://user1:password1@localhost:808
|
||||
==== Customizing Sanitization
|
||||
Sanitization can be customized in two different ways.
|
||||
|
||||
The default patterns used by the `env` and `configprops` endpoints can be replaced using configprop:management.endpoint.env.keys-to-sanitize[] and configprop:management.endpoint.configprops.keys-to-sanitize[] respectively.
|
||||
Alternatively, additional patterns can be configured using configprop:management.endpoint.env.additional-keys-to-sanitize[] and configprop:management.endpoint.configprops.additional-keys-to-sanitize[].
|
||||
|
||||
To take more control over the sanitization, define a `SanitizingFunction` bean.
|
||||
The `SanitizableData` with which the function is called provides access to the key and value as well as the `PropertySource` from which they came.
|
||||
This allows you to, for example, sanitize every value that comes from a particular property source.
|
||||
|
Loading…
Reference in New Issue
Block a user