feature/run-office-module-without-booking-and-hosting #148
@ -0,0 +1,105 @@
|
||||
package net.hostsharing.hsadminng.config;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.actuate.endpoint.SanitizableData;
|
||||
import org.springframework.boot.actuate.endpoint.SanitizingFunction;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
// HOWTO: exclude sensitive values, like passwords and other secrets, from being show by actuator endpoints:
|
||||
// either use: add your custom keys to management.endpoint.additionalKeysToSanitize,
|
||||
// or, if you need more heuristics, amend this code down here.
|
||||
@Component
|
||||
public class ActuatorSanitizer implements SanitizingFunction {
|
||||
|
||||
private static final String[] REGEX_PARTS = {"*", "$", "^", "+"};
|
||||
|
||||
private static final Set<String> DEFAULT_KEYS_TO_SANITIZE = Set.of(
|
||||
"password", "secret", "token", ".*credentials.*", "vcap_services", "^vcap\\.services.*$", "sun.java.command", "^spring[._]application[._]json$"
|
||||
);
|
||||
|
||||
private static final Set<String> URI_USERINFO_KEYS = Set.of(
|
||||
"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<>();
|
||||
|
||||
public ActuatorSanitizer(@Value("${management.endpoint.additionalKeysToSanitize:}") final List<String> additionalKeysToSanitize) {
|
||||
addKeysToSanitize(DEFAULT_KEYS_TO_SANITIZE);
|
||||
addKeysToSanitize(URI_USERINFO_KEYS);
|
||||
addKeysToSanitize(additionalKeysToSanitize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SanitizableData apply(final SanitizableData data) {
|
||||
if (data.getValue() == null) {
|
||||
return data;
|
||||
}
|
||||
|
||||
for (final 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(final Collection<String> keysToSanitize) {
|
||||
for (final String key : keysToSanitize) {
|
||||
this.keysToSanitize.add(getPattern(key));
|
||||
}
|
||||
}
|
||||
|
||||
private Pattern getPattern(final String value) {
|
||||
if (isRegex(value)) {
|
||||
return Pattern.compile(value, Pattern.CASE_INSENSITIVE);
|
||||
}
|
||||
return Pattern.compile(".*" + value + "$", Pattern.CASE_INSENSITIVE);
|
||||
}
|
||||
|
||||
private boolean isRegex(final String value) {
|
||||
for (final String part : REGEX_PARTS) {
|
||||
if (value.contains(part)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean keyIsUriWithUserInfo(final Pattern pattern) {
|
||||
for (String uriKey : URI_USERINFO_KEYS) {
|
||||
if (pattern.matcher(uriKey).matches()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private Object sanitizeUris(final String value) {
|
||||
return Arrays.stream(value.split(",")).map(this::sanitizeUri).collect(Collectors.joining(","));
|
||||
}
|
||||
|
||||
private String sanitizeUri(final String value) {
|
||||
final var matcher = URI_USERINFO_PATTERN.matcher(value);
|
||||
final var password = matcher.matches() ? matcher.group(1) : null;
|
||||
if (password != null) {
|
||||
return StringUtils.replace(value, ":" + password + "@", ":" + SanitizableData.SANITIZED_VALUE + "@");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
@ -8,8 +8,23 @@ management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
# HOWTO: view _clickable_ Spring Actuator (Micrometer) Metrics endpoints: http://localhost:8081/actuator/metric-links
|
||||
include: info, health, metrics, metric-links, mappings, openapi, swaggerui
|
||||
# HOWTO: view _clickable_ Spring Actuator (Micrometer) Metrics endpoints:
|
||||
# http://localhost:8081/actuator/metric-links
|
||||
|
||||
# HOWTO: view all configured endpoints of the running application:
|
||||
# http://localhost:8081/actuator/mappings
|
||||
|
||||
# HOWTO: view the effective application configuration properties:
|
||||
# http://localhost:8081/actuator/configprops
|
||||
|
||||
include: info, health, metrics, metric-links, mappings, openapi, swaggerui, configprops, env
|
||||
endpoint:
|
||||
env:
|
||||
# TODO.spec: check this, maybe set to when_authorized?
|
||||
show-values: always
|
||||
configprops:
|
||||
# TODO.spec: check this, maybe set to when_authorized?
|
||||
show-values: always
|
||||
observations:
|
||||
annotations:
|
||||
enabled: true
|
||||
@ -18,7 +33,7 @@ spring:
|
||||
datasource:
|
||||
driver-class-name: org.postgresql.Driver
|
||||
password: password
|
||||
url: jdbc:postgresql://localhost:5432/postgres
|
||||
url: ${HSADMINNG_POSTGRES_JDBC_URL}
|
||||
username: postgres
|
||||
|
||||
sql:
|
||||
@ -30,13 +45,13 @@ spring:
|
||||
hibernate:
|
||||
dialect: net.hostsharing.hsadminng.config.PostgresCustomDialect
|
||||
|
||||
liquibase:
|
||||
contexts: dev,office
|
||||
|
||||
# keep this in sync with test/.../application.yml
|
||||
springdoc:
|
||||
use-management-port: true
|
||||
|
||||
liquibase:
|
||||
contexts: dev
|
||||
|
||||
hsadminng:
|
||||
postgres:
|
||||
leakproof:
|
||||
@ -51,3 +66,33 @@ metrics:
|
||||
server:
|
||||
requests: true
|
||||
|
||||
---
|
||||
|
||||
# the 'dev-all' profile automatically creates the PgSql users and runs only the office module
|
||||
spring:
|
||||
config:
|
||||
activate:
|
||||
on-profile: dev-all
|
||||
liquibase:
|
||||
contexts: dev,office,booking,hosting
|
||||
|
||||
---
|
||||
|
||||
# the 'dev-office' profile automatically creates the PgSql users and runs only the office module
|
||||
spring:
|
||||
config:
|
||||
activate:
|
||||
on-profile: dev-office
|
||||
liquibase:
|
||||
contexts: dev,office
|
||||
|
||||
---
|
||||
|
||||
# the 'prod-office' profile expects that the PgSql users are already created and runs only the office module
|
||||
spring:
|
||||
config:
|
||||
activate:
|
||||
on-profile: prod-office
|
||||
liquibase:
|
||||
contexts: office
|
||||
|
||||
|
@ -0,0 +1,95 @@
|
||||
package net.hostsharing.hsadminng.config;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.actuate.endpoint.SanitizableData;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class ActuatorSanitizerTest {
|
||||
|
||||
private ActuatorSanitizer actuatorSanitizer;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
// Initialize with additional keys for testing
|
||||
final var additionalKeys = List.of("customSecret", "^custom[._]regex.*$");
|
||||
actuatorSanitizer = new ActuatorSanitizer(additionalKeys);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSanitizesDefaultKeys() {
|
||||
final var data = createSanitizableData("password", "my-secret-password");
|
||||
final var sanitizedData = actuatorSanitizer.apply(data);
|
||||
|
||||
assertThat(sanitizedData.getValue()).isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSanitizesCustomKey() {
|
||||
final var data = createSanitizableData("customSecret", "my-custom-secret");
|
||||
final var sanitizedData = actuatorSanitizer.apply(data);
|
||||
|
||||
assertThat(sanitizedData.getValue()).isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSanitizesCustomRegexKey() {
|
||||
final var data = createSanitizableData("custom.regex.key", "my-custom-regex-value");
|
||||
final var sanitizedData = actuatorSanitizer.apply(data);
|
||||
|
||||
assertThat(sanitizedData.getValue()).isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSanitizesUriWithUserInfo() {
|
||||
final var data = createSanitizableData("uri", "http://user:password@host.com");
|
||||
final var sanitizedData = actuatorSanitizer.apply(data);
|
||||
|
||||
assertThat(sanitizedData.getValue()).isEqualTo("http://user:******@host.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDoesNotSanitizeIrrelevantKey() {
|
||||
final var data = createSanitizableData("irrelevantKey", "non-sensitive-value");
|
||||
final var sanitizedData = actuatorSanitizer.apply(data);
|
||||
|
||||
assertThat(sanitizedData.getValue()).isEqualTo("non-sensitive-value");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHandlesNullValue() {
|
||||
final var data = createSanitizableData("password", null);
|
||||
final var sanitizedData = actuatorSanitizer.apply(data);
|
||||
|
||||
assertThat(sanitizedData.getValue()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHandlesMultipleUris() {
|
||||
final var data = createSanitizableData(
|
||||
"uris",
|
||||
"http://user1:password1@host1.com,http://user2:geheim@host2.com,http://user2@host2.com");
|
||||
final var sanitizedData = actuatorSanitizer.apply(data);
|
||||
|
||||
assertThat(sanitizedData.getValue()).isEqualTo(
|
||||
"http://user1:******@host1.com,http://user2:******@host2.com,http://user2@host2.com");
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to create a SanitizableData instance for testing.
|
||||
*/
|
||||
private SanitizableData createSanitizableData(final String key, final String value) {
|
||||
final var dummyPropertySource = new PropertySource<>("testSource") {
|
||||
|
||||
@Override
|
||||
public Object getProperty(String name) {
|
||||
return null; // No real property resolution needed for this test
|
||||
}
|
||||
};
|
||||
return new SanitizableData(dummyPropertySource, key, value);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user