diff --git a/src/main/java/org/qortal/api/ApiService.java b/src/main/java/org/qortal/api/ApiService.java index 5baf2c5d..cafba4ae 100644 --- a/src/main/java/org/qortal/api/ApiService.java +++ b/src/main/java/org/qortal/api/ApiService.java @@ -14,6 +14,8 @@ import java.security.SecureRandom; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.rewrite.handler.RedirectPatternRule; import org.eclipse.jetty.rewrite.handler.RewriteHandler; @@ -50,6 +52,8 @@ import org.qortal.settings.Settings; public class ApiService { + private static final Logger LOGGER = LogManager.getLogger(ApiService.class); + private static ApiService instance; private final ResourceConfig config; @@ -203,6 +207,9 @@ public class ApiService { context.addServlet(TradeBotWebSocket.class, "/websockets/crosschain/tradebot"); context.addServlet(PresenceWebSocket.class, "/websockets/presence"); + // Warn about API security if needed + this.checkApiSecurity(); + // Start server this.server.start(); } catch (Exception e) { @@ -222,4 +229,23 @@ public class ApiService { this.server = null; } + private void checkApiSecurity() { + // Warn about API security if needed + boolean allConnectionsAllowed = false; + if (Settings.getInstance().isApiKeyDisabled()) { + for (String pattern : Settings.getInstance().getApiWhitelist()) { + if (pattern.startsWith("0.0.0.0/") || pattern.startsWith("::/") || pattern.endsWith("/0")) { + allConnectionsAllowed = true; + } + } + + if (allConnectionsAllowed) { + LOGGER.warn("Warning: API key validation is currently disabled, and the API whitelist " + + "is allowing all connections. This can be a security risk."); + LOGGER.warn("To fix, set the apiKeyDisabled setting to false, or allow only specific local " + + "IP addresses using the apiWhitelist setting."); + } + } + } + } diff --git a/src/main/java/org/qortal/api/Security.java b/src/main/java/org/qortal/api/Security.java index 448f951a..4e25b03b 100644 --- a/src/main/java/org/qortal/api/Security.java +++ b/src/main/java/org/qortal/api/Security.java @@ -12,6 +12,11 @@ public abstract class Security { public static final String API_KEY_HEADER = "X-API-KEY"; public static void checkApiCallAllowed(HttpServletRequest request) { + // If API key checking has been disabled, we will allow the request in all cases + boolean isApiKeyDisabled = Settings.getInstance().isApiKeyDisabled(); + if (isApiKeyDisabled) + return; + String expectedApiKey = Settings.getInstance().getApiKey(); String passedApiKey = request.getHeader(API_KEY_HEADER); diff --git a/src/main/java/org/qortal/settings/Settings.java b/src/main/java/org/qortal/settings/Settings.java index b8884c6c..6543c09b 100644 --- a/src/main/java/org/qortal/settings/Settings.java +++ b/src/main/java/org/qortal/settings/Settings.java @@ -68,6 +68,9 @@ public class Settings { }; private Boolean apiRestricted; private String apiKey = null; + /** Whether to disable API key or loopback address checking + * IMPORTANT: do not disable for shared nodes or low-security local networks */ + private boolean apiKeyDisabled = false; private boolean apiLoggingEnabled = false; private boolean apiDocumentationEnabled = false; // Both of these need to be set for API to use SSL @@ -356,6 +359,10 @@ public class Settings { return this.apiKey; } + public boolean isApiKeyDisabled() { + return this.apiKeyDisabled; + } + public boolean isApiLoggingEnabled() { return this.apiLoggingEnabled; }