@ConfigurationProperties("management.endpoint.env")
public class EnvironmentEndpointProperties {
private String[] keysToSanitize;
public EnvironmentEndpointProperties() {
}
public String[] getKeysToSanitize() {
return this.keysToSanitize;
}
public void setKeysToSanitize(String[] keysToSanitize) {
this.keysToSanitize = keysToSanitize;
}
}
์คํ๋ง ๋ถํธ 2์์ ์ก์ถ์์ดํฐ ์๋ํฌ์ธํธ์ ๋ํด ๋ฏผ๊ฐํ ๋ฐ์ดํฐ๊ฐ ๋ ธ์ถ๋๋ ๊ฒ์ ๋ฐฉ์งํ๊ธฐ ์ํด์ keys-to-sanitize ์์ฑ์ ํตํด ํน์ ํค ํจํด์ ๋ํ ๋ฐ์ดํฐ๊ฐ ๋ง์คํน ๋๋๋ก ์ง์ํ์ต๋๋ค. ๊ทธ๋ฌ๋, ์คํ๋ง ๋ถํธ 3 ๋ถํฐ๋ ํค ๊ธฐ๋ฐ์ด ์๋ ์ธ์ฆ ๋ฐ ์ญํ (Role) ๊ธฐ๋ฐ์ Sanitize๋ฅผ ์ํํ๋ ๊ฒ์ผ๋ก ๋ณ๊ฒฝ๋์์ต๋๋ค. ์ด์ ๋ํ ์ ๋ณด๋ ๋ง์ด๊ทธ๋ ์ด์ ๊ฐ์ด๋์ Actuator Endpoints Sanitization๋ก ๊ธฐ์ฌ๋์ด ์์ผ๋ฉฐ EnvironmentEndpointProperties ๋ ์๋์ ๊ฐ์ด ๋ณ๊ฒฝ๋์์ต๋๋ค.
@ConfigurationProperties("management.endpoint.env")
public class EnvironmentEndpointProperties {
private Show showValues;
private final Set<String> roles;
public EnvironmentEndpointProperties() {
this.showValues = Show.NEVER;
this.roles = new HashSet();
}
public Show getShowValues() {
return this.showValues;
}
public void setShowValues(Show showValues) {
this.showValues = showValues;
}
public Set<String> getRoles() {
return this.roles;
}
}
management:
endpoints:
web:
exposure:
include:
- health
- env
endpoint:
env:
show-values: when_authorized
roles: ADMIN
Customizing Sanitization โ
์คํ๋ง ๋ถํธ 3 ์์ ๊ธฐ์กด์ ํค ํจํด ๊ธฐ๋ฐ์ Sanitize ๋ฅผ ์ ์ฉํ๊ณ ์ ํ๋ค๋ฉด ๊ณต์ ๋ฌธ์์ Customizing Sanitization์ ๋ฐ๋ผ SanitizingFunction ์ธํฐํ์ด์ค์ ๊ตฌํ์ฒด๋ฅผ ๋น์ผ๋ก ๋ฑ๋กํด์ผ ํฉ๋๋ค. ์คํ๋ง ๋ถํธ 2.7 ๋ฒ์ ์ Sanitizer ์ฝ๋๋ฅผ ์ฐธ๊ณ ํ์ฌ ๊ตฌํํ๋ฉด ๋ ๊ฒ ๊ฐ์ต๋๋ค.
@Component
public class ActuatorSanitizingFunction implements SanitizingFunction {
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 final List<Pattern> keysToSanitize = new ArrayList<>();
static {
DEFAULT_KEYS_TO_SANITIZE.addAll(URI_USERINFO_KEYS);
}
public ActuatorSanitizingFunction(
@Value("${management.endpoint.additionalKeysToSanitize:}") List<String> additionalKeysToSanitize) {
addKeysToSanitize(DEFAULT_KEYS_TO_SANITIZE);
addKeysToSanitize(URI_USERINFO_KEYS);
addKeysToSanitize(additionalKeysToSanitize);
}
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;
}
@Override
public SanitizableData apply(SanitizableData data) {
if (data.getValue() == null) {
return data;
}
for (Pattern pattern : keysToSanitize) {
if (pattern.matcher(data.getKey()).matches()) {
if (keyIsUriWithUserInfo(pattern)) {
return data.withValue(sanitizeUris(data.getValue().toString()));
}
return data.withValue(SanitizableData.SANITIZED_VALUE);
}
}
return data;
}
private void addKeysToSanitize(Collection<String> keysToSanitize) {
for (String key : keysToSanitize) {
this.keysToSanitize.add(getPattern(key));
}
}
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;
}
}
์์ ๊ฐ์ด SanitizingFunction ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ๊ฒ ๋๋ฉด ๊ด๋ฆฌ์(ADMIN)๋ก ์ธ์ฆ๋ ์ฌ์ฉ์๊ฐ /actuator/env ์๋ํฌ์ธํธ๋ฅผ ํธ์ถํด๋ ์๋์ ๊ฐ์ด ๋ฏผ๊ฐํ ์ ๋ณด๋ ๋ง์คํน๋์ด ์ฒ๋ฆฌ๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค. ์ด์ฒ๋ผ ์์คํ ์์ ์ฌ์ฉ๋๋ ๋ฏผ๊ฐํ ์ ๋ณด๋ ๋ณดํธ๋ ์ ์๋๋ก ๋ณด์ํฉ์๋ค.
{
"name": "Config resource 'class path resource [application.yml]' via location 'optional:classpath:'",
"properties": {
"management.endpoints.web.exposure.include[0]": {
"value": "health",
"origin": "class path resource [application.yml] - 6:13"
},
"management.endpoints.web.exposure.include[1]": {
"value": "env",
"origin": "class path resource [application.yml] - 7:13"
},
"management.endpoint.env.show-values": {
"value": "always",
"origin": "class path resource [application.yml] - 10:20"
},
"management.endpoint.env.roles": {
"value": "ADMIN",
"origin": "class path resource [application.yml] - 11:14"
},
"spring.datasource.password": {
"value": "******",
"origin": "class path resource [application.yml] - 14:15"
}
}
}