Support role-based sanitization for actuator endpoints
Closes gh-32156
This commit is contained in:
@@ -16,8 +16,6 @@
|
|||||||
|
|
||||||
package org.springframework.boot.actuate.autoconfigure.context.properties;
|
package org.springframework.boot.actuate.autoconfigure.context.properties;
|
||||||
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.ObjectProvider;
|
import org.springframework.beans.factory.ObjectProvider;
|
||||||
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
|
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
|
||||||
import org.springframework.boot.actuate.autoconfigure.endpoint.expose.EndpointExposure;
|
import org.springframework.boot.actuate.autoconfigure.endpoint.expose.EndpointExposure;
|
||||||
@@ -50,17 +48,8 @@ public class ConfigurationPropertiesReportEndpointAutoConfiguration {
|
|||||||
public ConfigurationPropertiesReportEndpoint configurationPropertiesReportEndpoint(
|
public ConfigurationPropertiesReportEndpoint configurationPropertiesReportEndpoint(
|
||||||
ConfigurationPropertiesReportEndpointProperties properties,
|
ConfigurationPropertiesReportEndpointProperties properties,
|
||||||
ObjectProvider<SanitizingFunction> sanitizingFunctions) {
|
ObjectProvider<SanitizingFunction> sanitizingFunctions) {
|
||||||
ConfigurationPropertiesReportEndpoint endpoint = new ConfigurationPropertiesReportEndpoint(
|
return new ConfigurationPropertiesReportEndpoint(sanitizingFunctions.orderedStream().toList(),
|
||||||
sanitizingFunctions.orderedStream().collect(Collectors.toList()));
|
properties.getShowValues());
|
||||||
String[] keysToSanitize = properties.getKeysToSanitize();
|
|
||||||
if (keysToSanitize != null) {
|
|
||||||
endpoint.setKeysToSanitize(keysToSanitize);
|
|
||||||
}
|
|
||||||
String[] additionalKeysToSanitize = properties.getAdditionalKeysToSanitize();
|
|
||||||
if (additionalKeysToSanitize != null) {
|
|
||||||
endpoint.keysToSanitize(additionalKeysToSanitize);
|
|
||||||
}
|
|
||||||
return endpoint;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@@ -68,8 +57,10 @@ public class ConfigurationPropertiesReportEndpointAutoConfiguration {
|
|||||||
@ConditionalOnBean(ConfigurationPropertiesReportEndpoint.class)
|
@ConditionalOnBean(ConfigurationPropertiesReportEndpoint.class)
|
||||||
@ConditionalOnAvailableEndpoint(exposure = { EndpointExposure.WEB, EndpointExposure.CLOUD_FOUNDRY })
|
@ConditionalOnAvailableEndpoint(exposure = { EndpointExposure.WEB, EndpointExposure.CLOUD_FOUNDRY })
|
||||||
public ConfigurationPropertiesReportEndpointWebExtension configurationPropertiesReportEndpointWebExtension(
|
public ConfigurationPropertiesReportEndpointWebExtension configurationPropertiesReportEndpointWebExtension(
|
||||||
ConfigurationPropertiesReportEndpoint configurationPropertiesReportEndpoint) {
|
ConfigurationPropertiesReportEndpoint configurationPropertiesReportEndpoint,
|
||||||
return new ConfigurationPropertiesReportEndpointWebExtension(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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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;
|
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.context.properties.ConfigurationPropertiesReportEndpoint;
|
||||||
|
import org.springframework.boot.actuate.endpoint.Show;
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configuration properties for {@link ConfigurationPropertiesReportEndpoint}.
|
* Configuration properties for {@link ConfigurationPropertiesReportEndpoint}.
|
||||||
*
|
*
|
||||||
* @author Stephane Nicoll
|
* @author Stephane Nicoll
|
||||||
|
* @author Madhura Bhave
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
@ConfigurationProperties("management.endpoint.configprops")
|
@ConfigurationProperties("management.endpoint.configprops")
|
||||||
public class ConfigurationPropertiesReportEndpointProperties {
|
public class ConfigurationPropertiesReportEndpointProperties {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Keys that should be sanitized. Keys can be simple strings that the property ends
|
* When to show unsanitized values.
|
||||||
* with or regular expressions.
|
|
||||||
*/
|
*/
|
||||||
private String[] keysToSanitize;
|
private Show showValues = Show.NEVER;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Keys that should be sanitized in addition to those already configured. Keys can be
|
* Roles used to determine whether a user is authorized to be shown unsanitized
|
||||||
* simple strings that the property ends with or regular expressions.
|
* values. When empty, all authenticated users are authorized.
|
||||||
*/
|
*/
|
||||||
private String[] additionalKeysToSanitize;
|
private Set<String> roles = new HashSet<>();
|
||||||
|
|
||||||
public String[] getKeysToSanitize() {
|
public Show getShowValues() {
|
||||||
return this.keysToSanitize;
|
return this.showValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setKeysToSanitize(String[] keysToSanitize) {
|
public void setShowValues(Show showValues) {
|
||||||
this.keysToSanitize = keysToSanitize;
|
this.showValues = showValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String[] getAdditionalKeysToSanitize() {
|
public Set<String> getRoles() {
|
||||||
return this.additionalKeysToSanitize;
|
return this.roles;
|
||||||
}
|
|
||||||
|
|
||||||
public void setAdditionalKeysToSanitize(String[] additionalKeysToSanitize) {
|
|
||||||
this.additionalKeysToSanitize = additionalKeysToSanitize;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,8 +16,6 @@
|
|||||||
|
|
||||||
package org.springframework.boot.actuate.autoconfigure.env;
|
package org.springframework.boot.actuate.autoconfigure.env;
|
||||||
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.ObjectProvider;
|
import org.springframework.beans.factory.ObjectProvider;
|
||||||
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
|
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
|
||||||
import org.springframework.boot.actuate.autoconfigure.endpoint.expose.EndpointExposure;
|
import org.springframework.boot.actuate.autoconfigure.endpoint.expose.EndpointExposure;
|
||||||
@@ -48,25 +46,18 @@ public class EnvironmentEndpointAutoConfiguration {
|
|||||||
@ConditionalOnMissingBean
|
@ConditionalOnMissingBean
|
||||||
public EnvironmentEndpoint environmentEndpoint(Environment environment, EnvironmentEndpointProperties properties,
|
public EnvironmentEndpoint environmentEndpoint(Environment environment, EnvironmentEndpointProperties properties,
|
||||||
ObjectProvider<SanitizingFunction> sanitizingFunctions) {
|
ObjectProvider<SanitizingFunction> sanitizingFunctions) {
|
||||||
EnvironmentEndpoint endpoint = new EnvironmentEndpoint(environment,
|
return new EnvironmentEndpoint(environment, sanitizingFunctions.orderedStream().toList(),
|
||||||
sanitizingFunctions.orderedStream().collect(Collectors.toList()));
|
properties.getShowValues());
|
||||||
String[] keysToSanitize = properties.getKeysToSanitize();
|
|
||||||
if (keysToSanitize != null) {
|
|
||||||
endpoint.setKeysToSanitize(keysToSanitize);
|
|
||||||
}
|
|
||||||
String[] additionalKeysToSanitize = properties.getAdditionalKeysToSanitize();
|
|
||||||
if (additionalKeysToSanitize != null) {
|
|
||||||
endpoint.keysToSanitize(additionalKeysToSanitize);
|
|
||||||
}
|
|
||||||
return endpoint;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnMissingBean
|
@ConditionalOnMissingBean
|
||||||
@ConditionalOnBean(EnvironmentEndpoint.class)
|
@ConditionalOnBean(EnvironmentEndpoint.class)
|
||||||
@ConditionalOnAvailableEndpoint(exposure = { EndpointExposure.WEB, EndpointExposure.CLOUD_FOUNDRY })
|
@ConditionalOnAvailableEndpoint(exposure = { EndpointExposure.WEB, EndpointExposure.CLOUD_FOUNDRY })
|
||||||
public EnvironmentEndpointWebExtension environmentEndpointWebExtension(EnvironmentEndpoint environmentEndpoint) {
|
public EnvironmentEndpointWebExtension environmentEndpointWebExtension(EnvironmentEndpoint environmentEndpoint,
|
||||||
return new EnvironmentEndpointWebExtension(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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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;
|
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.actuate.env.EnvironmentEndpoint;
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
|
||||||
@@ -29,31 +33,26 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
|
|||||||
public class EnvironmentEndpointProperties {
|
public class EnvironmentEndpointProperties {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Keys that should be sanitized. Keys can be simple strings that the property ends
|
* When to show unsanitized values.
|
||||||
* with or regular expressions.
|
|
||||||
*/
|
*/
|
||||||
private String[] keysToSanitize;
|
private Show showValues = Show.NEVER;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Keys that should be sanitized in addition to those already configured. Keys can be
|
* Roles used to determine whether a user is authorized to be shown unsanitized
|
||||||
* simple strings that the property ends with or regular expressions.
|
* values. When empty, all authenticated users are authorized.
|
||||||
*/
|
*/
|
||||||
private String[] additionalKeysToSanitize;
|
private Set<String> roles = new HashSet<>();
|
||||||
|
|
||||||
public String[] getKeysToSanitize() {
|
public Show getShowValues() {
|
||||||
return this.keysToSanitize;
|
return this.showValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setKeysToSanitize(String[] keysToSanitize) {
|
public void setShowValues(Show showValues) {
|
||||||
this.keysToSanitize = keysToSanitize;
|
this.showValues = showValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String[] getAdditionalKeysToSanitize() {
|
public Set<String> getRoles() {
|
||||||
return this.additionalKeysToSanitize;
|
return this.roles;
|
||||||
}
|
|
||||||
|
|
||||||
public void setAdditionalKeysToSanitize(String[] additionalKeysToSanitize) {
|
|
||||||
this.additionalKeysToSanitize = additionalKeysToSanitize;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,20 +16,15 @@
|
|||||||
|
|
||||||
package org.springframework.boot.actuate.autoconfigure.health;
|
package org.springframework.boot.actuate.autoconfigure.health;
|
||||||
|
|
||||||
import java.security.Principal;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.function.Predicate;
|
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.SecurityContext;
|
||||||
|
import org.springframework.boot.actuate.endpoint.Show;
|
||||||
import org.springframework.boot.actuate.health.AdditionalHealthEndpointPath;
|
import org.springframework.boot.actuate.health.AdditionalHealthEndpointPath;
|
||||||
import org.springframework.boot.actuate.health.HealthEndpointGroup;
|
import org.springframework.boot.actuate.health.HealthEndpointGroup;
|
||||||
import org.springframework.boot.actuate.health.HttpCodeStatusMapper;
|
import org.springframework.boot.actuate.health.HttpCodeStatusMapper;
|
||||||
import org.springframework.boot.actuate.health.StatusAggregator;
|
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}.
|
* Auto-configured {@link HealthEndpointGroup} backed by {@link HealthProperties}.
|
||||||
@@ -83,54 +78,13 @@ class AutoConfiguredHealthEndpointGroup implements HealthEndpointGroup {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean showComponents(SecurityContext securityContext) {
|
public boolean showComponents(SecurityContext securityContext) {
|
||||||
if (this.showComponents == null) {
|
Show show = (this.showComponents != null) ? this.showComponents : this.showDetails;
|
||||||
return showDetails(securityContext);
|
return show.isShown(securityContext, this.roles);
|
||||||
}
|
|
||||||
return getShowResult(securityContext, this.showComponents);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean showDetails(SecurityContext securityContext) {
|
public boolean showDetails(SecurityContext securityContext) {
|
||||||
return getShowResult(securityContext, this.showDetails);
|
return this.showDetails.isShown(securityContext, this.roles);
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ import org.springframework.beans.factory.ListableBeanFactory;
|
|||||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||||
import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils;
|
import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils;
|
||||||
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointProperties.Group;
|
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.autoconfigure.health.HealthProperties.Status;
|
||||||
|
import org.springframework.boot.actuate.endpoint.Show;
|
||||||
import org.springframework.boot.actuate.health.AdditionalHealthEndpointPath;
|
import org.springframework.boot.actuate.health.AdditionalHealthEndpointPath;
|
||||||
import org.springframework.boot.actuate.health.HealthEndpointGroup;
|
import org.springframework.boot.actuate.health.HealthEndpointGroup;
|
||||||
import org.springframework.boot.actuate.health.HealthEndpointGroups;
|
import org.springframework.boot.actuate.health.HealthEndpointGroups;
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import java.util.LinkedHashMap;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.springframework.boot.actuate.endpoint.Show;
|
||||||
import org.springframework.boot.actuate.health.HealthEndpoint;
|
import org.springframework.boot.actuate.health.HealthEndpoint;
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
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;
|
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.quartz.Scheduler;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.ObjectProvider;
|
||||||
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
|
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
|
||||||
import org.springframework.boot.actuate.autoconfigure.endpoint.expose.EndpointExposure;
|
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.QuartzEndpoint;
|
||||||
import org.springframework.boot.actuate.quartz.QuartzEndpointWebExtension;
|
import org.springframework.boot.actuate.quartz.QuartzEndpointWebExtension;
|
||||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
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.ConditionalOnClass;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||||
import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration;
|
import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration;
|
||||||
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -40,21 +43,23 @@ import org.springframework.context.annotation.Bean;
|
|||||||
@AutoConfiguration(after = QuartzAutoConfiguration.class)
|
@AutoConfiguration(after = QuartzAutoConfiguration.class)
|
||||||
@ConditionalOnClass(Scheduler.class)
|
@ConditionalOnClass(Scheduler.class)
|
||||||
@ConditionalOnAvailableEndpoint(endpoint = QuartzEndpoint.class)
|
@ConditionalOnAvailableEndpoint(endpoint = QuartzEndpoint.class)
|
||||||
|
@EnableConfigurationProperties(QuartzEndpointProperties.class)
|
||||||
public class QuartzEndpointAutoConfiguration {
|
public class QuartzEndpointAutoConfiguration {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnBean(Scheduler.class)
|
@ConditionalOnBean(Scheduler.class)
|
||||||
@ConditionalOnMissingBean
|
@ConditionalOnMissingBean
|
||||||
public QuartzEndpoint quartzEndpoint(Scheduler scheduler) {
|
public QuartzEndpoint quartzEndpoint(Scheduler scheduler, ObjectProvider<SanitizingFunction> sanitizingFunctions) {
|
||||||
return new QuartzEndpoint(scheduler);
|
return new QuartzEndpoint(scheduler, sanitizingFunctions.orderedStream().toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnBean(QuartzEndpoint.class)
|
@ConditionalOnBean(QuartzEndpoint.class)
|
||||||
@ConditionalOnMissingBean
|
@ConditionalOnMissingBean
|
||||||
@ConditionalOnAvailableEndpoint(exposure = { EndpointExposure.WEB, EndpointExposure.CLOUD_FOUNDRY })
|
@ConditionalOnAvailableEndpoint(exposure = { EndpointExposure.WEB, EndpointExposure.CLOUD_FOUNDRY })
|
||||||
public QuartzEndpointWebExtension quartzEndpointWebExtension(QuartzEndpoint endpoint) {
|
public QuartzEndpointWebExtension quartzEndpointWebExtension(QuartzEndpoint endpoint,
|
||||||
return new QuartzEndpointWebExtension(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.",
|
"description": "Whether to enable default metrics exporters.",
|
||||||
"defaultValue": true
|
"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",
|
"name": "management.endpoint.health.probes.add-additional-paths",
|
||||||
"type": "java.lang.Boolean",
|
"type": "java.lang.Boolean",
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
package org.springframework.boot.actuate.autoconfigure.context.properties;
|
package org.springframework.boot.actuate.autoconfigure.context.properties;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
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.ConfigurationPropertiesReportEndpoint.ApplicationConfigurationProperties;
|
||||||
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpointWebExtension;
|
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpointWebExtension;
|
||||||
import org.springframework.boot.actuate.endpoint.SanitizingFunction;
|
import org.springframework.boot.actuate.endpoint.SanitizingFunction;
|
||||||
|
import org.springframework.boot.actuate.endpoint.Show;
|
||||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
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.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.core.annotation.Order;
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
@@ -50,7 +53,7 @@ class ConfigurationPropertiesReportEndpointAutoConfigurationTests {
|
|||||||
void runShouldHaveEndpointBean() {
|
void runShouldHaveEndpointBean() {
|
||||||
this.contextRunner.withUserConfiguration(Config.class)
|
this.contextRunner.withUserConfiguration(Config.class)
|
||||||
.withPropertyValues("management.endpoints.web.exposure.include=configprops")
|
.withPropertyValues("management.endpoints.web.exposure.include=configprops")
|
||||||
.run(validateTestProperties("******", "654321"));
|
.run(validateTestProperties("******", "******"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -60,24 +63,42 @@ class ConfigurationPropertiesReportEndpointAutoConfigurationTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void keysToSanitizeCanBeConfiguredViaTheEnvironment() {
|
@SuppressWarnings("unchecked")
|
||||||
|
void rolesCanBeConfiguredViaTheEnvironment() {
|
||||||
this.contextRunner.withUserConfiguration(Config.class)
|
this.contextRunner.withUserConfiguration(Config.class)
|
||||||
.withPropertyValues("management.endpoint.configprops.keys-to-sanitize: .*pass.*, property")
|
.withPropertyValues("management.endpoint.configprops.roles: test")
|
||||||
.withPropertyValues("management.endpoints.web.exposure.include=configprops")
|
.withPropertyValues("management.endpoints.web.exposure.include=configprops").run((context) -> {
|
||||||
.run(validateTestProperties("******", "******"));
|
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
|
@Test
|
||||||
void additionalKeysToSanitizeCanBeConfiguredViaTheEnvironment() {
|
@SuppressWarnings("unchecked")
|
||||||
|
void showValuesCanBeConfiguredViaTheEnvironment() {
|
||||||
this.contextRunner.withUserConfiguration(Config.class)
|
this.contextRunner.withUserConfiguration(Config.class)
|
||||||
.withPropertyValues("management.endpoint.configprops.additional-keys-to-sanitize: property")
|
.withPropertyValues("management.endpoint.configprops.show-values: WHEN_AUTHORIZED")
|
||||||
.withPropertyValues("management.endpoints.web.exposure.include=configprops")
|
.withPropertyValues("management.endpoints.web.exposure.include=configprops").run((context) -> {
|
||||||
.run(validateTestProperties("******", "******"));
|
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
|
@Test
|
||||||
void customSanitizingFunctionsAreAppliedInOrder() {
|
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",
|
.withPropertyValues("management.endpoints.web.exposure.include=configprops",
|
||||||
"test.my-test-property=abc")
|
"test.my-test-property=abc")
|
||||||
.run(validateTestProperties("$$$111$$$", "$$$222$$$"));
|
.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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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;
|
package org.springframework.boot.actuate.autoconfigure.endpoint.web.documentation;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint;
|
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.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.Import;
|
import org.springframework.context.annotation.Import;
|
||||||
@@ -80,7 +83,7 @@ class ConfigurationPropertiesReportEndpointDocumentationTests extends MockMvcEnd
|
|||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
ConfigurationPropertiesReportEndpoint endpoint() {
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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;
|
package org.springframework.boot.actuate.autoconfigure.endpoint.web.documentation;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -27,6 +28,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
|||||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import org.springframework.boot.actuate.endpoint.Show;
|
||||||
import org.springframework.boot.actuate.env.EnvironmentEndpoint;
|
import org.springframework.boot.actuate.env.EnvironmentEndpoint;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
@@ -148,7 +150,7 @@ class EnvironmentEndpointDocumentationTests extends MockMvcEndpointDocumentation
|
|||||||
&& !"Inlined Test Properties".equals(propertySource.getName());
|
&& !"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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.impl.matchers.GroupMatcher;
|
||||||
import org.quartz.spi.OperableTrigger;
|
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.QuartzEndpoint;
|
||||||
import org.springframework.boot.actuate.quartz.QuartzEndpointWebExtension;
|
import org.springframework.boot.actuate.quartz.QuartzEndpointWebExtension;
|
||||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||||
@@ -456,12 +457,12 @@ class QuartzEndpointDocumentationTests extends MockMvcEndpointDocumentationTests
|
|||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
QuartzEndpoint endpoint(Scheduler scheduler) {
|
QuartzEndpoint endpoint(Scheduler scheduler) {
|
||||||
return new QuartzEndpoint(scheduler);
|
return new QuartzEndpoint(scheduler, Collections.emptyList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
QuartzEndpointWebExtension endpointWebExtension(QuartzEndpoint endpoint) {
|
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;
|
package org.springframework.boot.actuate.autoconfigure.env;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import org.springframework.boot.actuate.endpoint.SanitizingFunction;
|
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;
|
||||||
import org.springframework.boot.actuate.env.EnvironmentEndpoint.EnvironmentDescriptor;
|
import org.springframework.boot.actuate.env.EnvironmentEndpoint.EnvironmentDescriptor;
|
||||||
import org.springframework.boot.actuate.env.EnvironmentEndpoint.PropertySourceDescriptor;
|
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.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.core.annotation.Order;
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
@@ -64,17 +67,10 @@ class EnvironmentEndpointAutoConfigurationTests {
|
|||||||
this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(EnvironmentEndpoint.class));
|
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
|
@Test
|
||||||
void customSanitizingFunctionsAreAppliedInOrder() {
|
void customSanitizingFunctionsAreAppliedInOrder() {
|
||||||
this.contextRunner.withUserConfiguration(SanitizingFunctionConfiguration.class)
|
this.contextRunner.withUserConfiguration(SanitizingFunctionConfiguration.class)
|
||||||
|
.withPropertyValues("management.endpoint.env.show-values: WHEN_AUTHORIZED")
|
||||||
.withPropertyValues("management.endpoints.web.exposure.include=env")
|
.withPropertyValues("management.endpoints.web.exposure.include=env")
|
||||||
.withSystemProperties("custom=123456", "password=123456").run((context) -> {
|
.withSystemProperties("custom=123456", "password=123456").run((context) -> {
|
||||||
assertThat(context).hasSingleBean(EnvironmentEndpoint.class);
|
assertThat(context).hasSingleBean(EnvironmentEndpoint.class);
|
||||||
@@ -88,11 +84,34 @@ class EnvironmentEndpointAutoConfigurationTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void additionalKeysToSanitizeCanBeConfiguredViaTheEnvironment() {
|
@SuppressWarnings("unchecked")
|
||||||
this.contextRunner.withPropertyValues("management.endpoints.web.exposure.include=env")
|
void rolesCanBeConfiguredViaTheEnvironment() {
|
||||||
.withSystemProperties("dbPassword=123456", "apiKey=123456")
|
this.contextRunner.withPropertyValues("management.endpoint.env.roles: test")
|
||||||
.withPropertyValues("management.endpoint.env.additional-keys-to-sanitize=key")
|
.withPropertyValues("management.endpoints.web.exposure.include=env")
|
||||||
.run(validateSystemProperties("******", "******"));
|
.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
|
@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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
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.SecurityContext;
|
||||||
|
import org.springframework.boot.actuate.endpoint.Show;
|
||||||
import org.springframework.boot.actuate.health.HttpCodeStatusMapper;
|
import org.springframework.boot.actuate.health.HttpCodeStatusMapper;
|
||||||
import org.springframework.boot.actuate.health.StatusAggregator;
|
import org.springframework.boot.actuate.health.StatusAggregator;
|
||||||
import org.springframework.security.core.Authentication;
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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;
|
package org.springframework.boot.actuate.autoconfigure.quartz;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.quartz.Scheduler;
|
import org.quartz.Scheduler;
|
||||||
|
|
||||||
|
import org.springframework.boot.actuate.endpoint.Show;
|
||||||
import org.springframework.boot.actuate.quartz.QuartzEndpoint;
|
import org.springframework.boot.actuate.quartz.QuartzEndpoint;
|
||||||
import org.springframework.boot.actuate.quartz.QuartzEndpointWebExtension;
|
import org.springframework.boot.actuate.quartz.QuartzEndpointWebExtension;
|
||||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
@@ -81,6 +86,34 @@ class QuartzEndpointAutoConfigurationTests {
|
|||||||
.doesNotHaveBean(QuartzEndpointWebExtension.class));
|
.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)
|
@Configuration(proxyBeanMethods = false)
|
||||||
static class CustomEndpointConfiguration {
|
static class CustomEndpointConfiguration {
|
||||||
|
|
||||||
@@ -94,7 +127,7 @@ class QuartzEndpointAutoConfigurationTests {
|
|||||||
private static final class CustomEndpoint extends QuartzEndpoint {
|
private static final class CustomEndpoint extends QuartzEndpoint {
|
||||||
|
|
||||||
private CustomEndpoint() {
|
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.SanitizableData;
|
||||||
import org.springframework.boot.actuate.endpoint.Sanitizer;
|
import org.springframework.boot.actuate.endpoint.Sanitizer;
|
||||||
import org.springframework.boot.actuate.endpoint.SanitizingFunction;
|
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.Endpoint;
|
||||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||||
import org.springframework.boot.actuate.endpoint.annotation.Selector;
|
import org.springframework.boot.actuate.endpoint.annotation.Selector;
|
||||||
@@ -83,11 +84,11 @@ import org.springframework.util.StringUtils;
|
|||||||
* {@link ConfigurationProperties @ConfigurationProperties} annotated beans.
|
* {@link ConfigurationProperties @ConfigurationProperties} annotated beans.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* To protect sensitive information from being exposed, certain property values are masked
|
* To protect sensitive information from being exposed, all property values are masked by
|
||||||
* if their names end with a set of configurable values (default "password" and "secret").
|
* default. To configure when property values should be shown, use
|
||||||
* Configure property names by using
|
* {@code management.endpoint.configprops.show-values} and
|
||||||
* {@code management.endpoint.configprops.keys-to-sanitize} in your Spring Boot
|
* {@code management.endpoint.configprops.roles} in your Spring Boot application
|
||||||
* application configuration.
|
* configuration.
|
||||||
*
|
*
|
||||||
* @author Christian Dupuis
|
* @author Christian Dupuis
|
||||||
* @author Dave Syer
|
* @author Dave Syer
|
||||||
@@ -104,16 +105,15 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
|
|||||||
|
|
||||||
private final Sanitizer sanitizer;
|
private final Sanitizer sanitizer;
|
||||||
|
|
||||||
|
private final Show showValues;
|
||||||
|
|
||||||
private ApplicationContext context;
|
private ApplicationContext context;
|
||||||
|
|
||||||
private ObjectMapper objectMapper;
|
private ObjectMapper objectMapper;
|
||||||
|
|
||||||
public ConfigurationPropertiesReportEndpoint() {
|
public ConfigurationPropertiesReportEndpoint(Iterable<SanitizingFunction> sanitizingFunctions, Show showValues) {
|
||||||
this(Collections.emptyList());
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConfigurationPropertiesReportEndpoint(Iterable<SanitizingFunction> sanitizingFunctions) {
|
|
||||||
this.sanitizer = new Sanitizer(sanitizingFunctions);
|
this.sanitizer = new Sanitizer(sanitizingFunctions);
|
||||||
|
this.showValues = showValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -121,31 +121,35 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
|
|||||||
this.context = context;
|
this.context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setKeysToSanitize(String... keysToSanitize) {
|
|
||||||
this.sanitizer.setKeysToSanitize(keysToSanitize);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void keysToSanitize(String... keysToSanitize) {
|
|
||||||
this.sanitizer.keysToSanitize(keysToSanitize);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ReadOperation
|
@ReadOperation
|
||||||
public ApplicationConfigurationProperties configurationProperties() {
|
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
|
@ReadOperation
|
||||||
public ApplicationConfigurationProperties configurationPropertiesWithPrefix(@Selector String prefix) {
|
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,
|
ApplicationConfigurationProperties getConfigurationProperties(String prefix, boolean showUnsanitized) {
|
||||||
Predicate<ConfigurationPropertiesBean> beanFilterPredicate) {
|
return getConfigurationProperties(this.context, (bean) -> bean.getAnnotation().prefix().startsWith(prefix),
|
||||||
|
showUnsanitized);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ApplicationConfigurationProperties getConfigurationProperties(ApplicationContext context,
|
||||||
|
Predicate<ConfigurationPropertiesBean> beanFilterPredicate, boolean showUnsanitized) {
|
||||||
ObjectMapper mapper = getObjectMapper();
|
ObjectMapper mapper = getObjectMapper();
|
||||||
Map<String, ContextConfigurationProperties> contexts = new HashMap<>();
|
Map<String, ContextConfigurationProperties> contexts = new HashMap<>();
|
||||||
ApplicationContext target = context;
|
ApplicationContext target = context;
|
||||||
|
|
||||||
while (target != null) {
|
while (target != null) {
|
||||||
contexts.put(target.getId(), describeBeans(mapper, target, beanFilterPredicate));
|
contexts.put(target.getId(), describeBeans(mapper, target, beanFilterPredicate, showUnsanitized));
|
||||||
target = target.getParent();
|
target = target.getParent();
|
||||||
}
|
}
|
||||||
return new ApplicationConfigurationProperties(contexts);
|
return new ApplicationConfigurationProperties(contexts);
|
||||||
@@ -196,20 +200,21 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
|
|||||||
}
|
}
|
||||||
|
|
||||||
private ContextConfigurationProperties describeBeans(ObjectMapper mapper, ApplicationContext context,
|
private ContextConfigurationProperties describeBeans(ObjectMapper mapper, ApplicationContext context,
|
||||||
Predicate<ConfigurationPropertiesBean> beanFilterPredicate) {
|
Predicate<ConfigurationPropertiesBean> beanFilterPredicate, boolean showUnsanitized) {
|
||||||
Map<String, ConfigurationPropertiesBean> beans = ConfigurationPropertiesBean.getAll(context);
|
Map<String, ConfigurationPropertiesBean> beans = ConfigurationPropertiesBean.getAll(context);
|
||||||
Map<String, ConfigurationPropertiesBeanDescriptor> descriptors = beans.values().stream()
|
Map<String, ConfigurationPropertiesBeanDescriptor> descriptors = beans.values().stream()
|
||||||
.filter(beanFilterPredicate)
|
.filter(beanFilterPredicate).collect(Collectors.toMap(ConfigurationPropertiesBean::getName,
|
||||||
.collect(Collectors.toMap(ConfigurationPropertiesBean::getName, (bean) -> describeBean(mapper, bean)));
|
(bean) -> describeBean(mapper, bean, showUnsanitized)));
|
||||||
return new ContextConfigurationProperties(descriptors,
|
return new ContextConfigurationProperties(descriptors,
|
||||||
(context.getParent() != null) ? context.getParent().getId() : null);
|
(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();
|
String prefix = bean.getAnnotation().prefix();
|
||||||
Map<String, Object> serialized = safeSerialize(mapper, bean.getInstance(), prefix);
|
Map<String, Object> serialized = safeSerialize(mapper, bean.getInstance(), prefix);
|
||||||
Map<String, Object> properties = sanitize(prefix, serialized);
|
Map<String, Object> properties = sanitize(prefix, serialized, showUnsanitized);
|
||||||
Map<String, Object> inputs = getInputs(prefix, serialized);
|
Map<String, Object> inputs = getInputs(prefix, serialized, showUnsanitized);
|
||||||
return new ConfigurationPropertiesBeanDescriptor(prefix, properties, inputs);
|
return new ConfigurationPropertiesBeanDescriptor(prefix, properties, inputs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,35 +241,36 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
|
|||||||
* information.
|
* information.
|
||||||
* @param prefix the property prefix
|
* @param prefix the property prefix
|
||||||
* @param map the source map
|
* @param map the source map
|
||||||
|
* @param showUnsanitized whether to show the unsanitized values
|
||||||
* @return the sanitized map
|
* @return the sanitized map
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@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) -> {
|
map.forEach((key, value) -> {
|
||||||
String qualifiedKey = getQualifiedKey(prefix, key);
|
String qualifiedKey = getQualifiedKey(prefix, key);
|
||||||
if (value instanceof Map) {
|
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) {
|
else if (value instanceof List) {
|
||||||
map.put(key, sanitize(qualifiedKey, (List<Object>) value));
|
map.put(key, sanitize(qualifiedKey, (List<Object>) value, showUnsanitized));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
map.put(key, sanitizeWithPropertySourceIfPresent(qualifiedKey, value));
|
map.put(key, sanitizeWithPropertySourceIfPresent(qualifiedKey, value, showUnsanitized));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Object sanitizeWithPropertySourceIfPresent(String qualifiedKey, Object value) {
|
private Object sanitizeWithPropertySourceIfPresent(String qualifiedKey, Object value, boolean showUnsanitized) {
|
||||||
ConfigurationPropertyName currentName = getCurrentName(qualifiedKey);
|
ConfigurationPropertyName currentName = getCurrentName(qualifiedKey);
|
||||||
ConfigurationProperty candidate = getCandidate(currentName);
|
ConfigurationProperty candidate = getCandidate(currentName);
|
||||||
PropertySource<?> propertySource = getPropertySource(candidate);
|
PropertySource<?> propertySource = getPropertySource(candidate);
|
||||||
if (propertySource != null) {
|
if (propertySource != null) {
|
||||||
SanitizableData data = new SanitizableData(propertySource, qualifiedKey, value);
|
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);
|
SanitizableData data = new SanitizableData(null, qualifiedKey, value);
|
||||||
return this.sanitizer.sanitize(data);
|
return this.sanitizer.sanitize(data, showUnsanitized);
|
||||||
}
|
}
|
||||||
|
|
||||||
private PropertySource<?> getPropertySource(ConfigurationProperty configurationProperty) {
|
private PropertySource<?> getPropertySource(ConfigurationProperty configurationProperty) {
|
||||||
@@ -293,69 +299,69 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@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<>();
|
List<Object> sanitized = new ArrayList<>();
|
||||||
int index = 0;
|
int index = 0;
|
||||||
for (Object item : list) {
|
for (Object item : list) {
|
||||||
String name = prefix + "[" + index++ + "]";
|
String name = prefix + "[" + index++ + "]";
|
||||||
if (item instanceof Map) {
|
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) {
|
else if (item instanceof List) {
|
||||||
sanitized.add(sanitize(name, (List<Object>) item));
|
sanitized.add(sanitize(name, (List<Object>) item, showUnsanitized));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
sanitized.add(sanitizeWithPropertySourceIfPresent(name, item));
|
sanitized.add(sanitizeWithPropertySourceIfPresent(name, item, showUnsanitized));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return sanitized;
|
return sanitized;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@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<String, Object> augmented = new LinkedHashMap<>(map);
|
||||||
map.forEach((key, value) -> {
|
map.forEach((key, value) -> {
|
||||||
String qualifiedKey = getQualifiedKey(prefix, key);
|
String qualifiedKey = getQualifiedKey(prefix, key);
|
||||||
if (value instanceof Map) {
|
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) {
|
else if (value instanceof List) {
|
||||||
augmented.put(key, getInputs(qualifiedKey, (List<Object>) value));
|
augmented.put(key, getInputs(qualifiedKey, (List<Object>) value, showUnsanitized));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
augmented.put(key, applyInput(qualifiedKey));
|
augmented.put(key, applyInput(qualifiedKey, showUnsanitized));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return augmented;
|
return augmented;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@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<>();
|
List<Object> augmented = new ArrayList<>();
|
||||||
int index = 0;
|
int index = 0;
|
||||||
for (Object item : list) {
|
for (Object item : list) {
|
||||||
String name = prefix + "[" + index++ + "]";
|
String name = prefix + "[" + index++ + "]";
|
||||||
if (item instanceof Map) {
|
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) {
|
else if (item instanceof List) {
|
||||||
augmented.add(getInputs(name, (List<Object>) item));
|
augmented.add(getInputs(name, (List<Object>) item, showUnsanitized));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
augmented.add(applyInput(name));
|
augmented.add(applyInput(name, showUnsanitized));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return augmented;
|
return augmented;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, Object> applyInput(String qualifiedKey) {
|
private Map<String, Object> applyInput(String qualifiedKey, boolean showUnsanitized) {
|
||||||
ConfigurationPropertyName currentName = getCurrentName(qualifiedKey);
|
ConfigurationPropertyName currentName = getCurrentName(qualifiedKey);
|
||||||
ConfigurationProperty candidate = getCandidate(currentName);
|
ConfigurationProperty candidate = getCandidate(currentName);
|
||||||
PropertySource<?> propertySource = getPropertySource(candidate);
|
PropertySource<?> propertySource = getPropertySource(candidate);
|
||||||
if (propertySource != null) {
|
if (propertySource != null) {
|
||||||
Object value = stringifyIfNecessary(candidate.getValue());
|
Object value = stringifyIfNecessary(candidate.getValue());
|
||||||
SanitizableData data = new SanitizableData(propertySource, currentName.toString(), value);
|
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();
|
return Collections.emptyMap();
|
||||||
}
|
}
|
||||||
@@ -550,7 +556,7 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
|
|||||||
|
|
||||||
private final Map<String, ContextConfigurationProperties> contexts;
|
private final Map<String, ContextConfigurationProperties> contexts;
|
||||||
|
|
||||||
private ApplicationConfigurationProperties(Map<String, ContextConfigurationProperties> contexts) {
|
ApplicationConfigurationProperties(Map<String, ContextConfigurationProperties> contexts) {
|
||||||
this.contexts = 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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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;
|
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.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.ReadOperation;
|
||||||
import org.springframework.boot.actuate.endpoint.annotation.Selector;
|
import org.springframework.boot.actuate.endpoint.annotation.Selector;
|
||||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
||||||
@@ -34,15 +38,29 @@ public class ConfigurationPropertiesReportEndpointWebExtension {
|
|||||||
|
|
||||||
private final ConfigurationPropertiesReportEndpoint delegate;
|
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.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
|
@ReadOperation
|
||||||
public WebEndpointResponse<ApplicationConfigurationProperties> configurationPropertiesWithPrefix(
|
public WebEndpointResponse<ApplicationConfigurationProperties> configurationPropertiesWithPrefix(
|
||||||
@Selector String prefix) {
|
SecurityContext securityContext, @Selector String prefix) {
|
||||||
ApplicationConfigurationProperties configurationProperties = this.delegate
|
boolean showUnsanitized = this.showValues.isShown(securityContext, this.roles);
|
||||||
.configurationPropertiesWithPrefix(prefix);
|
ApplicationConfigurationProperties configurationProperties = this.delegate.getConfigurationProperties(prefix,
|
||||||
|
showUnsanitized);
|
||||||
boolean foundMatchingBeans = configurationProperties.getContexts().values().stream()
|
boolean foundMatchingBeans = configurationProperties.getContexts().values().stream()
|
||||||
.anyMatch((context) -> !context.getBeans().isEmpty());
|
.anyMatch((context) -> !context.getBeans().isEmpty());
|
||||||
return (foundMatchingBeans) ? new WebEndpointResponse<>(configurationProperties, WebEndpointResponse.STATUS_OK)
|
return (foundMatchingBeans) ? new WebEndpointResponse<>(configurationProperties, WebEndpointResponse.STATUS_OK)
|
||||||
|
|||||||
@@ -17,17 +17,8 @@
|
|||||||
package org.springframework.boot.actuate.endpoint;
|
package org.springframework.boot.actuate.endpoint;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedHashSet;
|
|
||||||
import java.util.List;
|
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
|
* Strategy that should be used by endpoint implementations to sanitize potentially
|
||||||
@@ -46,148 +37,40 @@ import org.springframework.util.StringUtils;
|
|||||||
*/
|
*/
|
||||||
public class Sanitizer {
|
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<>();
|
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() {
|
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.
|
* Create a new {@link Sanitizer} instance with sanitizing functions.
|
||||||
* @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.
|
|
||||||
* @param sanitizingFunctions the sanitizing functions to apply
|
* @param sanitizingFunctions the sanitizing functions to apply
|
||||||
* @since 2.6.0
|
* @since 2.6.0
|
||||||
*/
|
*/
|
||||||
public Sanitizer(Iterable<SanitizingFunction> sanitizingFunctions) {
|
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);
|
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
|
* Sanitize the value from the given {@link SanitizableData} using the available
|
||||||
* {@link SanitizingFunction}s.
|
* {@link SanitizingFunction}s.
|
||||||
* @param data the sanitizable data
|
* @param data the sanitizable data
|
||||||
|
* @param showUnsanitized whether to show the unsanitized values or not
|
||||||
* @return the potentially updated data
|
* @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();
|
Object value = data.getValue();
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (!showUnsanitized) {
|
||||||
|
return SanitizableData.SANITIZED_VALUE;
|
||||||
|
}
|
||||||
for (SanitizingFunction sanitizingFunction : this.sanitizingFunctions) {
|
for (SanitizingFunction sanitizingFunction : this.sanitizingFunctions) {
|
||||||
data = sanitizingFunction.apply(data);
|
data = sanitizingFunction.apply(data);
|
||||||
Object sanitizedValue = data.getValue();
|
Object sanitizedValue = data.getValue();
|
||||||
@@ -198,26 +81,4 @@ public class Sanitizer {
|
|||||||
return value;
|
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.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
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.SanitizableData;
|
||||||
import org.springframework.boot.actuate.endpoint.Sanitizer;
|
import org.springframework.boot.actuate.endpoint.Sanitizer;
|
||||||
import org.springframework.boot.actuate.endpoint.SanitizingFunction;
|
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.Endpoint;
|
||||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||||
import org.springframework.boot.actuate.endpoint.annotation.Selector;
|
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.core.env.StandardEnvironment;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.util.ClassUtils;
|
import org.springframework.util.ClassUtils;
|
||||||
import org.springframework.util.PropertyPlaceholderHelper;
|
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.util.SystemPropertyUtils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link Endpoint @Endpoint} to expose {@link ConfigurableEnvironment environment}
|
* {@link Endpoint @Endpoint} to expose {@link ConfigurableEnvironment environment}
|
||||||
@@ -71,50 +69,48 @@ public class EnvironmentEndpoint {
|
|||||||
|
|
||||||
private final Environment environment;
|
private final Environment environment;
|
||||||
|
|
||||||
public EnvironmentEndpoint(Environment environment) {
|
private final Show showValues;
|
||||||
this(environment, Collections.emptyList());
|
|
||||||
}
|
|
||||||
|
|
||||||
public EnvironmentEndpoint(Environment environment, Iterable<SanitizingFunction> sanitizingFunctions) {
|
public EnvironmentEndpoint(Environment environment, Iterable<SanitizingFunction> sanitizingFunctions,
|
||||||
|
Show showValues) {
|
||||||
this.environment = environment;
|
this.environment = environment;
|
||||||
this.sanitizer = new Sanitizer(sanitizingFunctions);
|
this.sanitizer = new Sanitizer(sanitizingFunctions);
|
||||||
}
|
this.showValues = showValues;
|
||||||
|
|
||||||
public void setKeysToSanitize(String... keysToSanitize) {
|
|
||||||
this.sanitizer.setKeysToSanitize(keysToSanitize);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void keysToSanitize(String... keysToSanitize) {
|
|
||||||
this.sanitizer.keysToSanitize(keysToSanitize);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ReadOperation
|
@ReadOperation
|
||||||
public EnvironmentDescriptor environment(@Nullable String pattern) {
|
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)) {
|
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
|
private EnvironmentDescriptor getEnvironmentDescriptor(Predicate<String> propertyNamePredicate,
|
||||||
public EnvironmentEntryDescriptor environmentEntry(@Selector String toMatch) {
|
boolean showUnsanitized) {
|
||||||
return getEnvironmentEntryDescriptor(toMatch);
|
|
||||||
}
|
|
||||||
|
|
||||||
private EnvironmentDescriptor getEnvironmentDescriptor(Predicate<String> propertyNamePredicate) {
|
|
||||||
PlaceholdersResolver resolver = getResolver();
|
|
||||||
List<PropertySourceDescriptor> propertySources = new ArrayList<>();
|
List<PropertySourceDescriptor> propertySources = new ArrayList<>();
|
||||||
getPropertySourcesAsMap().forEach((sourceName, source) -> {
|
getPropertySourcesAsMap().forEach((sourceName, source) -> {
|
||||||
if (source instanceof EnumerablePropertySource) {
|
if (source instanceof EnumerablePropertySource) {
|
||||||
propertySources.add(describeSource(sourceName, (EnumerablePropertySource<?>) source, resolver,
|
propertySources.add(describeSource(sourceName, (EnumerablePropertySource<?>) source,
|
||||||
propertyNamePredicate));
|
propertyNamePredicate, showUnsanitized));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return new EnvironmentDescriptor(Arrays.asList(this.environment.getActiveProfiles()), propertySources);
|
return new EnvironmentDescriptor(Arrays.asList(this.environment.getActiveProfiles()), propertySources);
|
||||||
}
|
}
|
||||||
|
|
||||||
private EnvironmentEntryDescriptor getEnvironmentEntryDescriptor(String propertyName) {
|
@ReadOperation
|
||||||
Map<String, PropertyValueDescriptor> descriptors = getPropertySourceDescriptors(propertyName);
|
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);
|
PropertySummaryDescriptor summary = getPropertySummaryDescriptor(descriptors);
|
||||||
return new EnvironmentEntryDescriptor(summary, Arrays.asList(this.environment.getActiveProfiles()),
|
return new EnvironmentEntryDescriptor(summary, Arrays.asList(this.environment.getActiveProfiles()),
|
||||||
toPropertySourceDescriptors(descriptors));
|
toPropertySourceDescriptors(descriptors));
|
||||||
@@ -136,35 +132,31 @@ public class EnvironmentEndpoint {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, PropertyValueDescriptor> getPropertySourceDescriptors(String propertyName) {
|
private Map<String, PropertyValueDescriptor> getPropertySourceDescriptors(String propertyName,
|
||||||
|
boolean showUnsanitized) {
|
||||||
Map<String, PropertyValueDescriptor> propertySources = new LinkedHashMap<>();
|
Map<String, PropertyValueDescriptor> propertySources = new LinkedHashMap<>();
|
||||||
PlaceholdersResolver resolver = getResolver();
|
|
||||||
getPropertySourcesAsMap().forEach((sourceName, source) -> propertySources.put(sourceName,
|
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;
|
return propertySources;
|
||||||
}
|
}
|
||||||
|
|
||||||
private PropertySourceDescriptor describeSource(String sourceName, EnumerablePropertySource<?> source,
|
private PropertySourceDescriptor describeSource(String sourceName, EnumerablePropertySource<?> source,
|
||||||
PlaceholdersResolver resolver, Predicate<String> namePredicate) {
|
Predicate<String> namePredicate, boolean showUnsanitized) {
|
||||||
Map<String, PropertyValueDescriptor> properties = new LinkedHashMap<>();
|
Map<String, PropertyValueDescriptor> properties = new LinkedHashMap<>();
|
||||||
Stream.of(source.getPropertyNames()).filter(namePredicate)
|
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);
|
return new PropertySourceDescriptor(sourceName, properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private PropertyValueDescriptor describeValueOf(String name, PropertySource<?> source,
|
private PropertyValueDescriptor describeValueOf(String name, PropertySource<?> source, boolean showUnsanitized) {
|
||||||
PlaceholdersResolver resolver) {
|
PlaceholdersResolver resolver = new PropertySourcesPlaceholdersResolver(getPropertySources());
|
||||||
Object resolved = resolver.resolvePlaceholders(source.getProperty(name));
|
Object resolved = resolver.resolvePlaceholders(source.getProperty(name));
|
||||||
Origin origin = ((source instanceof OriginLookup) ? ((OriginLookup<Object>) source).getOrigin(name) : null);
|
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);
|
return new PropertyValueDescriptor(stringifyIfNecessary(sanitizedValue), origin);
|
||||||
}
|
}
|
||||||
|
|
||||||
private PlaceholdersResolver getResolver() {
|
|
||||||
return new PropertySourcesPlaceholdersSanitizingResolver(getPropertySources(), this.sanitizer);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map<String, PropertySource<?>> getPropertySourcesAsMap() {
|
private Map<String, PropertySource<?>> getPropertySourcesAsMap() {
|
||||||
Map<String, PropertySource<?>> map = new LinkedHashMap<>();
|
Map<String, PropertySource<?>> map = new LinkedHashMap<>();
|
||||||
for (PropertySource<?> source : getPropertySources()) {
|
for (PropertySource<?> source : getPropertySources()) {
|
||||||
@@ -193,8 +185,8 @@ public class EnvironmentEndpoint {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Object sanitize(PropertySource<?> source, String name, Object value) {
|
private Object sanitize(PropertySource<?> source, String name, Object value, boolean showUnsanitized) {
|
||||||
return this.sanitizer.sanitize(new SanitizableData(source, name, value));
|
return this.sanitizer.sanitize(new SanitizableData(source, name, value), showUnsanitized);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Object stringifyIfNecessary(Object value) {
|
protected Object stringifyIfNecessary(Object value) {
|
||||||
@@ -208,40 +200,6 @@ public class EnvironmentEndpoint {
|
|||||||
return "Complex property type " + value.getClass().getName();
|
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}.
|
* A description of an {@link Environment}.
|
||||||
*/
|
*/
|
||||||
@@ -278,7 +236,7 @@ public class EnvironmentEndpoint {
|
|||||||
|
|
||||||
private final List<PropertySourceEntryDescriptor> propertySources;
|
private final List<PropertySourceEntryDescriptor> propertySources;
|
||||||
|
|
||||||
private EnvironmentEntryDescriptor(PropertySummaryDescriptor property, List<String> activeProfiles,
|
EnvironmentEntryDescriptor(PropertySummaryDescriptor property, List<String> activeProfiles,
|
||||||
List<PropertySourceEntryDescriptor> propertySources) {
|
List<PropertySourceEntryDescriptor> propertySources) {
|
||||||
this.property = property;
|
this.property = property;
|
||||||
this.activeProfiles = activeProfiles;
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -16,11 +16,17 @@
|
|||||||
|
|
||||||
package org.springframework.boot.actuate.env;
|
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.ReadOperation;
|
||||||
import org.springframework.boot.actuate.endpoint.annotation.Selector;
|
import org.springframework.boot.actuate.endpoint.annotation.Selector;
|
||||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
||||||
import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension;
|
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.boot.actuate.env.EnvironmentEndpoint.EnvironmentEntryDescriptor;
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link EndpointWebExtension @EndpointWebExtension} for the {@link EnvironmentEndpoint}.
|
* {@link EndpointWebExtension @EndpointWebExtension} for the {@link EnvironmentEndpoint}.
|
||||||
@@ -34,13 +40,27 @@ public class EnvironmentEndpointWebExtension {
|
|||||||
|
|
||||||
private final EnvironmentEndpoint delegate;
|
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.delegate = delegate;
|
||||||
|
this.showValues = showValues;
|
||||||
|
this.roles = roles;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ReadOperation
|
@ReadOperation
|
||||||
public WebEndpointResponse<EnvironmentEntryDescriptor> environmentEntry(@Selector String toMatch) {
|
public EnvironmentDescriptor environment(SecurityContext securityContext, @Nullable String pattern) {
|
||||||
EnvironmentEntryDescriptor descriptor = this.delegate.environmentEntry(toMatch);
|
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)
|
return (descriptor.getProperty() != null) ? new WebEndpointResponse<>(descriptor, WebEndpointResponse.STATUS_OK)
|
||||||
: new WebEndpointResponse<>(WebEndpointResponse.STATUS_NOT_FOUND);
|
: new WebEndpointResponse<>(WebEndpointResponse.STATUS_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,9 @@ import org.quartz.Trigger.TriggerState;
|
|||||||
import org.quartz.TriggerKey;
|
import org.quartz.TriggerKey;
|
||||||
import org.quartz.impl.matchers.GroupMatcher;
|
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.Sanitizer;
|
||||||
|
import org.springframework.boot.actuate.endpoint.SanitizingFunction;
|
||||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
@@ -71,25 +73,10 @@ public class QuartzEndpoint {
|
|||||||
|
|
||||||
private final Sanitizer sanitizer;
|
private final Sanitizer sanitizer;
|
||||||
|
|
||||||
/**
|
public QuartzEndpoint(Scheduler scheduler, Iterable<SanitizingFunction> sanitizingFunctions) {
|
||||||
* 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) {
|
|
||||||
Assert.notNull(scheduler, "Scheduler must not be null");
|
Assert.notNull(scheduler, "Scheduler must not be null");
|
||||||
Assert.notNull(sanitizer, "Sanitizer must not be null");
|
|
||||||
this.scheduler = scheduler;
|
this.scheduler = scheduler;
|
||||||
this.sanitizer = sanitizer;
|
this.sanitizer = new Sanitizer(sanitizingFunctions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -201,17 +188,19 @@ public class QuartzEndpoint {
|
|||||||
* group name and job name.
|
* group name and job name.
|
||||||
* @param groupName the name of the group
|
* @param groupName the name of the group
|
||||||
* @param jobName the name of the job
|
* @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
|
* @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
|
* @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);
|
JobKey jobKey = JobKey.jobKey(jobName, groupName);
|
||||||
JobDetail jobDetail = this.scheduler.getJobDetail(jobKey);
|
JobDetail jobDetail = this.scheduler.getJobDetail(jobKey);
|
||||||
if (jobDetail != null) {
|
if (jobDetail != null) {
|
||||||
List<? extends Trigger> triggers = this.scheduler.getTriggersOfJob(jobKey);
|
List<? extends Trigger> triggers = this.scheduler.getTriggersOfJob(jobKey);
|
||||||
return new QuartzJobDetails(jobDetail.getKey().getGroup(), jobDetail.getKey().getName(),
|
return new QuartzJobDetails(jobDetail.getKey().getGroup(), jobDetail.getKey().getName(),
|
||||||
jobDetail.getDescription(), jobDetail.getJobClass().getName(), jobDetail.isDurable(),
|
jobDetail.getDescription(), jobDetail.getJobClass().getName(), jobDetail.isDurable(),
|
||||||
jobDetail.requestsRecovery(), sanitizeJobDataMap(jobDetail.getJobDataMap()),
|
jobDetail.requestsRecovery(), sanitizeJobDataMap(jobDetail.getJobDataMap(), showUnsanitized),
|
||||||
extractTriggersSummary(triggers));
|
extractTriggersSummary(triggers));
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -236,14 +225,18 @@ public class QuartzEndpoint {
|
|||||||
* name.
|
* name.
|
||||||
* @param groupName the name of the group
|
* @param groupName the name of the group
|
||||||
* @param triggerName the name of the trigger
|
* @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
|
* @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
|
* @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);
|
TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, groupName);
|
||||||
Trigger trigger = this.scheduler.getTrigger(triggerKey);
|
Trigger trigger = this.scheduler.getTrigger(triggerKey);
|
||||||
return (trigger != null) ? TriggerDescription.of(trigger).buildDetails(
|
return (trigger != null)
|
||||||
this.scheduler.getTriggerState(triggerKey), sanitizeJobDataMap(trigger.getJobDataMap())) : null;
|
? TriggerDescription.of(trigger).buildDetails(this.scheduler.getTriggerState(triggerKey),
|
||||||
|
sanitizeJobDataMap(trigger.getJobDataMap(), showUnsanitized))
|
||||||
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Duration getIntervalDuration(long amount, IntervalUnit unit) {
|
private static Duration getIntervalDuration(long amount, IntervalUnit unit) {
|
||||||
@@ -255,15 +248,20 @@ public class QuartzEndpoint {
|
|||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, Object> sanitizeJobDataMap(JobDataMap dataMap) {
|
private Map<String, Object> sanitizeJobDataMap(JobDataMap dataMap, boolean showUnsanitized) {
|
||||||
if (dataMap != null) {
|
if (dataMap != null) {
|
||||||
Map<String, Object> map = new LinkedHashMap<>(dataMap.getWrappedMap());
|
Map<String, Object> map = new LinkedHashMap<>(dataMap.getWrappedMap());
|
||||||
map.replaceAll(this.sanitizer::sanitize);
|
map.replaceAll((key, value) -> getSanitizedValue(showUnsanitized, key, value));
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
return null;
|
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) {
|
private static TemporalUnit temporalUnit(IntervalUnit unit) {
|
||||||
return switch (unit) {
|
return switch (unit) {
|
||||||
case DAY -> ChronoUnit.DAYS;
|
case DAY -> ChronoUnit.DAYS;
|
||||||
|
|||||||
@@ -16,10 +16,14 @@
|
|||||||
|
|
||||||
package org.springframework.boot.actuate.quartz;
|
package org.springframework.boot.actuate.quartz;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import org.quartz.SchedulerException;
|
import org.quartz.SchedulerException;
|
||||||
|
|
||||||
import org.springframework.aot.hint.RuntimeHints;
|
import org.springframework.aot.hint.RuntimeHints;
|
||||||
import org.springframework.aot.hint.RuntimeHintsRegistrar;
|
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.ReadOperation;
|
||||||
import org.springframework.boot.actuate.endpoint.annotation.Selector;
|
import org.springframework.boot.actuate.endpoint.annotation.Selector;
|
||||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
||||||
@@ -44,8 +48,14 @@ public class QuartzEndpointWebExtension {
|
|||||||
|
|
||||||
private final QuartzEndpoint delegate;
|
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.delegate = delegate;
|
||||||
|
this.showValues = showValues;
|
||||||
|
this.roles = roles;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ReadOperation
|
@ReadOperation
|
||||||
@@ -62,10 +72,11 @@ public class QuartzEndpointWebExtension {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ReadOperation
|
@ReadOperation
|
||||||
public WebEndpointResponse<Object> quartzJobOrTrigger(@Selector String jobsOrTriggers, @Selector String group,
|
public WebEndpointResponse<Object> quartzJobOrTrigger(SecurityContext securityContext,
|
||||||
@Selector String name) throws SchedulerException {
|
@Selector String jobsOrTriggers, @Selector String group, @Selector String name) throws SchedulerException {
|
||||||
return handle(jobsOrTriggers, () -> this.delegate.quartzJob(group, name),
|
boolean showUnsanitized = this.showValues.isShown(securityContext, this.roles);
|
||||||
() -> this.delegate.quartzTrigger(group, name));
|
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,
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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;
|
package org.springframework.boot.actuate.context.properties;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ApplicationConfigurationProperties;
|
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.context.properties.ConfigurationPropertiesReportEndpoint.ContextConfigurationProperties;
|
||||||
|
import org.springframework.boot.actuate.endpoint.Show;
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Import;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
@@ -39,16 +45,7 @@ class ConfigurationPropertiesReportEndpointFilteringTests {
|
|||||||
void filterByPrefixSingleMatch() {
|
void filterByPrefixSingleMatch() {
|
||||||
ApplicationContextRunner contextRunner = new ApplicationContextRunner().withUserConfiguration(Config.class)
|
ApplicationContextRunner contextRunner = new ApplicationContextRunner().withUserConfiguration(Config.class)
|
||||||
.withPropertyValues("foo.primary.name:foo1", "foo.secondary.name:foo2", "only.bar.name:solo1");
|
.withPropertyValues("foo.primary.name:foo1", "foo.secondary.name:foo2", "only.bar.name:solo1");
|
||||||
contextRunner.run((context) -> {
|
assertProperties(contextRunner, "solo1");
|
||||||
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");
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@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)
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
@Import(BaseConfiguration.class)
|
||||||
@EnableConfigurationProperties(Bar.class)
|
@EnableConfigurationProperties(Bar.class)
|
||||||
static class Config {
|
static class Config {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
ConfigurationPropertiesReportEndpoint endpoint() {
|
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
|
@Bean
|
||||||
@ConfigurationProperties(prefix = "foo.primary")
|
@ConfigurationProperties(prefix = "foo.primary")
|
||||||
Foo primaryFoo() {
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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;
|
package org.springframework.boot.actuate.context.properties;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ApplicationConfigurationProperties;
|
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.ConfigurationPropertiesBeanDescriptor;
|
||||||
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ContextConfigurationProperties;
|
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.ConfigurationProperties;
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||||
@@ -79,7 +82,7 @@ class ConfigurationPropertiesReportEndpointMethodAnnotationsTests {
|
|||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
ConfigurationPropertiesReportEndpoint endpoint() {
|
ConfigurationPropertiesReportEndpoint endpoint() {
|
||||||
return new ConfigurationPropertiesReportEndpoint();
|
return new ConfigurationPropertiesReportEndpoint(Collections.emptyList(), Show.ALWAYS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@@ -102,7 +105,7 @@ class ConfigurationPropertiesReportEndpointMethodAnnotationsTests {
|
|||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
ConfigurationPropertiesReportEndpoint endpoint() {
|
ConfigurationPropertiesReportEndpoint endpoint() {
|
||||||
return new ConfigurationPropertiesReportEndpoint();
|
return new ConfigurationPropertiesReportEndpoint(Collections.emptyList(), Show.ALWAYS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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;
|
package org.springframework.boot.actuate.context.properties;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ApplicationConfigurationProperties;
|
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.ConfigurationProperties;
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||||
@@ -86,7 +89,7 @@ class ConfigurationPropertiesReportEndpointParentTests {
|
|||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
ConfigurationPropertiesReportEndpoint endpoint() {
|
ConfigurationPropertiesReportEndpoint endpoint() {
|
||||||
return new ConfigurationPropertiesReportEndpoint();
|
return new ConfigurationPropertiesReportEndpoint(Collections.emptyList(), Show.ALWAYS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@@ -102,7 +105,7 @@ class ConfigurationPropertiesReportEndpointParentTests {
|
|||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
ConfigurationPropertiesReportEndpoint endpoint() {
|
ConfigurationPropertiesReportEndpoint endpoint() {
|
||||||
return new ConfigurationPropertiesReportEndpoint();
|
return new ConfigurationPropertiesReportEndpoint(Collections.emptyList(), Show.ALWAYS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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;
|
package org.springframework.boot.actuate.context.properties;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import javax.sql.DataSource;
|
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.ApplicationConfigurationProperties;
|
||||||
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ConfigurationPropertiesBeanDescriptor;
|
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.ConfigurationProperties;
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||||
@@ -85,7 +87,7 @@ class ConfigurationPropertiesReportEndpointProxyTests {
|
|||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
ConfigurationPropertiesReportEndpoint endpoint() {
|
ConfigurationPropertiesReportEndpoint endpoint() {
|
||||||
return new ConfigurationPropertiesReportEndpoint();
|
return new ConfigurationPropertiesReportEndpoint(Collections.emptyList(), Show.ALWAYS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@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.ApplicationConfigurationProperties;
|
||||||
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ConfigurationPropertiesBeanDescriptor;
|
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.ConfigurationProperties;
|
||||||
import org.springframework.boot.context.properties.ConfigurationPropertiesBinding;
|
import org.springframework.boot.context.properties.ConfigurationPropertiesBinding;
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
@@ -290,7 +291,7 @@ class ConfigurationPropertiesReportEndpointSerializationTests {
|
|||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
ConfigurationPropertiesReportEndpoint endpoint() {
|
ConfigurationPropertiesReportEndpoint endpoint() {
|
||||||
return new ConfigurationPropertiesReportEndpoint();
|
return new ConfigurationPropertiesReportEndpoint(Collections.emptyList(), Show.ALWAYS);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -553,7 +554,7 @@ class ConfigurationPropertiesReportEndpointSerializationTests {
|
|||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
ConfigurationPropertiesReportEndpoint endpoint() {
|
ConfigurationPropertiesReportEndpoint endpoint() {
|
||||||
return new ConfigurationPropertiesReportEndpoint();
|
return new ConfigurationPropertiesReportEndpoint(Collections.emptyList(), Show.ALWAYS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@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.ConfigurationPropertiesBeanDescriptor;
|
||||||
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ContextConfigurationProperties;
|
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ContextConfigurationProperties;
|
||||||
import org.springframework.boot.actuate.endpoint.SanitizingFunction;
|
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.ConfigurationProperties;
|
||||||
import org.springframework.boot.context.properties.ConstructorBinding;
|
import org.springframework.boot.context.properties.ConstructorBinding;
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
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.ConfigurableApplicationContext;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.core.env.Environment;
|
|
||||||
import org.springframework.mock.env.MockPropertySource;
|
import org.springframework.mock.env.MockPropertySource;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
@@ -175,82 +175,10 @@ class ConfigurationPropertiesReportEndpointTests {
|
|||||||
(properties) -> assertThat(properties.get("mixedBoolean")).isEqualTo(true)));
|
(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
|
@Test
|
||||||
void sanitizeLists() {
|
void sanitizeLists() {
|
||||||
this.contextRunner.withUserConfiguration(SensiblePropertiesConfiguration.class)
|
new ApplicationContextRunner()
|
||||||
|
.withUserConfiguration(EndpointConfigWithShowNever.class, SensiblePropertiesConfiguration.class)
|
||||||
.withPropertyValues("sensible.listItems[0].some-password=password")
|
.withPropertyValues("sensible.listItems[0].some-password=password")
|
||||||
.run(assertProperties("sensible", (properties) -> {
|
.run(assertProperties("sensible", (properties) -> {
|
||||||
assertThat(properties.get("listItems")).isInstanceOf(List.class);
|
assertThat(properties.get("listItems")).isInstanceOf(List.class);
|
||||||
@@ -271,7 +199,8 @@ class ConfigurationPropertiesReportEndpointTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void listsOfListsAreSanitized() {
|
void listsOfListsAreSanitized() {
|
||||||
this.contextRunner.withUserConfiguration(SensiblePropertiesConfiguration.class)
|
new ApplicationContextRunner()
|
||||||
|
.withUserConfiguration(EndpointConfigWithShowNever.class, SensiblePropertiesConfiguration.class)
|
||||||
.withPropertyValues("sensible.listOfListItems[0][0].some-password=password")
|
.withPropertyValues("sensible.listOfListItems[0][0].some-password=password")
|
||||||
.run(assertProperties("sensible", (properties) -> {
|
.run(assertProperties("sensible", (properties) -> {
|
||||||
assertThat(properties.get("listOfListItems")).isInstanceOf(List.class);
|
assertThat(properties.get("listOfListItems")).isInstanceOf(List.class);
|
||||||
@@ -311,7 +240,7 @@ class ConfigurationPropertiesReportEndpointTests {
|
|||||||
.withUserConfiguration(CustomSanitizingEndpointConfig.class,
|
.withUserConfiguration(CustomSanitizingEndpointConfig.class,
|
||||||
PropertySourceBasedSanitizingFunctionConfiguration.class, TestPropertiesConfiguration.class)
|
PropertySourceBasedSanitizingFunctionConfiguration.class, TestPropertiesConfiguration.class)
|
||||||
.withPropertyValues("test.my-test-property=abcde").run(assertProperties("test", (properties) -> {
|
.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("$$$");
|
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
|
@Test
|
||||||
void originParents() {
|
void originParents() {
|
||||||
this.contextRunner.withUserConfiguration(SensiblePropertiesConfiguration.class)
|
this.contextRunner.withUserConfiguration(SensiblePropertiesConfiguration.class)
|
||||||
@@ -367,8 +316,9 @@ class ConfigurationPropertiesReportEndpointTests {
|
|||||||
return (context) -> {
|
return (context) -> {
|
||||||
ConfigurationPropertiesReportEndpoint endpoint = context
|
ConfigurationPropertiesReportEndpoint endpoint = context
|
||||||
.getBean(ConfigurationPropertiesReportEndpoint.class);
|
.getBean(ConfigurationPropertiesReportEndpoint.class);
|
||||||
ContextConfigurationProperties allProperties = endpoint.configurationProperties().getContexts()
|
ConfigurationPropertiesReportEndpoint.ApplicationConfigurationProperties configurationProperties = endpoint
|
||||||
.get(context.getId());
|
.configurationProperties();
|
||||||
|
ContextConfigurationProperties allProperties = configurationProperties.getContexts().get(context.getId());
|
||||||
Optional<String> key = allProperties.getBeans().keySet().stream()
|
Optional<String> key = allProperties.getBeans().keySet().stream()
|
||||||
.filter((id) -> findIdFromPrefix(prefix, id)).findAny();
|
.filter((id) -> findIdFromPrefix(prefix, id)).findAny();
|
||||||
assertThat(key).describedAs("No configuration properties with prefix '%s' found", prefix).isPresent();
|
assertThat(key).describedAs("No configuration properties with prefix '%s' found", prefix).isPresent();
|
||||||
@@ -421,12 +371,33 @@ class ConfigurationPropertiesReportEndpointTests {
|
|||||||
static class EndpointConfig {
|
static class EndpointConfig {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
ConfigurationPropertiesReportEndpoint endpoint(Environment environment) {
|
ConfigurationPropertiesReportEndpoint endpoint() {
|
||||||
ConfigurationPropertiesReportEndpoint endpoint = new ConfigurationPropertiesReportEndpoint();
|
ConfigurationPropertiesReportEndpoint endpoint = new ConfigurationPropertiesReportEndpoint(
|
||||||
String[] keys = environment.getProperty("test.keys-to-sanitize", String[].class);
|
Collections.emptyList(), Show.WHEN_AUTHORIZED);
|
||||||
if (keys != null) {
|
return endpoint;
|
||||||
endpoint.setKeysToSanitize(keys);
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@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;
|
return endpoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -891,13 +862,9 @@ class ConfigurationPropertiesReportEndpointTests {
|
|||||||
static class CustomSanitizingEndpointConfig {
|
static class CustomSanitizingEndpointConfig {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
ConfigurationPropertiesReportEndpoint endpoint(Environment environment, SanitizingFunction sanitizingFunction) {
|
ConfigurationPropertiesReportEndpoint endpoint(SanitizingFunction sanitizingFunction) {
|
||||||
ConfigurationPropertiesReportEndpoint endpoint = new ConfigurationPropertiesReportEndpoint(
|
ConfigurationPropertiesReportEndpoint endpoint = new ConfigurationPropertiesReportEndpoint(
|
||||||
Collections.singletonList(sanitizingFunction));
|
Collections.singletonList(sanitizingFunction), Show.ALWAYS);
|
||||||
String[] keys = environment.getProperty("test.keys-to-sanitize", String[].class);
|
|
||||||
if (keys != null) {
|
|
||||||
endpoint.setKeysToSanitize(keys);
|
|
||||||
}
|
|
||||||
return endpoint;
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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;
|
package org.springframework.boot.actuate.context.properties;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
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.actuate.endpoint.web.test.WebEndpointTest;
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
@@ -77,13 +80,13 @@ class ConfigurationPropertiesReportEndpointWebIntegrationTests {
|
|||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
ConfigurationPropertiesReportEndpoint endpoint() {
|
ConfigurationPropertiesReportEndpoint endpoint() {
|
||||||
return new ConfigurationPropertiesReportEndpoint();
|
return new ConfigurationPropertiesReportEndpoint(Collections.emptyList(), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
ConfigurationPropertiesReportEndpointWebExtension endpointWebExtension(
|
ConfigurationPropertiesReportEndpointWebExtension endpointWebExtension(
|
||||||
ConfigurationPropertiesReportEndpoint endpoint) {
|
ConfigurationPropertiesReportEndpoint endpoint) {
|
||||||
return new ConfigurationPropertiesReportEndpointWebExtension(endpoint);
|
return new ConfigurationPropertiesReportEndpointWebExtension(endpoint, Show.ALWAYS, Collections.emptySet());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
|||||||
@@ -19,11 +19,8 @@ package org.springframework.boot.actuate.endpoint;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
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;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
@@ -39,42 +36,21 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||||||
class SanitizerTests {
|
class SanitizerTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void defaultNonUriKeys() {
|
void whenNoSanitizationFunctionAndShowUnsanitizedIsFalse() {
|
||||||
Sanitizer sanitizer = new Sanitizer();
|
Sanitizer sanitizer = new Sanitizer();
|
||||||
assertThat(sanitizer.sanitize("password", "secret")).isEqualTo("******");
|
assertThat(sanitizer.sanitize(new SanitizableData(null, "password", "secret"), false)).isEqualTo("******");
|
||||||
assertThat(sanitizer.sanitize("my-password", "secret")).isEqualTo("******");
|
assertThat(sanitizer.sanitize(new SanitizableData(null, "other", "something"), false)).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("******");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void whenAdditionalKeysAreAddedValuesOfBothThemAndTheDefaultKeysAreSanitized() {
|
void whenNoSanitizationFunctionAndShowUnsanitizedIsTrue() {
|
||||||
Sanitizer sanitizer = new Sanitizer();
|
Sanitizer sanitizer = new Sanitizer();
|
||||||
sanitizer.keysToSanitize("find", "confidential");
|
assertThat(sanitizer.sanitize(new SanitizableData(null, "password", "secret"), true)).isEqualTo("secret");
|
||||||
assertThat(sanitizer.sanitize("password", "secret")).isEqualTo("******");
|
assertThat(sanitizer.sanitize(new SanitizableData(null, "other", "something"), true)).isEqualTo("something");
|
||||||
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");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void whenCustomSanitizingFunctionPresentValueShouldBeSanitized() {
|
void whenCustomSanitizationFunctionAndShowUnsanitizedIsFalse() {
|
||||||
Sanitizer sanitizer = new Sanitizer(Collections.singletonList((data) -> {
|
Sanitizer sanitizer = new Sanitizer(Collections.singletonList((data) -> {
|
||||||
if (data.getKey().equals("custom")) {
|
if (data.getKey().equals("custom")) {
|
||||||
return data.withValue("$$$$$$");
|
return data.withValue("$$$$$$");
|
||||||
@@ -82,11 +58,27 @@ class SanitizerTests {
|
|||||||
return data;
|
return data;
|
||||||
}));
|
}));
|
||||||
SanitizableData secret = new SanitizableData(null, "secret", "xyz");
|
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");
|
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");
|
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
|
@Test
|
||||||
@@ -98,7 +90,7 @@ class SanitizerTests {
|
|||||||
return data;
|
return data;
|
||||||
}));
|
}));
|
||||||
SanitizableData password = new SanitizableData(null, "password", "123456");
|
SanitizableData password = new SanitizableData(null, "password", "123456");
|
||||||
assertThat(sanitizer.sanitize(password)).isEqualTo("------");
|
assertThat(sanitizer.sanitize(password, true)).isEqualTo("------");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -119,101 +111,7 @@ class SanitizerTests {
|
|||||||
});
|
});
|
||||||
Sanitizer sanitizer = new Sanitizer(sanitizingFunctions);
|
Sanitizer sanitizer = new Sanitizer(sanitizingFunctions);
|
||||||
SanitizableData custom = new SanitizableData(null, sameKey, "123456");
|
SanitizableData custom = new SanitizableData(null, sameKey, "123456");
|
||||||
assertThat(sanitizer.sanitize(custom)).isEqualTo("------");
|
assertThat(sanitizer.sanitize(custom, true)).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");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.AfterEach;
|
||||||
import org.junit.jupiter.api.Test;
|
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.EnvironmentDescriptor;
|
||||||
import org.springframework.boot.actuate.env.EnvironmentEndpoint.EnvironmentEntryDescriptor;
|
import org.springframework.boot.actuate.env.EnvironmentEndpoint.EnvironmentEntryDescriptor;
|
||||||
import org.springframework.boot.actuate.env.EnvironmentEndpoint.PropertySourceDescriptor;
|
import org.springframework.boot.actuate.env.EnvironmentEndpoint.PropertySourceDescriptor;
|
||||||
@@ -73,7 +74,8 @@ class EnvironmentEndpointTests {
|
|||||||
ConfigurableEnvironment environment = emptyEnvironment();
|
ConfigurableEnvironment environment = emptyEnvironment();
|
||||||
environment.getPropertySources().addLast(singleKeyPropertySource("one", "my.key", "first"));
|
environment.getPropertySources().addLast(singleKeyPropertySource("one", "my.key", "first"));
|
||||||
environment.getPropertySources().addLast(singleKeyPropertySource("two", "my.key", "second"));
|
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();
|
assertThat(descriptor.getActiveProfiles()).isEmpty();
|
||||||
Map<String, PropertySourceDescriptor> sources = propertySources(descriptor);
|
Map<String, PropertySourceDescriptor> sources = propertySources(descriptor);
|
||||||
assertThat(sources.keySet()).containsExactly("one", "two");
|
assertThat(sources.keySet()).containsExactly("one", "two");
|
||||||
@@ -81,6 +83,38 @@ class EnvironmentEndpointTests {
|
|||||||
assertThat(sources.get("two").getProperties()).containsOnlyKeys("my.key");
|
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
|
@Test
|
||||||
void compositeSourceIsHandledCorrectly() {
|
void compositeSourceIsHandledCorrectly() {
|
||||||
ConfigurableEnvironment environment = emptyEnvironment();
|
ConfigurableEnvironment environment = emptyEnvironment();
|
||||||
@@ -88,82 +122,14 @@ class EnvironmentEndpointTests {
|
|||||||
source.addPropertySource(new MapPropertySource("one", Collections.singletonMap("foo", "bar")));
|
source.addPropertySource(new MapPropertySource("one", Collections.singletonMap("foo", "bar")));
|
||||||
source.addPropertySource(new MapPropertySource("two", Collections.singletonMap("foo", "spam")));
|
source.addPropertySource(new MapPropertySource("two", Collections.singletonMap("foo", "spam")));
|
||||||
environment.getPropertySources().addFirst(source);
|
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);
|
Map<String, PropertySourceDescriptor> sources = propertySources(descriptor);
|
||||||
assertThat(sources.keySet()).containsExactly("composite:one", "composite:two");
|
assertThat(sources.keySet()).containsExactly("composite:one", "composite:two");
|
||||||
assertThat(sources.get("composite:one").getProperties().get("foo").getValue()).isEqualTo("bar");
|
assertThat(sources.get("composite:one").getProperties().get("foo").getValue()).isEqualTo("bar");
|
||||||
assertThat(sources.get("composite:two").getProperties().get("foo").getValue()).isEqualTo("spam");
|
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
|
@Test
|
||||||
void keysMatchingCustomSanitizingFunctionHaveTheirValuesSanitized() {
|
void keysMatchingCustomSanitizingFunctionHaveTheirValuesSanitized() {
|
||||||
ConfigurableEnvironment environment = new StandardEnvironment();
|
ConfigurableEnvironment environment = new StandardEnvironment();
|
||||||
@@ -176,7 +142,7 @@ class EnvironmentEndpointTests {
|
|||||||
return data.withValue("******");
|
return data.withValue("******");
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
})).environment(null);
|
}), Show.ALWAYS).environment(null);
|
||||||
assertThat(propertySources(descriptor).get("test").getProperties().get("other.service").getValue())
|
assertThat(propertySources(descriptor).get("test").getProperties().get("other.service").getValue())
|
||||||
.isEqualTo("abcde");
|
.isEqualTo("abcde");
|
||||||
Map<String, PropertyValueDescriptor> systemProperties = propertySources(descriptor).get("systemProperties")
|
Map<String, PropertyValueDescriptor> systemProperties = propertySources(descriptor).get("systemProperties")
|
||||||
@@ -190,7 +156,8 @@ class EnvironmentEndpointTests {
|
|||||||
void propertyWithPlaceholderResolved() {
|
void propertyWithPlaceholderResolved() {
|
||||||
ConfigurableEnvironment environment = emptyEnvironment();
|
ConfigurableEnvironment environment = emptyEnvironment();
|
||||||
TestPropertyValues.of("my.foo: ${bar.blah}", "bar.blah: hello").applyTo(environment);
|
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");
|
assertThat(propertySources(descriptor).get("test").getProperties().get("my.foo").getValue()).isEqualTo("hello");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,46 +165,19 @@ class EnvironmentEndpointTests {
|
|||||||
void propertyWithPlaceholderNotResolved() {
|
void propertyWithPlaceholderNotResolved() {
|
||||||
ConfigurableEnvironment environment = emptyEnvironment();
|
ConfigurableEnvironment environment = emptyEnvironment();
|
||||||
TestPropertyValues.of("my.foo: ${bar.blah}").applyTo(environment);
|
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())
|
assertThat(propertySources(descriptor).get("test").getProperties().get("my.foo").getValue())
|
||||||
.isEqualTo("${bar.blah}");
|
.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
|
@Test
|
||||||
void propertyWithComplexTypeShouldNotFail() {
|
void propertyWithComplexTypeShouldNotFail() {
|
||||||
ConfigurableEnvironment environment = emptyEnvironment();
|
ConfigurableEnvironment environment = emptyEnvironment();
|
||||||
environment.getPropertySources()
|
environment.getPropertySources()
|
||||||
.addFirst(singleKeyPropertySource("test", "foo", Collections.singletonMap("bar", "baz")));
|
.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();
|
String value = (String) propertySources(descriptor).get("test").getProperties().get("foo").getValue();
|
||||||
assertThat(value).isEqualTo("Complex property type java.util.Collections$SingletonMap");
|
assertThat(value).isEqualTo("Complex property type java.util.Collections$SingletonMap");
|
||||||
}
|
}
|
||||||
@@ -251,7 +191,8 @@ class EnvironmentEndpointTests {
|
|||||||
map.put("boolean", true);
|
map.put("boolean", true);
|
||||||
map.put("biginteger", BigInteger.valueOf(200));
|
map.put("biginteger", BigInteger.valueOf(200));
|
||||||
environment.getPropertySources().addFirst(new MapPropertySource("test", map));
|
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();
|
Map<String, PropertyValueDescriptor> properties = propertySources(descriptor).get("test").getProperties();
|
||||||
assertThat(properties.get("char").getValue()).isEqualTo('a');
|
assertThat(properties.get("char").getValue()).isEqualTo('a');
|
||||||
assertThat(properties.get("integer").getValue()).isEqualTo(100);
|
assertThat(properties.get("integer").getValue()).isEqualTo(100);
|
||||||
@@ -263,26 +204,42 @@ class EnvironmentEndpointTests {
|
|||||||
void propertyWithCharSequenceTypeIsConvertedToString() {
|
void propertyWithCharSequenceTypeIsConvertedToString() {
|
||||||
ConfigurableEnvironment environment = emptyEnvironment();
|
ConfigurableEnvironment environment = emptyEnvironment();
|
||||||
environment.getPropertySources().addFirst(singleKeyPropertySource("test", "foo", new CharSequenceProperty()));
|
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();
|
String value = (String) propertySources(descriptor).get("test").getProperties().get("foo").getValue();
|
||||||
assertThat(value).isEqualTo("test value");
|
assertThat(value).isEqualTo("test value");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void propertyEntry() {
|
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(() -> {
|
TestPropertyValues.of("my.foo=another").applyToSystemProperties(() -> {
|
||||||
StandardEnvironment environment = new StandardEnvironment();
|
StandardEnvironment environment = new StandardEnvironment();
|
||||||
TestPropertyValues.of("my.foo=bar", "my.foo2=bar2").applyTo(environment, TestPropertyValues.Type.MAP,
|
TestPropertyValues.of("my.foo=bar", "my.foo2=bar2").applyTo(environment, TestPropertyValues.Type.MAP,
|
||||||
"test");
|
"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).isNotNull();
|
||||||
assertThat(descriptor.getProperty()).isNotNull();
|
assertThat(descriptor.getProperty()).isNotNull();
|
||||||
assertThat(descriptor.getProperty().getSource()).isEqualTo("test");
|
assertThat(descriptor.getProperty().getSource()).isEqualTo("test");
|
||||||
assertThat(descriptor.getProperty().getValue()).isEqualTo("bar");
|
assertThat(descriptor.getProperty().getValue()).isEqualTo(bar);
|
||||||
Map<String, PropertySourceEntryDescriptor> sources = propertySources(descriptor);
|
Map<String, PropertySourceEntryDescriptor> sources = propertySources(descriptor);
|
||||||
assertThat(sources.keySet()).containsExactly("test", "systemProperties", "systemEnvironment");
|
assertThat(sources.keySet()).containsExactly("test", "systemProperties", "systemEnvironment");
|
||||||
assertPropertySourceEntryDescriptor(sources.get("test"), "bar", null);
|
assertPropertySourceEntryDescriptor(sources.get("test"), bar, null);
|
||||||
assertPropertySourceEntryDescriptor(sources.get("systemProperties"), "another", null);
|
assertPropertySourceEntryDescriptor(sources.get("systemProperties"), another, null);
|
||||||
assertPropertySourceEntryDescriptor(sources.get("systemEnvironment"), null, null);
|
assertPropertySourceEntryDescriptor(sources.get("systemEnvironment"), null, null);
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
@@ -294,7 +251,8 @@ class EnvironmentEndpointTests {
|
|||||||
OriginParentMockPropertySource propertySource = new OriginParentMockPropertySource();
|
OriginParentMockPropertySource propertySource = new OriginParentMockPropertySource();
|
||||||
propertySource.setProperty("name", "test");
|
propertySource.setProperty("name", "test");
|
||||||
environment.getPropertySources().addFirst(propertySource);
|
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");
|
PropertySourceEntryDescriptor entryDescriptor = propertySources(descriptor).get("mockProperties");
|
||||||
assertThat(entryDescriptor.getProperty().getOrigin()).isEqualTo("name");
|
assertThat(entryDescriptor.getProperty().getOrigin()).isEqualTo("name");
|
||||||
assertThat(entryDescriptor.getProperty().getOriginParents()).containsExactly("spring", "boot");
|
assertThat(entryDescriptor.getProperty().getOriginParents()).containsExactly("spring", "boot");
|
||||||
@@ -304,7 +262,8 @@ class EnvironmentEndpointTests {
|
|||||||
void propertyEntryNotFound() {
|
void propertyEntryNotFound() {
|
||||||
ConfigurableEnvironment environment = emptyEnvironment();
|
ConfigurableEnvironment environment = emptyEnvironment();
|
||||||
environment.getPropertySources().addFirst(singleKeyPropertySource("test", "foo", "bar"));
|
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).isNotNull();
|
||||||
assertThat(descriptor.getProperty()).isNull();
|
assertThat(descriptor.getProperty()).isNull();
|
||||||
Map<String, PropertySourceEntryDescriptor> sources = propertySources(descriptor);
|
Map<String, PropertySourceEntryDescriptor> sources = propertySources(descriptor);
|
||||||
@@ -317,33 +276,14 @@ class EnvironmentEndpointTests {
|
|||||||
ConfigurableEnvironment environment = emptyEnvironment();
|
ConfigurableEnvironment environment = emptyEnvironment();
|
||||||
environment.getPropertySources().addFirst(singleKeyPropertySource("one", "a", "alpha"));
|
environment.getPropertySources().addFirst(singleKeyPropertySource("one", "a", "alpha"));
|
||||||
environment.getPropertySources().addFirst(singleKeyPropertySource("two", "a", "apple"));
|
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);
|
Map<String, PropertySourceDescriptor> sources = propertySources(descriptor);
|
||||||
assertThat(sources.keySet()).containsExactly("two", "one");
|
assertThat(sources.keySet()).containsExactly("two", "one");
|
||||||
assertThat(sources.get("one").getProperties().get("a").getValue()).isEqualTo("alpha");
|
assertThat(sources.get("one").getProperties().get("a").getValue()).isEqualTo("alpha");
|
||||||
assertThat(sources.get("two").getProperties().get("a").getValue()).isEqualTo("apple");
|
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() {
|
private static ConfigurableEnvironment emptyEnvironment() {
|
||||||
StandardEnvironment environment = new StandardEnvironment();
|
StandardEnvironment environment = new StandardEnvironment();
|
||||||
environment.getPropertySources().remove(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME);
|
environment.getPropertySources().remove(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME);
|
||||||
@@ -418,7 +358,7 @@ class EnvironmentEndpointTests {
|
|||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
EnvironmentEndpoint environmentEndpoint(Environment environment) {
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -16,11 +16,13 @@
|
|||||||
|
|
||||||
package org.springframework.boot.actuate.env;
|
package org.springframework.boot.actuate.env;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
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.actuate.endpoint.web.test.WebEndpointTest;
|
||||||
import org.springframework.boot.test.util.TestPropertyValues;
|
import org.springframework.boot.test.util.TestPropertyValues;
|
||||||
import org.springframework.context.ConfigurableApplicationContext;
|
import org.springframework.context.ConfigurableApplicationContext;
|
||||||
@@ -76,17 +78,6 @@ class EnvironmentEndpointWebIntegrationTests {
|
|||||||
.isEqualTo("${my.bar}");
|
.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
|
@WebEndpointTest
|
||||||
void nestedPathForUnknownKeyShouldReturn404() {
|
void nestedPathForUnknownKeyShouldReturn404() {
|
||||||
this.client.get().uri("/actuator/env/this.does.not.exist").exchange().expectStatus().isNotFound();
|
this.client.get().uri("/actuator/env/this.does.not.exist").exchange().expectStatus().isNotFound();
|
||||||
@@ -103,16 +94,6 @@ class EnvironmentEndpointWebIntegrationTests {
|
|||||||
.isEqualTo("${my.bar}");
|
.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) {
|
private String forProperty(String source, String name) {
|
||||||
return "propertySources[?(@.name=='" + source + "')].properties.['" + name + "'].value";
|
return "propertySources[?(@.name=='" + source + "')].properties.['" + name + "'].value";
|
||||||
}
|
}
|
||||||
@@ -126,12 +107,12 @@ class EnvironmentEndpointWebIntegrationTests {
|
|||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
EnvironmentEndpoint endpoint(Environment environment) {
|
EnvironmentEndpoint endpoint(Environment environment) {
|
||||||
return new EnvironmentEndpoint(environment);
|
return new EnvironmentEndpoint(environment, Collections.emptyList(), Show.ALWAYS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
EnvironmentEndpointWebExtension environmentEndpointWebExtension(EnvironmentEndpoint endpoint) {
|
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.impl.matchers.GroupMatcher;
|
||||||
import org.quartz.spi.OperableTrigger;
|
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.QuartzJobDetails;
|
||||||
import org.springframework.boot.actuate.quartz.QuartzEndpoint.QuartzJobGroupSummary;
|
import org.springframework.boot.actuate.quartz.QuartzEndpoint.QuartzJobGroupSummary;
|
||||||
import org.springframework.boot.actuate.quartz.QuartzEndpoint.QuartzJobSummary;
|
import org.springframework.boot.actuate.quartz.QuartzEndpoint.QuartzJobSummary;
|
||||||
@@ -107,7 +106,7 @@ class QuartzEndpointTests {
|
|||||||
|
|
||||||
QuartzEndpointTests() {
|
QuartzEndpointTests() {
|
||||||
this.scheduler = mock(Scheduler.class);
|
this.scheduler = mock(Scheduler.class);
|
||||||
this.endpoint = new QuartzEndpoint(this.scheduler);
|
this.endpoint = new QuartzEndpoint(this.scheduler, Collections.emptyList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -417,7 +416,7 @@ class QuartzEndpointTests {
|
|||||||
mockTriggers(trigger);
|
mockTriggers(trigger);
|
||||||
given(this.scheduler.getTriggerState(TriggerKey.triggerKey("3am-every-day", "samples")))
|
given(this.scheduler.getTriggerState(TriggerKey.triggerKey("3am-every-day", "samples")))
|
||||||
.willReturn(TriggerState.NORMAL);
|
.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"),
|
assertThat(triggerDetails).contains(entry("group", "samples"), entry("name", "3am-every-day"),
|
||||||
entry("description", "Sample description"), entry("type", "cron"), entry("state", TriggerState.NORMAL),
|
entry("description", "Sample description"), entry("type", "cron"), entry("state", TriggerState.NORMAL),
|
||||||
entry("priority", 3));
|
entry("priority", 3));
|
||||||
@@ -443,7 +442,7 @@ class QuartzEndpointTests {
|
|||||||
mockTriggers(trigger);
|
mockTriggers(trigger);
|
||||||
given(this.scheduler.getTriggerState(TriggerKey.triggerKey("every-hour", "samples")))
|
given(this.scheduler.getTriggerState(TriggerKey.triggerKey("every-hour", "samples")))
|
||||||
.willReturn(TriggerState.COMPLETE);
|
.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"),
|
assertThat(triggerDetails).contains(entry("group", "samples"), entry("name", "every-hour"),
|
||||||
entry("description", "Every hour"), entry("type", "simple"), entry("state", TriggerState.COMPLETE),
|
entry("description", "Every hour"), entry("type", "simple"), entry("state", TriggerState.COMPLETE),
|
||||||
entry("priority", 20));
|
entry("priority", 20));
|
||||||
@@ -470,7 +469,7 @@ class QuartzEndpointTests {
|
|||||||
mockTriggers(trigger);
|
mockTriggers(trigger);
|
||||||
given(this.scheduler.getTriggerState(TriggerKey.triggerKey("every-hour-mon-wed", "samples")))
|
given(this.scheduler.getTriggerState(TriggerKey.triggerKey("every-hour-mon-wed", "samples")))
|
||||||
.willReturn(TriggerState.NORMAL);
|
.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"),
|
assertThat(triggerDetails).contains(entry("group", "samples"), entry("name", "every-hour-mon-wed"),
|
||||||
entry("description", "Every working hour Mon Wed"), entry("type", "dailyTimeInterval"),
|
entry("description", "Every working hour Mon Wed"), entry("type", "dailyTimeInterval"),
|
||||||
entry("state", TriggerState.NORMAL), entry("priority", 4));
|
entry("state", TriggerState.NORMAL), entry("priority", 4));
|
||||||
@@ -499,7 +498,7 @@ class QuartzEndpointTests {
|
|||||||
mockTriggers(trigger);
|
mockTriggers(trigger);
|
||||||
given(this.scheduler.getTriggerState(TriggerKey.triggerKey("once-a-week", "samples")))
|
given(this.scheduler.getTriggerState(TriggerKey.triggerKey("once-a-week", "samples")))
|
||||||
.willReturn(TriggerState.BLOCKED);
|
.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"),
|
assertThat(triggerDetails).contains(entry("group", "samples"), entry("name", "once-a-week"),
|
||||||
entry("description", "Once a week"), entry("type", "calendarInterval"),
|
entry("description", "Once a week"), entry("type", "calendarInterval"),
|
||||||
entry("state", TriggerState.BLOCKED), entry("priority", 8));
|
entry("state", TriggerState.BLOCKED), entry("priority", 8));
|
||||||
@@ -524,7 +523,7 @@ class QuartzEndpointTests {
|
|||||||
mockTriggers(trigger);
|
mockTriggers(trigger);
|
||||||
given(this.scheduler.getTriggerState(TriggerKey.triggerKey("custom", "samples")))
|
given(this.scheduler.getTriggerState(TriggerKey.triggerKey("custom", "samples")))
|
||||||
.willReturn(TriggerState.ERROR);
|
.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"),
|
assertThat(triggerDetails).contains(entry("group", "samples"), entry("name", "custom"), entry("type", "custom"),
|
||||||
entry("state", TriggerState.ERROR), entry("priority", 9));
|
entry("state", TriggerState.ERROR), entry("priority", 9));
|
||||||
assertThat(triggerDetails).contains(entry("previousFireTime", previousFireTime),
|
assertThat(triggerDetails).contains(entry("previousFireTime", previousFireTime),
|
||||||
@@ -542,9 +541,22 @@ class QuartzEndpointTests {
|
|||||||
mockTriggers(trigger);
|
mockTriggers(trigger);
|
||||||
given(this.scheduler.getTriggerState(TriggerKey.triggerKey("3am-every-day", "samples")))
|
given(this.scheduler.getTriggerState(TriggerKey.triggerKey("3am-every-day", "samples")))
|
||||||
.willReturn(TriggerState.NORMAL);
|
.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"),
|
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}")
|
@ParameterizedTest(name = "unit {1}")
|
||||||
@@ -554,7 +566,7 @@ class QuartzEndpointTests {
|
|||||||
.withSchedule(CalendarIntervalScheduleBuilder.calendarIntervalSchedule().withInterval(amount, unit))
|
.withSchedule(CalendarIntervalScheduleBuilder.calendarIntervalSchedule().withInterval(amount, unit))
|
||||||
.build();
|
.build();
|
||||||
mockTriggers(trigger);
|
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())
|
assertThat(triggerDetails).extractingByKey("calendarInterval", nestedMap())
|
||||||
.contains(entry("interval", expectedDuration.toMillis()));
|
.contains(entry("interval", expectedDuration.toMillis()));
|
||||||
}
|
}
|
||||||
@@ -575,7 +587,7 @@ class QuartzEndpointTests {
|
|||||||
JobDetail job = JobBuilder.newJob(Job.class).withIdentity("hello", "samples").withDescription("A sample job")
|
JobDetail job = JobBuilder.newJob(Job.class).withIdentity("hello", "samples").withDescription("A sample job")
|
||||||
.storeDurably().requestRecovery(false).build();
|
.storeDurably().requestRecovery(false).build();
|
||||||
mockJobs(job);
|
mockJobs(job);
|
||||||
QuartzJobDetails jobDetails = this.endpoint.quartzJob("samples", "hello");
|
QuartzJobDetails jobDetails = this.endpoint.quartzJob("samples", "hello", true);
|
||||||
assertThat(jobDetails.getGroup()).isEqualTo("samples");
|
assertThat(jobDetails.getGroup()).isEqualTo("samples");
|
||||||
assertThat(jobDetails.getName()).isEqualTo("hello");
|
assertThat(jobDetails.getName()).isEqualTo("hello");
|
||||||
assertThat(jobDetails.getDescription()).isEqualTo("A sample job");
|
assertThat(jobDetails.getDescription()).isEqualTo("A sample job");
|
||||||
@@ -600,7 +612,7 @@ class QuartzEndpointTests {
|
|||||||
mockTriggers(trigger);
|
mockTriggers(trigger);
|
||||||
given(this.scheduler.getTriggersOfJob(JobKey.jobKey("hello", "samples")))
|
given(this.scheduler.getTriggersOfJob(JobKey.jobKey("hello", "samples")))
|
||||||
.willAnswer((invocation) -> Collections.singletonList(trigger));
|
.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);
|
assertThat(jobDetails.getTriggers()).hasSize(1);
|
||||||
Map<String, Object> triggerDetails = jobDetails.getTriggers().get(0);
|
Map<String, Object> triggerDetails = jobDetails.getTriggers().get(0);
|
||||||
assertThat(triggerDetails).containsOnly(entry("group", "samples"), entry("name", "3am-every-day"),
|
assertThat(triggerDetails).containsOnly(entry("group", "samples"), entry("name", "3am-every-day"),
|
||||||
@@ -622,7 +634,7 @@ class QuartzEndpointTests {
|
|||||||
mockTriggers(triggerOne, triggerTwo);
|
mockTriggers(triggerOne, triggerTwo);
|
||||||
given(this.scheduler.getTriggersOfJob(JobKey.jobKey("hello", "samples")))
|
given(this.scheduler.getTriggersOfJob(JobKey.jobKey("hello", "samples")))
|
||||||
.willAnswer((invocation) -> Arrays.asList(triggerOne, triggerTwo));
|
.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()).hasSize(2);
|
||||||
assertThat(jobDetails.getTriggers().get(0)).containsEntry("name", "two");
|
assertThat(jobDetails.getTriggers().get(0)).containsEntry("name", "two");
|
||||||
assertThat(jobDetails.getTriggers().get(1)).containsEntry("name", "one");
|
assertThat(jobDetails.getTriggers().get(1)).containsEntry("name", "one");
|
||||||
@@ -642,35 +654,30 @@ class QuartzEndpointTests {
|
|||||||
mockTriggers(triggerOne, triggerTwo);
|
mockTriggers(triggerOne, triggerTwo);
|
||||||
given(this.scheduler.getTriggersOfJob(JobKey.jobKey("hello", "samples")))
|
given(this.scheduler.getTriggersOfJob(JobKey.jobKey("hello", "samples")))
|
||||||
.willAnswer((invocation) -> Arrays.asList(triggerOne, triggerTwo));
|
.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()).hasSize(2);
|
||||||
assertThat(jobDetails.getTriggers().get(0)).containsEntry("name", "two");
|
assertThat(jobDetails.getTriggers().get(0)).containsEntry("name", "two");
|
||||||
assertThat(jobDetails.getTriggers().get(1)).containsEntry("name", "one");
|
assertThat(jobDetails.getTriggers().get(1)).containsEntry("name", "one");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void quartzJobWithSensitiveDataMap() throws SchedulerException {
|
void quartzJobWithDataMap() throws SchedulerException {
|
||||||
JobDetail job = JobBuilder.newJob(Job.class).withIdentity("hello", "samples").usingJobData("user", "user")
|
JobDetail job = JobBuilder.newJob(Job.class).withIdentity("hello", "samples").usingJobData("user", "user")
|
||||||
.usingJobData("password", "secret").usingJobData("url", "https://user:secret@example.com").build();
|
.usingJobData("password", "secret").usingJobData("url", "https://user:secret@example.com").build();
|
||||||
mockJobs(job);
|
mockJobs(job);
|
||||||
QuartzJobDetails jobDetails = this.endpoint.quartzJob("samples", "hello");
|
QuartzJobDetails jobDetails = this.endpoint.quartzJob("samples", "hello", true);
|
||||||
assertThat(jobDetails.getData()).containsOnly(entry("user", "user"), entry("password", "******"),
|
assertThat(jobDetails.getData()).containsOnly(entry("user", "user"), entry("password", "secret"),
|
||||||
entry("url", "https://user:******@example.com"));
|
entry("url", "https://user:secret@example.com"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void quartzJobWithSensitiveDataMapAndCustomSanitizer() throws SchedulerException {
|
void quartzJobWithDataMapAndShowUnsanitizedFalse() throws SchedulerException {
|
||||||
JobDetail job = JobBuilder.newJob(Job.class).withIdentity("hello", "samples").usingJobData("test", "value")
|
JobDetail job = JobBuilder.newJob(Job.class).withIdentity("hello", "samples").usingJobData("user", "user")
|
||||||
.usingJobData("secret", "value").build();
|
.usingJobData("password", "secret").usingJobData("url", "https://user:secret@example.com").build();
|
||||||
mockJobs(job);
|
mockJobs(job);
|
||||||
Sanitizer sanitizer = mock(Sanitizer.class);
|
QuartzJobDetails jobDetails = this.endpoint.quartzJob("samples", "hello", false);
|
||||||
given(sanitizer.sanitize("test", "value")).willReturn("value");
|
assertThat(jobDetails.getData()).containsOnly(entry("user", "******"), entry("password", "******"),
|
||||||
given(sanitizer.sanitize("secret", "value")).willReturn("----");
|
entry("url", "******"));
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void mockJobs(JobDetail... jobs) throws SchedulerException {
|
private void mockJobs(JobDetail... jobs) throws SchedulerException {
|
||||||
|
|||||||
@@ -16,13 +16,18 @@
|
|||||||
|
|
||||||
package org.springframework.boot.actuate.quartz;
|
package org.springframework.boot.actuate.quartz;
|
||||||
|
|
||||||
|
import java.security.Principal;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import org.springframework.aot.hint.MemberCategory;
|
import org.springframework.aot.hint.MemberCategory;
|
||||||
import org.springframework.aot.hint.RuntimeHints;
|
import org.springframework.aot.hint.RuntimeHints;
|
||||||
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
|
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.QuartzGroups;
|
||||||
import org.springframework.boot.actuate.quartz.QuartzEndpoint.QuartzJobDetails;
|
import org.springframework.boot.actuate.quartz.QuartzEndpoint.QuartzJobDetails;
|
||||||
import org.springframework.boot.actuate.quartz.QuartzEndpoint.QuartzJobGroupSummary;
|
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 org.springframework.boot.actuate.quartz.QuartzEndpointWebExtension.QuartzEndpointWebExtensionRuntimeHints;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
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}.
|
* Tests for {@link QuartzEndpointWebExtension}.
|
||||||
*
|
*
|
||||||
* @author Moritz Halbritter
|
* @author Moritz Halbritter
|
||||||
|
* @author Madhura Bhave
|
||||||
*/
|
*/
|
||||||
class QuartzEndpointWebExtensionTests {
|
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
|
@Test
|
||||||
void shouldRegisterHints() {
|
void shouldRegisterHints() {
|
||||||
RuntimeHints runtimeHints = new RuntimeHints();
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.TriggerKey;
|
||||||
import org.quartz.impl.matchers.GroupMatcher;
|
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.boot.actuate.endpoint.web.test.WebEndpointTest;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
@@ -175,12 +176,12 @@ class QuartzEndpointWebIntegrationTests {
|
|||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
QuartzEndpoint endpoint(Scheduler scheduler) {
|
QuartzEndpoint endpoint(Scheduler scheduler) {
|
||||||
return new QuartzEndpoint(scheduler);
|
return new QuartzEndpoint(scheduler, Collections.emptyList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
QuartzEndpointWebExtension quartzEndpointWebExtension(QuartzEndpoint endpoint) {
|
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 {
|
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]]
|
[[howto.actuator.sanitize-sensitive-values]]
|
||||||
=== 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 `+******+`).
|
Information returned by the `/env`, `/configprops` and `/quartz` endpoints can be somewhat sensitive.
|
||||||
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.
|
All values are sanitized by default (that is replaced by `+******+`).
|
||||||
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.
|
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`
|
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.
|
||||||
- `addresses`
|
By default, any authenticated user is authorized.
|
||||||
- `uri`
|
For JMX endpoints, all users are always authorized.
|
||||||
- `uris`
|
|
||||||
- `url`
|
|
||||||
- `urls`
|
|
||||||
|
|
||||||
The sensitive portion of the URI is identified using the format `<scheme>://<username>:<password>@<host>:<port>/`.
|
[source,yaml,indent=0,subs="verbatim",configprops,configblocks]
|
||||||
For example, for the property `myclient.uri=http://user1:password1@localhost:8081`, the resulting sanitized value is
|
----
|
||||||
`++http://user1:******@localhost:8081++`.
|
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
|
==== Customizing Sanitization
|
||||||
Sanitization can be customized in two different ways.
|
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.
|
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.
|
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.
|
This allows you to, for example, sanitize every value that comes from a particular property source.
|
||||||
|
|||||||
Reference in New Issue
Block a user