Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 50 additions & 3 deletions server-config/src/main/java/com/networknt/server/ServerConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.slf4j.LoggerFactory;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

Expand Down Expand Up @@ -419,12 +420,58 @@
if (instance != null && instance.getMappedConfig() == Config.getInstance().getJsonMapConfig(configName)) {
return instance;
}
instance = new ServerConfig(configName);
ModuleRegistry.registerModule(CONFIG_NAME, ServerConfig.class.getName(), Config.getNoneDecryptedInstance().getJsonMapConfigNoCache(CONFIG_NAME), masks);
ValidatedConfig loaded = loadValidated(configName);
loaded.options().forEach((key, option) -> {
if (option.corrected() && option.originalValue() != null) {
logger.warn("Server option: {} set in server.yml is invalid, has been reset to default value.", key);
}
});
instance = loaded.config();
Map<String, Object> reportedConfig = validatedConfig(
Config.getNoneDecryptedInstance().getJsonMapConfigNoCache(CONFIG_NAME), loaded);
ModuleRegistry.registerModule(CONFIG_NAME, ServerConfig.class.getName(), reportedConfig, masks);
return instance;
}
}
return new ServerConfig(configName);
return loadValidated(configName).config();
}

private static ValidatedConfig loadValidated(String configName) {
ServerConfig config = new ServerConfig(configName);
return new ValidatedConfig(config, ServerOption.validateServerOptions(config.mappedConfig, config));
}

private record ValidatedConfig(ServerConfig config, Map<String, ServerOption.ValidatedOption> options) {}

/** Preserve raw key presence and values unless the same input was corrected by validation. */
private static Map<String, Object> validatedConfig(Map<String, Object> rawConfig, ValidatedConfig loaded) {
// Never fall back to mappedConfig: it can contain decrypted credentials.
if (rawConfig == null) {
if (loaded.config().mappedConfig != null) {
logger.warn("Non-decrypted server configuration is unavailable; server-info configuration will be empty.");
}
return null;

Check warning on line 453 in server-config/src/main/java/com/networknt/server/ServerConfig.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Return an empty map instead of null.

See more on https://sonarcloud.io/project/issues?id=networknt_light-4j&issues=AaGOVI4sFOqWrBDz8Qy_&open=AaGOVI4sFOqWrBDz8Qy_&pullRequest=2839
}
Map<String, Object> reportedConfig = new HashMap<>(rawConfig);
loaded.options().forEach((key, option) -> {
boolean rawPresent = rawConfig.containsKey(key);
if (option.present() && !rawPresent) {
logger.warn("Server option {} is absent from non-decrypted reporting configuration; retaining its absence.", key);
} else if (!option.present() && rawPresent) {
logger.warn("Server option {} is present only in non-decrypted reporting configuration; retaining the raw reporting value.", key);
} else if (rawPresent) {
ServerOption.Comparison comparison = option.compare(rawConfig.get(key));
if (comparison.decryptionFailureType() != null) {
logger.warn("Server option {} could not be decrypted for reporting comparison ({}); check the configured key and ciphertext. Retaining the raw reporting value.",
key, comparison.decryptionFailureType());
} else if (!comparison.matches()) {
logger.warn("Server option {} differs between validated and non-decrypted configuration; retaining the raw reporting value.", key);
} else if (option.corrected()) {
reportedConfig.put(key, option.effectiveValue());
}
}
});
return reportedConfig;
}

private void loadData() {
Expand Down
68 changes: 44 additions & 24 deletions server-config/src/main/java/com/networknt/server/ServerOption.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package com.networknt.server;

import com.networknt.config.Config;
import com.networknt.config.ConfigInjection;
import com.networknt.decrypt.Decryptor;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;

/**
* This enum class is used to set and validate server options.
Expand All @@ -18,42 +23,61 @@
* Note: To set these options, configuring them in server.yml
*/
public enum ServerOption {
IO_THREADS("ioThreads"),
WORKER_THREADS("workerThreads"),
BUFFER_SIZE("bufferSize"),
BACKLOG("backlog"),
SERVER_STRING("serverString"),
ALWAYS_SET_DATE("alwaysSetDate"),
MAX_TRANSFER_FILE_SIZE("maxTransferFileSize"),
ALLOW_UNESCAPED_CHARACTERS_IN_URL("allowUnescapedCharactersInUrl"),
SHUTDOWN_TIMEOUT("shutdownTimeout");
IO_THREADS("ioThreads", ServerConfig::getIoThreads),
WORKER_THREADS("workerThreads", ServerConfig::getWorkerThreads),
BUFFER_SIZE("bufferSize", ServerConfig::getBufferSize),
BACKLOG("backlog", ServerConfig::getBacklog),
SERVER_STRING("serverString", ServerConfig::getServerString),
ALWAYS_SET_DATE("alwaysSetDate", ServerConfig::isAlwaysSetDate),
MAX_TRANSFER_FILE_SIZE("maxTransferFileSize", ServerConfig::getMaxTransferFileSize),
ALLOW_UNESCAPED_CHARACTERS_IN_URL("allowUnescapedCharactersInUrl", ServerConfig::isAllowUnescapedCharactersInUrl),
SHUTDOWN_TIMEOUT("shutdownTimeout", ServerConfig::getShutdownTimeout);

private final String value;

ServerOption(String serverOption) {
private final Function<ServerConfig, Object> effectiveValue;

ServerOption(String serverOption, Function<ServerConfig, Object> effectiveValue) {
this.value = serverOption;
this.effectiveValue = effectiveValue;
}

public String value() {
return this.value;
}

/**
* @param mapConfig map config generated from server.yml
* @param serverConfig object config generated from server.yml
*/
protected static void serverOptionInit(Map<String, Object> mapConfig, ServerConfig serverConfig) {
/** Validate fields and capture the presence and input of every option for reporting comparisons. */
static Map<String, ValidatedOption> validateServerOptions(Map<String, Object> mapConfig, ServerConfig serverConfig) {
Map<String, ValidatedOption> options = new LinkedHashMap<>();
for (ServerOption serverOption : ServerOption.values()) {
if (mapConfig.containsKey(serverOption.value())) {
if (!setServerOption(serverOption, mapConfig.get(serverOption.value), serverConfig)) {
ServerConfig.logger.warn("Server option: " + serverOption.value() + " set in server.yml is invalid, has been reset to default value.");
boolean present = mapConfig != null && mapConfig.containsKey(serverOption.value());
Object value = present ? mapConfig.get(serverOption.value()) : null;
boolean valid = setServerOption(serverOption, value, serverConfig);
options.put(serverOption.value(), new ValidatedOption(present, value,
serverOption.effectiveValue.apply(serverConfig), present && (!valid || value == null)));
}
return options;
}

/** Capture scalar inputs without retaining validation state on ServerConfig. */
record ValidatedOption(boolean present, Object originalValue, Object effectiveValue, boolean corrected) {
Comparison compare(Object rawValue) {
if (rawValue instanceof String encrypted && encrypted.startsWith(Decryptor.CRYPT_PREFIX)) {
try {
// Compare the scalar only; reporting must retain its original ciphertext.
return new Comparison(Objects.equals(ConfigInjection.getDecryptor().decrypt(encrypted),
originalValue == null ? null : originalValue.toString()), null);
} catch (RuntimeException failure) {
// Only retain the type: messages and causes can contain configuration values.
return new Comparison(false, failure.getClass().getName());
}
} else {
setToDefaultServerOption(serverOption, serverConfig);
}
return new Comparison(Objects.equals(rawValue, originalValue), null);
}
}

record Comparison(boolean matches, String decryptionFailureType) {}

/**
* Method used to validate and set server options
*
Expand Down Expand Up @@ -128,8 +152,4 @@ private static boolean setServerOption(ServerOption serverOption, Object value,
default -> true;
};
}

private static void setToDefaultServerOption(ServerOption serverOption, ServerConfig serverConfig) {
setServerOption(serverOption, null, serverConfig);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ public void testNullEnv() {
public void testDefaultServerOptions() {
config.clear();
ServerConfig serverConfig = ServerConfig.load("server");
ServerOption.serverOptionInit(serverConfig.getMappedConfig(),serverConfig);
Assertions.assertEquals(1024*16, serverConfig.getBufferSize());
Assertions.assertEquals(Runtime.getRuntime().availableProcessors() * 2, serverConfig.getIoThreads());
Assertions.assertEquals(200, serverConfig.getWorkerThreads());
Expand All @@ -53,7 +52,6 @@ public void testDefaultServerOptions() {
public void testInvalidServerOptions() {
config.clear();
ServerConfig serverConfig = ServerConfig.load("server_invalid_option");
ServerOption.serverOptionInit(serverConfig.getMappedConfig(),serverConfig);
Assertions.assertEquals(1024*16, serverConfig.getBufferSize());
Assertions.assertEquals(Runtime.getRuntime().availableProcessors() * 2, serverConfig.getIoThreads());
Assertions.assertEquals(200, serverConfig.getWorkerThreads());
Expand All @@ -67,7 +65,6 @@ public void testInvalidServerOptions() {
public void testValidServerOptions() {
config.clear();
ServerConfig serverConfig = ServerConfig.load("server_valid_option");
ServerOption.serverOptionInit(serverConfig.getMappedConfig(),serverConfig);
Assertions.assertEquals(10000, serverConfig.getBufferSize());
Assertions.assertEquals(1, serverConfig.getIoThreads());
Assertions.assertEquals(100, serverConfig.getWorkerThreads());
Expand All @@ -81,7 +78,6 @@ public void testValidServerOptions() {
public void testMaxTransferFileSize() {
config.clear();
ServerConfig serverConfig = ServerConfig.load();
ServerOption.serverOptionInit(serverConfig.getMappedConfig(), serverConfig);
Assertions.assertEquals(1000000, serverConfig.getMaxTransferFileSize());
}
}
Loading
Loading