Skip to content

Commit

Permalink
Support role-based sanitization for actuator endpoints
Browse files Browse the repository at this point in the history
Closes gh-32156
  • Loading branch information
mbhave committed Aug 24, 2022
1 parent ada2450 commit 47effdc
Show file tree
Hide file tree
Showing 43 changed files with 1,211 additions and 966 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@

package org.springframework.boot.actuate.autoconfigure.context.properties;

import java.util.stream.Collectors;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
import org.springframework.boot.actuate.autoconfigure.endpoint.expose.EndpointExposure;
Expand Down Expand Up @@ -50,26 +48,19 @@ public class ConfigurationPropertiesReportEndpointAutoConfiguration {
public ConfigurationPropertiesReportEndpoint configurationPropertiesReportEndpoint(
ConfigurationPropertiesReportEndpointProperties properties,
ObjectProvider<SanitizingFunction> sanitizingFunctions) {
ConfigurationPropertiesReportEndpoint endpoint = new ConfigurationPropertiesReportEndpoint(
sanitizingFunctions.orderedStream().collect(Collectors.toList()));
String[] keysToSanitize = properties.getKeysToSanitize();
if (keysToSanitize != null) {
endpoint.setKeysToSanitize(keysToSanitize);
}
String[] additionalKeysToSanitize = properties.getAdditionalKeysToSanitize();
if (additionalKeysToSanitize != null) {
endpoint.keysToSanitize(additionalKeysToSanitize);
}
return endpoint;
return new ConfigurationPropertiesReportEndpoint(sanitizingFunctions.orderedStream().toList(),
properties.getShowValues());
}

@Bean
@ConditionalOnMissingBean
@ConditionalOnBean(ConfigurationPropertiesReportEndpoint.class)
@ConditionalOnAvailableEndpoint(exposure = { EndpointExposure.WEB, EndpointExposure.CLOUD_FOUNDRY })
public ConfigurationPropertiesReportEndpointWebExtension configurationPropertiesReportEndpointWebExtension(
ConfigurationPropertiesReportEndpoint configurationPropertiesReportEndpoint) {
return new ConfigurationPropertiesReportEndpointWebExtension(configurationPropertiesReportEndpoint);
ConfigurationPropertiesReportEndpoint configurationPropertiesReportEndpoint,
ConfigurationPropertiesReportEndpointProperties properties) {
return new ConfigurationPropertiesReportEndpointWebExtension(configurationPropertiesReportEndpoint,
properties.getShowValues(), properties.getRoles());
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2012-2021 the original author or authors.
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -16,44 +16,44 @@

package org.springframework.boot.actuate.autoconfigure.context.properties;

import java.util.HashSet;
import java.util.Set;

import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint;
import org.springframework.boot.actuate.endpoint.Show;
import org.springframework.boot.context.properties.ConfigurationProperties;

/**
* Configuration properties for {@link ConfigurationPropertiesReportEndpoint}.
*
* @author Stephane Nicoll
* @author Madhura Bhave
* @since 2.0.0
*/
@ConfigurationProperties("management.endpoint.configprops")
public class ConfigurationPropertiesReportEndpointProperties {

/**
* Keys that should be sanitized. Keys can be simple strings that the property ends
* with or regular expressions.
* When to show unsanitized values.
*/
private String[] keysToSanitize;
private Show showValues = Show.NEVER;

/**
* Keys that should be sanitized in addition to those already configured. Keys can be
* simple strings that the property ends with or regular expressions.
* Roles used to determine whether a user is authorized to be shown unsanitized
* values. When empty, all authenticated users are authorized.
*/
private String[] additionalKeysToSanitize;

public String[] getKeysToSanitize() {
return this.keysToSanitize;
}
private Set<String> roles = new HashSet<>();

public void setKeysToSanitize(String[] keysToSanitize) {
this.keysToSanitize = keysToSanitize;
public Show getShowValues() {
return this.showValues;
}

public String[] getAdditionalKeysToSanitize() {
return this.additionalKeysToSanitize;
public void setShowValues(Show showValues) {
this.showValues = showValues;
}

public void setAdditionalKeysToSanitize(String[] additionalKeysToSanitize) {
this.additionalKeysToSanitize = additionalKeysToSanitize;
public Set<String> getRoles() {
return this.roles;
}

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

package org.springframework.boot.actuate.autoconfigure.env;

import java.util.stream.Collectors;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
import org.springframework.boot.actuate.autoconfigure.endpoint.expose.EndpointExposure;
Expand Down Expand Up @@ -48,25 +46,18 @@ public class EnvironmentEndpointAutoConfiguration {
@ConditionalOnMissingBean
public EnvironmentEndpoint environmentEndpoint(Environment environment, EnvironmentEndpointProperties properties,
ObjectProvider<SanitizingFunction> sanitizingFunctions) {
EnvironmentEndpoint endpoint = new EnvironmentEndpoint(environment,
sanitizingFunctions.orderedStream().collect(Collectors.toList()));
String[] keysToSanitize = properties.getKeysToSanitize();
if (keysToSanitize != null) {
endpoint.setKeysToSanitize(keysToSanitize);
}
String[] additionalKeysToSanitize = properties.getAdditionalKeysToSanitize();
if (additionalKeysToSanitize != null) {
endpoint.keysToSanitize(additionalKeysToSanitize);
}
return endpoint;
return new EnvironmentEndpoint(environment, sanitizingFunctions.orderedStream().toList(),
properties.getShowValues());
}

@Bean
@ConditionalOnMissingBean
@ConditionalOnBean(EnvironmentEndpoint.class)
@ConditionalOnAvailableEndpoint(exposure = { EndpointExposure.WEB, EndpointExposure.CLOUD_FOUNDRY })
public EnvironmentEndpointWebExtension environmentEndpointWebExtension(EnvironmentEndpoint environmentEndpoint) {
return new EnvironmentEndpointWebExtension(environmentEndpoint);
public EnvironmentEndpointWebExtension environmentEndpointWebExtension(EnvironmentEndpoint environmentEndpoint,
EnvironmentEndpointProperties properties) {
return new EnvironmentEndpointWebExtension(environmentEndpoint, properties.getShowValues(),
properties.getRoles());
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2012-2021 the original author or authors.
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -16,6 +16,10 @@

package org.springframework.boot.actuate.autoconfigure.env;

import java.util.HashSet;
import java.util.Set;

import org.springframework.boot.actuate.endpoint.Show;
import org.springframework.boot.actuate.env.EnvironmentEndpoint;
import org.springframework.boot.context.properties.ConfigurationProperties;

Expand All @@ -29,31 +33,26 @@
public class EnvironmentEndpointProperties {

/**
* Keys that should be sanitized. Keys can be simple strings that the property ends
* with or regular expressions.
* When to show unsanitized values.
*/
private String[] keysToSanitize;
private Show showValues = Show.NEVER;

/**
* Keys that should be sanitized in addition to those already configured. Keys can be
* simple strings that the property ends with or regular expressions.
* Roles used to determine whether a user is authorized to be shown unsanitized
* values. When empty, all authenticated users are authorized.
*/
private String[] additionalKeysToSanitize;

public String[] getKeysToSanitize() {
return this.keysToSanitize;
}
private Set<String> roles = new HashSet<>();

public void setKeysToSanitize(String[] keysToSanitize) {
this.keysToSanitize = keysToSanitize;
public Show getShowValues() {
return this.showValues;
}

public String[] getAdditionalKeysToSanitize() {
return this.additionalKeysToSanitize;
public void setShowValues(Show showValues) {
this.showValues = showValues;
}

public void setAdditionalKeysToSanitize(String[] additionalKeysToSanitize) {
this.additionalKeysToSanitize = additionalKeysToSanitize;
public Set<String> getRoles() {
return this.roles;
}

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

package org.springframework.boot.actuate.autoconfigure.health;

import java.security.Principal;
import java.util.Collection;
import java.util.function.Predicate;

import org.springframework.boot.actuate.autoconfigure.health.HealthProperties.Show;
import org.springframework.boot.actuate.endpoint.SecurityContext;
import org.springframework.boot.actuate.endpoint.Show;
import org.springframework.boot.actuate.health.AdditionalHealthEndpointPath;
import org.springframework.boot.actuate.health.HealthEndpointGroup;
import org.springframework.boot.actuate.health.HttpCodeStatusMapper;
import org.springframework.boot.actuate.health.StatusAggregator;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;

/**
* Auto-configured {@link HealthEndpointGroup} backed by {@link HealthProperties}.
Expand Down Expand Up @@ -83,54 +78,13 @@ public boolean isMember(String name) {

@Override
public boolean showComponents(SecurityContext securityContext) {
if (this.showComponents == null) {
return showDetails(securityContext);
}
return getShowResult(securityContext, this.showComponents);
Show show = (this.showComponents != null) ? this.showComponents : this.showDetails;
return show.isShown(securityContext, this.roles);
}

@Override
public boolean showDetails(SecurityContext securityContext) {
return getShowResult(securityContext, this.showDetails);
}

private boolean getShowResult(SecurityContext securityContext, Show show) {
return switch (show) {
case NEVER -> false;
case ALWAYS -> true;
case WHEN_AUTHORIZED -> isAuthorized(securityContext);
};
}

private boolean isAuthorized(SecurityContext securityContext) {
Principal principal = securityContext.getPrincipal();
if (principal == null) {
return false;
}
if (CollectionUtils.isEmpty(this.roles)) {
return true;
}
boolean checkAuthorities = isSpringSecurityAuthentication(principal);
for (String role : this.roles) {
if (securityContext.isUserInRole(role)) {
return true;
}
if (checkAuthorities) {
Authentication authentication = (Authentication) principal;
for (GrantedAuthority authority : authentication.getAuthorities()) {
String name = authority.getAuthority();
if (role.equals(name)) {
return true;
}
}
}
}
return false;
}

private boolean isSpringSecurityAuthentication(Principal principal) {
return ClassUtils.isPresent("org.springframework.security.core.Authentication", null)
&& (principal instanceof Authentication);
return this.showDetails.isShown(securityContext, this.roles);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils;
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointProperties.Group;
import org.springframework.boot.actuate.autoconfigure.health.HealthProperties.Show;
import org.springframework.boot.actuate.autoconfigure.health.HealthProperties.Status;
import org.springframework.boot.actuate.endpoint.Show;
import org.springframework.boot.actuate.health.AdditionalHealthEndpointPath;
import org.springframework.boot.actuate.health.HealthEndpointGroup;
import org.springframework.boot.actuate.health.HealthEndpointGroups;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.util.Map;
import java.util.Set;

import org.springframework.boot.actuate.endpoint.Show;
import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.context.properties.ConfigurationProperties;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import java.util.Map;
import java.util.Set;

import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.endpoint.Show;
import org.springframework.boot.context.properties.NestedConfigurationProperty;

/**
Expand Down Expand Up @@ -103,27 +103,4 @@ public Map<String, Integer> getHttpMapping() {

}

/**
* 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

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@

import org.quartz.Scheduler;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
import org.springframework.boot.actuate.autoconfigure.endpoint.expose.EndpointExposure;
import org.springframework.boot.actuate.endpoint.SanitizingFunction;
import org.springframework.boot.actuate.quartz.QuartzEndpoint;
import org.springframework.boot.actuate.quartz.QuartzEndpointWebExtension;
import org.springframework.boot.autoconfigure.AutoConfiguration;
Expand All @@ -28,6 +30,7 @@
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;

/**
Expand All @@ -40,21 +43,23 @@
@AutoConfiguration(after = QuartzAutoConfiguration.class)
@ConditionalOnClass(Scheduler.class)
@ConditionalOnAvailableEndpoint(endpoint = QuartzEndpoint.class)
@EnableConfigurationProperties(QuartzEndpointProperties.class)
public class QuartzEndpointAutoConfiguration {

@Bean
@ConditionalOnBean(Scheduler.class)
@ConditionalOnMissingBean
public QuartzEndpoint quartzEndpoint(Scheduler scheduler) {
return new QuartzEndpoint(scheduler);
public QuartzEndpoint quartzEndpoint(Scheduler scheduler, ObjectProvider<SanitizingFunction> sanitizingFunctions) {
return new QuartzEndpoint(scheduler, sanitizingFunctions.orderedStream().toList());
}

@Bean
@ConditionalOnBean(QuartzEndpoint.class)
@ConditionalOnMissingBean
@ConditionalOnAvailableEndpoint(exposure = { EndpointExposure.WEB, EndpointExposure.CLOUD_FOUNDRY })
public QuartzEndpointWebExtension quartzEndpointWebExtension(QuartzEndpoint endpoint) {
return new QuartzEndpointWebExtension(endpoint);
public QuartzEndpointWebExtension quartzEndpointWebExtension(QuartzEndpoint endpoint,
QuartzEndpointProperties properties) {
return new QuartzEndpointWebExtension(endpoint, properties.getShowValues(), properties.getRoles());
}

}
Loading

0 comments on commit 47effdc

Please sign in to comment.