Skip to content
java
@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 ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

java
@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;
    }
}
yaml
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 ์ฝ”๋“œ๋ฅผ ์ฐธ๊ณ ํ•˜์—ฌ ๊ตฌํ˜„ํ•˜๋ฉด ๋  ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

java
@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 ์—”๋“œํฌ์ธํŠธ๋ฅผ ํ˜ธ์ถœํ•ด๋„ ์•„๋ž˜์™€ ๊ฐ™์ด ๋ฏผ๊ฐํ•œ ์ •๋ณด๋Š” ๋งˆ์Šคํ‚น๋˜์–ด ์ฒ˜๋ฆฌ๋œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด์ฒ˜๋Ÿผ ์‹œ์Šคํ…œ์—์„œ ์‚ฌ์šฉ๋˜๋Š” ๋ฏผ๊ฐํ•œ ์ •๋ณด๋Š” ๋ณดํ˜ธ๋  ์ˆ˜ ์žˆ๋„๋ก ๋ณด์™„ํ•ฉ์‹œ๋‹ค.

json
{
    "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"
        }
    }
}

์ฐธ๊ณ  ์ž๋ฃŒ โ€‹

Released under the MIT License.