From ac21a44af0f0814269ff1be5ab3c29d193fe467f Mon Sep 17 00:00:00 2001 From: Jochen Schalanda Date: Thu, 5 Oct 2017 09:55:40 +0200 Subject: [PATCH] Simplify HTTP configuration (#3840) * Extract HTTP-related settings into HttpConfiguration * Remove web_* configuration settings * WIP - Replace rest_* configuration settings * Make slash at the end of a web interface route optional. * Properly rewrite location when synchronizing range with browser query. * Use normalized URIs in Routes.qualifyUrls * Improve documentation for http_* settings [ci skip] * Add section to upgrade notes [ci skip] * Fix link in upgrade notes [ci skip] * Use correct default for `web_thread_pool_size` in UPGRADING.rst * Use GRAYLOG_DEFAULT_PORT constant instead of magic literal * Inline single line HttpConfiguration#getUriScheme(boolean) * Make HttpConfiguration#getDefaultHttpUri(String) private * Remove unnecessary "namePrefix" parameter from JerseyService#setUp() * Add support for IPv6 wildcard addresses in HttpConfiguration --- UPGRADING.rst | 77 ++- .../main/java/org/graylog2/Configuration.java | 33 -- .../org/graylog2/cluster/NodeService.java | 2 +- .../org/graylog2/cluster/NodeServiceImpl.java | 4 +- .../java/org/graylog2/commands/Server.java | 5 +- .../configuration/HttpConfiguration.java | 264 ++++++++++ .../graylog2/periodical/NodePingThread.java | 15 +- .../graylog2/plugin/BaseConfiguration.java | 295 ----------- .../main/java/org/graylog2/plugin/Tools.java | 35 +- .../java/org/graylog2/rest/RestTools.java | 52 +- .../filter/WebAppNotFoundResponseFilter.java | 10 +- .../rest/resources/HelloWorldResource.java | 17 +- .../shared/initializers/JerseyService.java | 132 ++--- .../shared/rest/resources/RestResource.java | 9 +- .../DocumentationBrowserResource.java | 19 +- .../documentation/DocumentationResource.java | 14 +- .../org/graylog2/web/IndexHtmlGenerator.java | 53 +- .../web/resources/AppConfigResource.java | 13 +- .../resources/WebInterfaceAssetsResource.java | 17 +- .../resources/swagger/index.html.template | 43 +- .../src/main/resources/swagger/swagger-ui.js | 2 +- .../web-interface/index.html.template | 9 +- .../java/org/graylog2/ConfigurationTest.java | 130 ----- .../configuration/HttpConfigurationTest.java | 407 +++++++++++++++ .../plugin/BaseConfigurationTest.java | 466 ------------------ .../java/org/graylog2/plugin/ToolsTest.java | 13 + .../java/org/graylog2/rest/RestToolsTest.java | 41 +- .../WebAppNotFoundResponseFilterTest.java | 18 +- .../resources/HelloWorldResourceTest.java | 24 +- .../src/components/sources/SourceOverview.jsx | 2 +- graylog2-web-interface/src/routing/Routes.jsx | 10 +- misc/graylog.conf | 147 +++--- 32 files changed, 1084 insertions(+), 1294 deletions(-) create mode 100644 graylog2-server/src/main/java/org/graylog2/configuration/HttpConfiguration.java create mode 100644 graylog2-server/src/test/java/org/graylog2/configuration/HttpConfigurationTest.java delete mode 100644 graylog2-server/src/test/java/org/graylog2/plugin/BaseConfigurationTest.java diff --git a/UPGRADING.rst b/UPGRADING.rst index 598d9fa9400a..54ae25dc4323 100644 --- a/UPGRADING.rst +++ b/UPGRADING.rst @@ -1,9 +1,82 @@ ************************** -Upgrading to Graylog 2.4.x +Upgrading to Graylog 3.0.x ************************** -.. _upgrade-from-23-to-24: +.. _upgrade-from-24-to-30: This file only contains the upgrade note for the upcoming release. Please see `our documentation `_ for the complete upgrade notes. + +Simplified HTTP interface configuration +======================================= + +Graylog used to have a lot of different settings regarding the various HTTP interfaces it provides, namely the Graylog REST API and the Graylog web interface. + +This mostly originates from the fact that Graylog used to consist of two components before Graylog 2.0.0, a server component and a separate web interface. + +The changes in this release finally merge the HTTP listeners for the Graylog REST API and web interface into a single HTTP listener, which should make the initial configuration of Graylog simpler and reduce errors caused by conflicting settings. + +The path of the Graylog REST API is now hard-coded to ``/api``, so if you're still using the legacy URI on port 12900/tcp or have been using a custom path (via the ``rest_listen_uri`` or ``rest_transport_uri`` settings), you'll have to update the URI used to access the Graylog REST API. + +For a more detailed description of the new HTTP settings, please consult the annotated `Graylog configuration file `__. + + +Overview of deprecated Graylog REST API settings: + ++----------------------------------+----------------------------------+--------------------------------+ +| Deprecated Setting | New Setting | Default | ++==================================+==================================+================================+ +| ``rest_listen_uri`` | ``http_bind_address`` | ``127.0.0.1:9000`` | ++----------------------------------+----------------------------------+--------------------------------+ +| ``rest_transport_uri`` | ``http_publish_uri`` | ``http://$http_bind_address/`` | ++----------------------------------+----------------------------------+--------------------------------+ +| ``web_endpoint_uri`` | ``http_external_uri`` | ``$http_publish_uri`` | ++----------------------------------+----------------------------------+--------------------------------+ +| ``rest_enable_cors`` | ``http_enable_cors`` | ``true`` | ++----------------------------------+----------------------------------+--------------------------------+ +| ``rest_enable_gzip`` | ``http_enable_gzip`` | ``true`` | ++----------------------------------+----------------------------------+--------------------------------+ +| ``rest_max_header_size`` | ``http_max_header_size`` | ``8192`` | ++----------------------------------+----------------------------------+--------------------------------+ +| ``rest_max_initial_line_length`` | ``http_max_initial_line_length`` | ``4096`` | ++----------------------------------+----------------------------------+--------------------------------+ +| ``rest_thread_pool_size`` | ``http_thread_pool_size`` | ``16`` | ++----------------------------------+----------------------------------+--------------------------------+ +| ``rest_enable_tls`` | ``http_enable_tls`` | ``false`` | ++----------------------------------+----------------------------------+--------------------------------+ +| ``rest_tls_cert_file`` | ``http_tls_cert_file`` | Empty | ++----------------------------------+----------------------------------+--------------------------------+ +| ``rest_tls_key_file`` | ``http_tls_key_file`` | Empty | ++----------------------------------+----------------------------------+--------------------------------+ +| ``rest_tls_key_password`` | ``http_tls_key_password`` | Empty | ++----------------------------------+----------------------------------+--------------------------------+ + + +Overview of deprecated Graylog web interface settings: + ++---------------------------------+----------------------------------+--------------------+ +| Deprecated Setting | New Setting | Default | ++=================================+==================================+====================+ +| ``web_enable`` | None | | ++---------------------------------+----------------------------------+--------------------+ +| ``web_listen_uri`` | ``http_bind_address`` | ``127.0.0.1:9000`` | ++---------------------------------+----------------------------------+--------------------+ +| ``web_enable_cors`` | ``http_enable_cors`` | ``true`` | ++---------------------------------+----------------------------------+--------------------+ +| ``web_enable_gzip`` | ``http_enable_gzip`` | ``true`` | ++---------------------------------+----------------------------------+--------------------+ +| ``web_max_header_size`` | ``http_max_header_size`` | ``8192`` | ++---------------------------------+----------------------------------+--------------------+ +| ``web_max_initial_line_length`` | ``http_max_initial_line_length`` | ``4096`` | ++---------------------------------+----------------------------------+--------------------+ +| ``web_thread_pool_size`` | ``http_thread_pool_size`` | ``16`` | ++---------------------------------+----------------------------------+--------------------+ +| ``web_enable_tls`` | ``http_enable_tls`` | ``false`` | ++---------------------------------+----------------------------------+--------------------+ +| ``web_tls_cert_file`` | ``http_tls_cert_file`` | Empty | ++---------------------------------+----------------------------------+--------------------+ +| ``web_tls_key_file`` | ``http_tls_key_file`` | Empty | ++---------------------------------+----------------------------------+--------------------+ +| ``web_tls_key_password`` | ``http_tls_key_password`` | Empty | ++---------------------------------+----------------------------------+--------------------+ diff --git a/graylog2-server/src/main/java/org/graylog2/Configuration.java b/graylog2-server/src/main/java/org/graylog2/Configuration.java index e67d05dd25c1..d2acfd282f95 100644 --- a/graylog2-server/src/main/java/org/graylog2/Configuration.java +++ b/graylog2-server/src/main/java/org/graylog2/Configuration.java @@ -26,20 +26,16 @@ import com.github.joschi.jadconfig.validators.PositiveIntegerValidator; import com.github.joschi.jadconfig.validators.PositiveLongValidator; import com.github.joschi.jadconfig.validators.StringNotBlankValidator; -import com.github.joschi.jadconfig.validators.URIAbsoluteValidator; import org.graylog2.plugin.BaseConfiguration; import org.graylog2.utilities.IPSubnetConverter; import org.graylog2.utilities.IpSubnet; import org.joda.time.DateTimeZone; -import java.net.URI; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collections; import java.util.Set; -import static org.graylog2.plugin.Tools.normalizeURI; - /** * Helper class to hold configuration of Graylog */ @@ -51,12 +47,6 @@ public class Configuration extends BaseConfiguration { @Parameter(value = "password_secret", required = true, validator = StringNotBlankValidator.class) private String passwordSecret; - @Parameter(value = "rest_listen_uri", required = true, validator = URIAbsoluteValidator.class) - private URI restListenUri = URI.create("http://127.0.0.1:" + GRAYLOG_DEFAULT_PORT + "/api/"); - - @Parameter(value = "web_listen_uri", required = true, validator = URIAbsoluteValidator.class) - private URI webListenUri = URI.create("http://127.0.0.1:" + GRAYLOG_DEFAULT_WEB_PORT + "/"); - @Parameter(value = "output_batch_size", required = true, validator = PositiveIntegerValidator.class) private int outputBatchSize = 500; @@ -204,16 +194,6 @@ public String getNodeIdFile() { return nodeIdFile; } - @Override - public URI getRestListenUri() { - return normalizeURI(restListenUri, getRestUriScheme(), GRAYLOG_DEFAULT_PORT, "/"); - } - - @Override - public URI getWebListenUri() { - return normalizeURI(webListenUri, getWebUriScheme(), GRAYLOG_DEFAULT_WEB_PORT, "/"); - } - public String getRootUsername() { return rootUsername; } @@ -326,17 +306,4 @@ public void validatePasswordSecret() throws ValidationException { throw new ValidationException("The minimum length for \"password_secret\" is 16 characters."); } } - - @ValidatorMethod - @SuppressWarnings("unused") - public void validateNetworkInterfaces() throws ValidationException { - final URI restListenUri = getRestListenUri(); - final URI webListenUri = getWebListenUri(); - - if (restListenUri.getPort() == webListenUri.getPort() && - !restListenUri.getHost().equals(webListenUri.getHost()) && - (WILDCARD_IP_ADDRESS.equals(restListenUri.getHost()) || WILDCARD_IP_ADDRESS.equals(webListenUri.getHost()))) { - throw new ValidationException("Wildcard IP addresses cannot be used if the Graylog REST API and web interface listen on the same port."); - } - } } diff --git a/graylog2-server/src/main/java/org/graylog2/cluster/NodeService.java b/graylog2-server/src/main/java/org/graylog2/cluster/NodeService.java index 46219a594448..383a228a92b5 100644 --- a/graylog2-server/src/main/java/org/graylog2/cluster/NodeService.java +++ b/graylog2-server/src/main/java/org/graylog2/cluster/NodeService.java @@ -23,7 +23,7 @@ import java.util.Map; public interface NodeService extends PersistedService { - String registerServer(String nodeId, boolean isMaster, URI restTransportUri, String hostname); + String registerServer(String nodeId, boolean isMaster, URI httpPublishUri, String hostname); Node byNodeId(String nodeId) throws NodeNotFoundException; diff --git a/graylog2-server/src/main/java/org/graylog2/cluster/NodeServiceImpl.java b/graylog2-server/src/main/java/org/graylog2/cluster/NodeServiceImpl.java index 1f2e8e9095fb..42ee837f15bb 100644 --- a/graylog2-server/src/main/java/org/graylog2/cluster/NodeServiceImpl.java +++ b/graylog2-server/src/main/java/org/graylog2/cluster/NodeServiceImpl.java @@ -42,13 +42,13 @@ public NodeServiceImpl(final MongoConnection mongoConnection, final Configuratio } @Override - public String registerServer(String nodeId, boolean isMaster, URI restTransportUri, String hostname) { + public String registerServer(String nodeId, boolean isMaster, URI httpPublishUri, String hostname) { Map fields = Maps.newHashMap(); fields.put("last_seen", Tools.getUTCTimestamp()); fields.put("node_id", nodeId); fields.put("type", Node.Type.SERVER.toString()); fields.put("is_master", isMaster); - fields.put("transport_address", restTransportUri.toString()); + fields.put("transport_address", httpPublishUri.toString()); fields.put("hostname", hostname); try { diff --git a/graylog2-server/src/main/java/org/graylog2/commands/Server.java b/graylog2-server/src/main/java/org/graylog2/commands/Server.java index 65e98a9e123e..577bfd76dd27 100644 --- a/graylog2-server/src/main/java/org/graylog2/commands/Server.java +++ b/graylog2-server/src/main/java/org/graylog2/commands/Server.java @@ -46,6 +46,7 @@ import org.graylog2.configuration.ElasticsearchClientConfiguration; import org.graylog2.configuration.ElasticsearchConfiguration; import org.graylog2.configuration.EmailConfiguration; +import org.graylog2.configuration.HttpConfiguration; import org.graylog2.configuration.MongoDbConfiguration; import org.graylog2.configuration.VersionCheckConfiguration; import org.graylog2.dashboards.DashboardBindings; @@ -85,6 +86,7 @@ public class Server extends ServerBootstrap { private static final Logger LOG = LoggerFactory.getLogger(Server.class); private static final Configuration configuration = new Configuration(); + private final HttpConfiguration httpConfiguration = new HttpConfiguration(); private final ElasticsearchConfiguration elasticsearchConfiguration = new ElasticsearchConfiguration(); private final ElasticsearchClientConfiguration elasticsearchClientConfiguration = new ElasticsearchClientConfiguration(); private final EmailConfiguration emailConfiguration = new EmailConfiguration(); @@ -137,6 +139,7 @@ protected List getCommandBindings() { @Override protected List getCommandConfigurationBeans() { return Arrays.asList(configuration, + httpConfiguration, elasticsearchConfiguration, elasticsearchClientConfiguration, emailConfiguration, @@ -153,7 +156,7 @@ protected void startNodeRegistration(Injector injector) { final ActivityWriter activityWriter = injector.getInstance(ActivityWriter.class); nodeService.registerServer(serverStatus.getNodeId().toString(), configuration.isMaster(), - configuration.getRestTransportUri(), + httpConfiguration.getHttpPublishUri(), Tools.getLocalCanonicalHostname()); serverStatus.setLocalMode(isLocal()); if (configuration.isMaster() && !nodeService.isOnlyMaster(serverStatus.getNodeId())) { diff --git a/graylog2-server/src/main/java/org/graylog2/configuration/HttpConfiguration.java b/graylog2-server/src/main/java/org/graylog2/configuration/HttpConfiguration.java new file mode 100644 index 000000000000..6fe163d663fc --- /dev/null +++ b/graylog2-server/src/main/java/org/graylog2/configuration/HttpConfiguration.java @@ -0,0 +1,264 @@ +/** + * This file is part of Graylog. + * + * Graylog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Graylog is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Graylog. If not, see . + */ +package org.graylog2.configuration; + +import com.github.joschi.jadconfig.Parameter; +import com.github.joschi.jadconfig.ParameterException; +import com.github.joschi.jadconfig.ValidationException; +import com.github.joschi.jadconfig.ValidatorMethod; +import com.github.joschi.jadconfig.validators.PositiveIntegerValidator; +import com.github.joschi.jadconfig.validators.URIAbsoluteValidator; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.net.HostAndPort; +import com.google.common.net.InetAddresses; +import org.graylog2.plugin.Tools; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.Nullable; +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.UnknownHostException; +import java.nio.file.Files; +import java.nio.file.Path; + +public class HttpConfiguration { + private static final Logger LOG = LoggerFactory.getLogger(HttpConfiguration.class); + + private static final int GRAYLOG_DEFAULT_PORT = 9000; + + public static final String OVERRIDE_HEADER = "X-Graylog-Server-URL"; + public static final String PATH_WEB = ""; + public static final String PATH_API = "api/"; + + @Parameter(value = "http_bind_address", required = true) + private HostAndPort httpBindAddress = HostAndPort.fromParts("127.0.0.1", GRAYLOG_DEFAULT_PORT); + + @Parameter(value = "http_publish_uri", validator = URIAbsoluteValidator.class) + private URI httpPublishUri; + + @Parameter(value = "http_enable_cors") + private boolean httpEnableCors = true; + + @Parameter(value = "http_enable_gzip") + private boolean httpEnableGzip = true; + + @Parameter(value = "http_max_header_size", required = true, validator = PositiveIntegerValidator.class) + private int httpMaxHeaderSize = 8192; + + @Parameter(value = "http_thread_pool_size", required = true, validator = PositiveIntegerValidator.class) + private int httpThreadPoolSize = 16; + + @Parameter(value = "http_selector_runners_count", required = true, validator = PositiveIntegerValidator.class) + private int httpSelectorRunnersCount = 1; + + @Parameter(value = "http_enable_tls") + private boolean httpEnableTls = false; + + @Parameter(value = "http_tls_cert_file") + private Path httpTlsCertFile; + + @Parameter(value = "http_tls_key_file") + private Path httpTlsKeyFile; + + @Parameter(value = "http_tls_key_password") + private String httpTlsKeyPassword; + + @Parameter(value = "http_external_uri") + private URI httpExternalUri; + + public HostAndPort getHttpBindAddress() { + return httpBindAddress + .requireBracketsForIPv6() + .withDefaultPort(GRAYLOG_DEFAULT_PORT); + } + + public String getUriScheme() { + return isHttpEnableTls() ? "https" : "http"; + } + + @Nullable + private InetAddress toInetAddress(String host) { + try { + return InetAddress.getByName(host); + } catch (UnknownHostException e) { + LOG.debug("Couldn't resolve \"{}\"", host, e); + return null; + } + } + + public URI getHttpPublishUri() { + if (httpPublishUri == null) { + final URI defaultHttpUri = getDefaultHttpUri(); + LOG.debug("No \"http_publish_uri\" set. Using default <{}>.", defaultHttpUri); + return defaultHttpUri; + } else { + final InetAddress inetAddress = toInetAddress(httpPublishUri.getHost()); + if (Tools.isWildcardInetAddress(inetAddress)) { + final URI defaultHttpUri = getDefaultHttpUri(httpPublishUri.getPath()); + LOG.warn("\"{}\" is not a valid setting for \"http_publish_uri\". Using default <{}>.", httpPublishUri, defaultHttpUri); + return defaultHttpUri; + } else { + return Tools.normalizeURI(httpPublishUri, httpPublishUri.getScheme(), GRAYLOG_DEFAULT_PORT, httpPublishUri.getPath()); + } + } + } + + @VisibleForTesting + URI getDefaultHttpUri() { + return getDefaultHttpUri("/"); + } + + private URI getDefaultHttpUri(String path) { + final HostAndPort bindAddress = getHttpBindAddress(); + + final URI publishUri; + final InetAddress inetAddress = toInetAddress(bindAddress.getHost()); + if (inetAddress != null && Tools.isWildcardInetAddress(inetAddress)) { + final InetAddress guessedAddress; + try { + guessedAddress = Tools.guessPrimaryNetworkAddress(inetAddress instanceof Inet4Address); + + if (guessedAddress.isLoopbackAddress()) { + LOG.debug("Using loopback address {}", guessedAddress); + } + } catch (Exception e) { + LOG.error("Could not guess primary network address for \"http_publish_uri\". Please configure it in your Graylog configuration.", e); + throw new ParameterException("No http_publish_uri.", e); + } + + try { + publishUri = new URI( + getUriScheme(), + null, + guessedAddress.getHostAddress(), + bindAddress.getPort(), + path, + null, + null + ); + } catch (URISyntaxException e) { + throw new RuntimeException("Invalid http_publish_uri.", e); + } + } else { + try { + publishUri = new URI( + getUriScheme(), + null, + getHttpBindAddress().getHost(), + getHttpBindAddress().getPort(), + path, + null, + null + ); + } catch (URISyntaxException e) { + throw new RuntimeException("Invalid http_publish_uri.", e); + } + } + + return publishUri; + } + + public boolean isHttpEnableCors() { + return httpEnableCors; + } + + public boolean isHttpEnableGzip() { + return httpEnableGzip; + } + + public int getHttpMaxHeaderSize() { + return httpMaxHeaderSize; + } + + public int getHttpThreadPoolSize() { + return httpThreadPoolSize; + } + + public int getHttpSelectorRunnersCount() { + return httpSelectorRunnersCount; + } + + public boolean isHttpEnableTls() { + return httpEnableTls; + } + + public Path getHttpTlsCertFile() { + return httpTlsCertFile; + } + + public Path getHttpTlsKeyFile() { + return httpTlsKeyFile; + } + + public String getHttpTlsKeyPassword() { + return httpTlsKeyPassword; + } + + public URI getHttpExternalUri() { + return httpExternalUri == null ? getHttpPublishUri() : httpExternalUri; + } + + @ValidatorMethod + @SuppressWarnings("unused") + public void validateHttpBindAddress() throws ValidationException { + try { + final String host = getHttpBindAddress().getHost(); + if (!InetAddresses.isInetAddress(host)) { + final InetAddress inetAddress = InetAddress.getByName(host); + } + } catch (IllegalArgumentException | UnknownHostException e) { + throw new ValidationException(e); + } + } + + @ValidatorMethod + @SuppressWarnings("unused") + public void validateHttpPublishUriPathEndsWithSlash() throws ValidationException { + if (!getHttpPublishUri().getPath().endsWith("/")) { + throw new ValidationException("\"http_publish_uri\" must end with a slash (\"/\")"); + } + } + + @ValidatorMethod + @SuppressWarnings("unused") + public void validateHttpExternalUriPathEndsWithSlash() throws ValidationException { + if (!getHttpExternalUri().getPath().endsWith("/")) { + throw new ValidationException("\"http_external_uri\" must end with a slash (\"/\")"); + } + } + + @ValidatorMethod + @SuppressWarnings("unused") + public void validateTlsConfig() throws ValidationException { + if (isHttpEnableTls()) { + if (!isRegularFileAndReadable(getHttpTlsKeyFile())) { + throw new ValidationException("Unreadable or missing HTTP private key: " + getHttpTlsKeyFile()); + } + + if (!isRegularFileAndReadable(getHttpTlsCertFile())) { + throw new ValidationException("Unreadable or missing HTTP X.509 certificate: " + getHttpTlsCertFile()); + } + } + } + + private boolean isRegularFileAndReadable(Path path) { + return path != null && Files.isRegularFile(path) && Files.isReadable(path); + } +} diff --git a/graylog2-server/src/main/java/org/graylog2/periodical/NodePingThread.java b/graylog2-server/src/main/java/org/graylog2/periodical/NodePingThread.java index 69c53ee7061c..2591e8f7eae0 100644 --- a/graylog2-server/src/main/java/org/graylog2/periodical/NodePingThread.java +++ b/graylog2-server/src/main/java/org/graylog2/periodical/NodePingThread.java @@ -16,10 +16,10 @@ */ package org.graylog2.periodical; -import org.graylog2.Configuration; import org.graylog2.cluster.Node; import org.graylog2.cluster.NodeNotFoundException; import org.graylog2.cluster.NodeService; +import org.graylog2.configuration.HttpConfiguration; import org.graylog2.notifications.Notification; import org.graylog2.notifications.NotificationImpl; import org.graylog2.notifications.NotificationService; @@ -33,28 +33,25 @@ import javax.inject.Inject; -/** - * @author Lennart Koopmann - */ public class NodePingThread extends Periodical { private static final Logger LOG = LoggerFactory.getLogger(NodePingThread.class); private final NodeService nodeService; private final NotificationService notificationService; private final ActivityWriter activityWriter; - private final Configuration configuration; + private final HttpConfiguration httpConfiguration; private final ServerStatus serverStatus; @Inject public NodePingThread(NodeService nodeService, NotificationService notificationService, ActivityWriter activityWriter, - Configuration configuration, + HttpConfiguration httpConfiguration, ServerStatus serverStatus) { this.nodeService = nodeService; this.notificationService = notificationService; this.activityWriter = activityWriter; - this.configuration = configuration; + this.httpConfiguration = httpConfiguration; this.serverStatus = serverStatus; } @@ -63,12 +60,12 @@ public void doRun() { final boolean isMaster = serverStatus.hasCapability(ServerStatus.Capability.MASTER); try { Node node = nodeService.byNodeId(serverStatus.getNodeId()); - nodeService.markAsAlive(node, isMaster, configuration.getRestTransportUri()); + nodeService.markAsAlive(node, isMaster, httpConfiguration.getHttpPublishUri().resolve(HttpConfiguration.PATH_API)); } catch (NodeNotFoundException e) { LOG.warn("Did not find meta info of this node. Re-registering."); nodeService.registerServer(serverStatus.getNodeId().toString(), isMaster, - configuration.getRestTransportUri(), + httpConfiguration.getHttpPublishUri().resolve(HttpConfiguration.PATH_API), Tools.getLocalCanonicalHostname()); } try { diff --git a/graylog2-server/src/main/java/org/graylog2/plugin/BaseConfiguration.java b/graylog2-server/src/main/java/org/graylog2/plugin/BaseConfiguration.java index 11c127f51d4e..58cbea238ffb 100644 --- a/graylog2-server/src/main/java/org/graylog2/plugin/BaseConfiguration.java +++ b/graylog2-server/src/main/java/org/graylog2/plugin/BaseConfiguration.java @@ -17,14 +17,10 @@ package org.graylog2.plugin; import com.github.joschi.jadconfig.Parameter; -import com.github.joschi.jadconfig.ValidationException; -import com.github.joschi.jadconfig.ValidatorMethod; import com.github.joschi.jadconfig.util.Duration; import com.github.joschi.jadconfig.validators.PositiveDurationValidator; import com.github.joschi.jadconfig.validators.PositiveIntegerValidator; import com.github.joschi.jadconfig.validators.StringNotBlankValidator; -import com.github.joschi.jadconfig.validators.URIAbsoluteValidator; -import com.google.common.annotations.VisibleForTesting; import com.lmax.disruptor.BlockingWaitStrategy; import com.lmax.disruptor.BusySpinWaitStrategy; import com.lmax.disruptor.SleepingWaitStrategy; @@ -33,27 +29,15 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.net.InetAddress; import java.net.URI; -import java.net.URISyntaxException; -import java.net.UnknownHostException; -import java.nio.file.Files; -import java.nio.file.Path; @SuppressWarnings("FieldMayBeFinal") public abstract class BaseConfiguration { private static final Logger LOG = LoggerFactory.getLogger(BaseConfiguration.class); - protected static final String WILDCARD_IP_ADDRESS = "0.0.0.0"; - - protected static final int GRAYLOG_DEFAULT_PORT = 9000; - protected static final int GRAYLOG_DEFAULT_WEB_PORT = 9000; @Parameter(value = "shutdown_timeout", validator = PositiveIntegerValidator.class) protected int shutdownTimeout = 30000; - @Parameter(value = "rest_transport_uri", validator = URIAbsoluteValidator.class) - private URI restTransportUri; - @Parameter(value = "processbuffer_processors", required = true, validator = PositiveIntegerValidator.class) private int processBufferProcessors = 5; @@ -69,33 +53,6 @@ public abstract class BaseConfiguration { @Parameter(value = "inputbuffer_wait_strategy", required = true) private String inputBufferWaitStrategy = "blocking"; - @Parameter(value = "rest_enable_cors") - private boolean restEnableCors = true; - - @Parameter(value = "rest_enable_gzip") - private boolean restEnableGzip = true; - - @Parameter(value = "rest_max_header_size", required = true, validator = PositiveIntegerValidator.class) - private int restMaxHeaderSize = 8192; - - @Parameter(value = "rest_thread_pool_size", required = true, validator = PositiveIntegerValidator.class) - private int restThreadPoolSize = 16; - - @Parameter(value = "rest_selector_runners_count", required = true, validator = PositiveIntegerValidator.class) - private int restSelectorRunnersCount = 1; - - @Parameter(value = "rest_enable_tls") - private boolean restEnableTls = false; - - @Parameter(value = "rest_tls_cert_file") - private Path restTlsCertFile; - - @Parameter(value = "rest_tls_key_file") - private Path restTlsKeyFile; - - @Parameter(value = "rest_tls_key_password") - private String restTlsKeyPassword; - @Parameter(value = "plugin_dir") private String pluginDir = "plugin"; @@ -132,109 +89,9 @@ public abstract class BaseConfiguration { @Parameter(value = "installation_source", validator = StringNotBlankValidator.class) private String installationSource = "unknown"; - @Parameter(value = "web_enable") - private boolean webEnable = true; - - @Parameter(value = "web_endpoint_uri") - private URI webEndpointUri; - - @Parameter(value = "web_enable_cors") - private boolean webEnableCors = false; - - @Parameter(value = "web_enable_gzip") - private boolean webEnableGzip = true; - - @Parameter(value = "web_max_header_size", required = true, validator = PositiveIntegerValidator.class) - private int webMaxHeaderSize = 8192; - - @Parameter(value = "web_enable_tls") - private boolean webEnableTls = false; - - @Parameter(value = "web_thread_pool_size", required = true, validator = PositiveIntegerValidator.class) - private int webThreadPoolSize = 16; - - @Parameter(value = "web_selector_runners_count", required = true, validator = PositiveIntegerValidator.class) - private int webSelectorRunnersCount = 1; - - @Parameter(value = "web_tls_cert_file") - private Path webTlsCertFile; - - @Parameter(value = "web_tls_key_file") - private Path webTlsKeyFile; - - @Parameter(value = "web_tls_key_password") - private String webTlsKeyPassword; - @Parameter(value = "proxied_requests_thread_pool_size", required = true, validator = PositiveIntegerValidator.class) private int proxiedRequestsThreadPoolSize = 32; - public String getRestUriScheme() { - return getUriScheme(isRestEnableTls()); - } - - public String getWebUriScheme() { - return getUriScheme(isWebEnableTls()); - } - - public String getUriScheme(boolean enableTls) { - return enableTls ? "https" : "http"; - } - - public URI getRestTransportUri() { - final URI defaultRestTransportUri = getDefaultRestTransportUri(); - if (restTransportUri == null) { - LOG.debug("No rest_transport_uri set. Using default [{}].", defaultRestTransportUri); - return defaultRestTransportUri; - } else if (WILDCARD_IP_ADDRESS.equals(restTransportUri.getHost())) { - LOG.warn("\"{}\" is not a valid setting for \"rest_transport_uri\". Using default [{}].", restTransportUri, defaultRestTransportUri); - return defaultRestTransportUri; - } else { - return Tools.normalizeURI(restTransportUri, restTransportUri.getScheme(), GRAYLOG_DEFAULT_PORT, "/"); - } - } - - public void setRestTransportUri(final URI restTransportUri) { - this.restTransportUri = restTransportUri; - } - - @VisibleForTesting - protected URI getDefaultRestTransportUri() { - final URI transportUri; - final URI listenUri = getRestListenUri(); - - if (WILDCARD_IP_ADDRESS.equals(listenUri.getHost())) { - final InetAddress guessedAddress; - try { - guessedAddress = Tools.guessPrimaryNetworkAddress(); - - if (guessedAddress.isLoopbackAddress()) { - LOG.debug("Using loopback address {}", guessedAddress); - } - } catch (Exception e) { - LOG.error("Could not guess primary network address for \"rest_transport_uri\". Please configure it in your Graylog configuration.", e); - throw new RuntimeException("No rest_transport_uri.", e); - } - - try { - transportUri = new URI( - listenUri.getScheme(), - listenUri.getUserInfo(), - guessedAddress.getHostAddress(), - listenUri.getPort(), - listenUri.getPath(), - listenUri.getQuery(), - listenUri.getFragment() - ); - } catch (URISyntaxException e) { - throw new RuntimeException("Invalid rest_transport_uri.", e); - } - } else { - transportUri = listenUri; - } - - return transportUri; - } - public int getProcessBufferProcessors() { return processBufferProcessors; } @@ -272,42 +129,6 @@ public WaitStrategy getInputBufferWaitStrategy() { return getWaitStrategy(inputBufferWaitStrategy, "inputbuffer_wait_strategy"); } - public boolean isRestEnableCors() { - return restEnableCors; - } - - public boolean isRestEnableGzip() { - return restEnableGzip; - } - - public int getRestMaxHeaderSize() { - return restMaxHeaderSize; - } - - public int getRestThreadPoolSize() { - return restThreadPoolSize; - } - - public int getRestSelectorRunnersCount() { - return restSelectorRunnersCount; - } - - public boolean isRestEnableTls() { - return restEnableTls; - } - - public Path getRestTlsCertFile() { - return restTlsCertFile; - } - - public Path getRestTlsKeyFile() { - return restTlsKeyFile; - } - - public String getRestTlsKeyPassword() { - return restTlsKeyPassword; - } - public String getPluginDir() { return pluginDir; } @@ -318,10 +139,6 @@ public int getAsyncEventbusProcessors() { public abstract String getNodeIdFile(); - public abstract URI getRestListenUri(); - - public abstract URI getWebListenUri(); - public boolean isMessageJournalEnabled() { return messageJournalEnabled; } @@ -369,116 +186,4 @@ public Duration getHttpReadTimeout() { public String getInstallationSource() { return installationSource; } - - public boolean isWebEnable() { - return webEnable; - } - - public boolean isRestAndWebOnSamePort() { - final URI restListenUri = getRestListenUri(); - final URI webListenUri = getWebListenUri(); - try { - final InetAddress restAddress = InetAddress.getByName(restListenUri.getHost()); - final InetAddress webAddress = InetAddress.getByName(webListenUri.getHost()); - return restListenUri.getPort() == webListenUri.getPort() && restAddress.equals(webAddress); - } catch (UnknownHostException e) { - throw new RuntimeException("Unable to resolve hostnames of rest/web listen uris: ", e); - } - } - - public boolean isWebEnableCors() { - return webEnableCors; - } - - public boolean isWebEnableGzip() { - return webEnableGzip; - } - - public int getWebMaxHeaderSize() { - return webMaxHeaderSize; - } - - public boolean isWebEnableTls() { - return webEnableTls; - } - - public int getWebThreadPoolSize() { - return webThreadPoolSize; - } - - public int getWebSelectorRunnersCount() { - return webSelectorRunnersCount; - } - - public Path getWebTlsCertFile() { - return webTlsCertFile; - } - - public Path getWebTlsKeyFile() { - return webTlsKeyFile; - } - - public String getWebTlsKeyPassword() { - return webTlsKeyPassword; - } - - public URI getWebEndpointUri() { - return webEndpointUri == null ? getRestTransportUri() : webEndpointUri; - } - - public String getWebPrefix() { - final String webPrefix = getWebListenUri().getPath(); - if (webPrefix.endsWith("/")) { - return webPrefix.substring(0, webPrefix.length() - 1); - } - return webPrefix; - } - - @ValidatorMethod - @SuppressWarnings("unused") - public void validateRestTlsConfig() throws ValidationException { - if(isRestEnableTls()) { - if(!isRegularFileAndReadable(getRestTlsKeyFile())) { - throw new ValidationException("Unreadable or missing REST API private key: " + getRestTlsKeyFile()); - } - - if(!isRegularFileAndReadable(getRestTlsCertFile())) { - throw new ValidationException("Unreadable or missing REST API X.509 certificate: " + getRestTlsCertFile()); - } - } - } - - @ValidatorMethod - @SuppressWarnings("unused") - public void validateWebTlsConfig() throws ValidationException { - if(isWebEnableTls() && !isRestAndWebOnSamePort()) { - if(!isRegularFileAndReadable(getWebTlsKeyFile())) { - throw new ValidationException("Unreadable or missing web interface private key: " + getWebTlsKeyFile()); - } - - if(!isRegularFileAndReadable(getWebTlsCertFile())) { - throw new ValidationException("Unreadable or missing web interface X.509 certificate: " + getWebTlsCertFile()); - } - } - } - - @ValidatorMethod - @SuppressWarnings("unused") - public void validateRestAndWebListenConfigConflict() throws ValidationException { - if (isRestAndWebOnSamePort() && getRestListenUri().getPath().equals(getWebListenUri().getPath())) { - throw new ValidationException("If REST and Web interface are served on the same host/port, the path must be different!"); - } - } - - @ValidatorMethod - @SuppressWarnings("unused") - public void validateWebAndRestHaveSameProtocolIfOnSamePort() throws ValidationException { - if (isRestAndWebOnSamePort() && !getWebListenUri().getScheme().equals(getRestListenUri().getScheme())) { - throw new ValidationException("If REST and Web interface are served on the same host/port, the protocols must be identical!"); - } - } - - private boolean isRegularFileAndReadable(Path path) { - return path != null && Files.isRegularFile(path) && Files.isReadable(path); - } } diff --git a/graylog2-server/src/main/java/org/graylog2/plugin/Tools.java b/graylog2-server/src/main/java/org/graylog2/plugin/Tools.java index e737b63ee901..b540ddaa15fa 100644 --- a/graylog2-server/src/main/java/org/graylog2/plugin/Tools.java +++ b/graylog2-server/src/main/java/org/graylog2/plugin/Tools.java @@ -36,6 +36,7 @@ import java.io.InputStream; import java.lang.management.ManagementFactory; import java.net.Inet4Address; +import java.net.Inet6Address; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; @@ -43,11 +44,13 @@ import java.net.URISyntaxException; import java.net.UnknownHostException; import java.nio.charset.StandardCharsets; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.Locale; import java.util.Map; +import java.util.Optional; import java.util.SortedSet; import java.util.UUID; import java.util.zip.GZIPInputStream; @@ -60,6 +63,8 @@ * Utility class for various tool/helper functions. */ public final class Tools { + private static final byte[] EMPTY_BYTE_ARRAY_4 = {0,0,0,0}; + private static final byte[] EMPTY_BYTE_ARRAY_16 = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; public static final String ES_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss.SSS"; public static final String ES_DATE_FORMAT_NO_MS = "yyyy-MM-dd HH:mm:ss"; @@ -443,14 +448,14 @@ public static Number getNumber(Object o, Number defaultValue) { } /** - * Try to get the primary {@link java.net.InetAddress} of the primary network interface with + * Try to get the primary {@link InetAddress} of the primary network interface with * fallback to the local loopback address (usually {@code 127.0.0.1} or {@code ::1}. * - * @return The primary {@link java.net.InetAddress} of the primary network interface + * @return The primary {@link InetAddress} of the primary network interface * or the loopback address as fallback. * @throws SocketException if the list of network interfaces couldn't be retrieved */ - public static InetAddress guessPrimaryNetworkAddress() throws SocketException { + public static InetAddress guessPrimaryNetworkAddress(boolean preferIPv4) throws SocketException { final Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); if (interfaces != null) { @@ -458,7 +463,10 @@ public static InetAddress guessPrimaryNetworkAddress() throws SocketException { if (!interf.isLoopback() && interf.isUp()) { // Interface is not loopback and up. Try to get the first address. for (InetAddress addr : Collections.list(interf.getInetAddresses())) { - if (addr instanceof Inet4Address) { + if (preferIPv4 && addr instanceof Inet4Address) { + return addr; + } + if (!preferIPv4 && addr instanceof Inet6Address) { return addr; } } @@ -469,6 +477,10 @@ public static InetAddress guessPrimaryNetworkAddress() throws SocketException { return InetAddress.getLoopbackAddress(); } + public static boolean isWildcardInetAddress(@Nullable InetAddress inetAddress) { + return inetAddress != null && (Arrays.equals(EMPTY_BYTE_ARRAY_4, inetAddress.getAddress()) || Arrays.equals(EMPTY_BYTE_ARRAY_16, inetAddress.getAddress())); + } + @Nullable public static URI getUriWithPort(@Nullable final URI uri, final int port) { if (uri == null) { @@ -572,15 +584,16 @@ public static URI uriWithTrailingSlash(@Nullable final URI uri) { @Nullable public static URI normalizeURI(@Nullable final URI uri, String scheme, int port, String path) { - return com.google.common.base.Optional.fromNullable(uri) - .transform(u -> getUriWithScheme(u, scheme)) - .transform(u -> getUriWithPort(u, port)) - .transform(u -> getUriWithDefaultPath(u, path)) - .transform(Tools::uriWithTrailingSlash) - .transform(URI::normalize) - .orNull(); + return Optional.ofNullable(uri) + .map(u -> getUriWithScheme(u, scheme)) + .map(u -> getUriWithPort(u, port)) + .map(u -> getUriWithDefaultPath(u, path)) + .map(Tools::uriWithTrailingSlash) + .map(URI::normalize) + .orElse(null); } + @Nullable public static T getKeyByValue(Map map, E value) { for (Map.Entry entry : map.entrySet()) { if (value.equals(entry.getValue())) { diff --git a/graylog2-server/src/main/java/org/graylog2/rest/RestTools.java b/graylog2-server/src/main/java/org/graylog2/rest/RestTools.java index 46e89da5b94e..664ccab0df01 100644 --- a/graylog2-server/src/main/java/org/graylog2/rest/RestTools.java +++ b/graylog2-server/src/main/java/org/graylog2/rest/RestTools.java @@ -19,6 +19,7 @@ import com.google.common.base.Strings; import org.glassfish.grizzly.http.server.Request; import org.glassfish.jersey.server.model.Resource; +import org.graylog2.configuration.HttpConfiguration; import org.graylog2.shared.security.ShiroPrincipal; import org.graylog2.shared.security.ShiroSecurityContext; import org.graylog2.utilities.IpSubnet; @@ -27,6 +28,7 @@ import javax.validation.constraints.NotNull; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.SecurityContext; import java.net.URI; import java.net.URISyntaxException; @@ -37,6 +39,7 @@ import java.util.Set; public class RestTools { + @Nullable public static String getUserNameFromRequest(ContainerRequestContext requestContext) { final SecurityContext securityContext = requestContext.getSecurityContext(); @@ -81,32 +84,35 @@ public static String getRemoteAddrFromRequest(Request request, Set tru return remoteAddr; } - public static String buildEndpointUri(@NotNull HttpHeaders httpHeaders, @NotNull URI defaultEndpointUri) { - Optional endpointUri = Optional.empty(); - final List headers = httpHeaders.getRequestHeader("X-Graylog-Server-URL"); + public static URI buildExternalUri(@NotNull MultivaluedMap httpHeaders, @NotNull URI defaultUri) { + Optional externalUri = Optional.empty(); + final List headers = httpHeaders.get(HttpConfiguration.OVERRIDE_HEADER); if (headers != null && !headers.isEmpty()) { - endpointUri = headers.stream().filter(s -> { - try { - if (Strings.isNullOrEmpty(s)) { - return false; - } - final URI uri = new URI(s); - if (!uri.isAbsolute()) { - return true; - } - switch (uri.getScheme()) { - case "http": - case "https": - return true; - } - return false; - } catch (URISyntaxException e) { - return false; - } - }).findFirst(); + externalUri = headers.stream() + .filter(s -> { + try { + if (Strings.isNullOrEmpty(s)) { + return false; + } + final URI uri = new URI(s); + if (!uri.isAbsolute()) { + return true; + } + switch (uri.getScheme()) { + case "http": + case "https": + return true; + } + return false; + } catch (URISyntaxException e) { + return false; + } + }) + .map(URI::create) + .findFirst(); } - return endpointUri.orElse(defaultEndpointUri.toString()); + return externalUri.orElse(defaultUri); } public static String getPathFromResource(Resource resource) { diff --git a/graylog2-server/src/main/java/org/graylog2/rest/filter/WebAppNotFoundResponseFilter.java b/graylog2-server/src/main/java/org/graylog2/rest/filter/WebAppNotFoundResponseFilter.java index 4041348313b7..bd503a0026e3 100644 --- a/graylog2-server/src/main/java/org/graylog2/rest/filter/WebAppNotFoundResponseFilter.java +++ b/graylog2-server/src/main/java/org/graylog2/rest/filter/WebAppNotFoundResponseFilter.java @@ -16,7 +16,7 @@ */ package org.graylog2.rest.filter; -import org.graylog2.Configuration; +import org.graylog2.configuration.HttpConfiguration; import org.graylog2.web.IndexHtmlGenerator; import javax.annotation.Priority; @@ -33,12 +33,10 @@ @Priority(Priorities.ENTITY_CODER) public class WebAppNotFoundResponseFilter implements ContainerResponseFilter { - private final String webAppPrefix; private final IndexHtmlGenerator indexHtmlGenerator; @Inject - public WebAppNotFoundResponseFilter(Configuration configuration, IndexHtmlGenerator indexHtmlGenerator) { - this.webAppPrefix = configuration.getWebListenUri().getPath(); + public WebAppNotFoundResponseFilter(IndexHtmlGenerator indexHtmlGenerator) { this.indexHtmlGenerator = indexHtmlGenerator; } @@ -54,8 +52,8 @@ public void filter(ContainerRequestContext requestContext, ContainerResponseCont if (isGetRequest && responseStatus == Response.Status.NOT_FOUND && acceptsHtml - && requestPath.startsWith(webAppPrefix)) { - final String entity = indexHtmlGenerator.get(); + && !requestPath.startsWith("/" + HttpConfiguration.PATH_API)) { + final String entity = indexHtmlGenerator.get(requestContext.getHeaders()); responseContext.setStatusInfo(Response.Status.OK); responseContext.setEntity(entity, new Annotation[0], MediaType.TEXT_HTML_TYPE); diff --git a/graylog2-server/src/main/java/org/graylog2/rest/resources/HelloWorldResource.java b/graylog2-server/src/main/java/org/graylog2/rest/resources/HelloWorldResource.java index eaccfd0c069d..f571d94cdc68 100644 --- a/graylog2-server/src/main/java/org/graylog2/rest/resources/HelloWorldResource.java +++ b/graylog2-server/src/main/java/org/graylog2/rest/resources/HelloWorldResource.java @@ -19,7 +19,7 @@ import com.codahale.metrics.annotation.Timed; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; -import org.graylog2.Configuration; +import org.graylog2.configuration.HttpConfiguration; import org.graylog2.plugin.Version; import org.graylog2.plugin.cluster.ClusterConfigService; import org.graylog2.plugin.cluster.ClusterId; @@ -42,15 +42,12 @@ public class HelloWorldResource extends RestResource { private final NodeId nodeId; private final ClusterConfigService clusterConfigService; - private final Configuration configuration; @Inject public HelloWorldResource(NodeId nodeId, - ClusterConfigService clusterConfigService, - Configuration configuration) { + ClusterConfigService clusterConfigService) { this.nodeId = requireNonNull(nodeId); this.clusterConfigService = requireNonNull(clusterConfigService); - this.configuration = configuration; } @GET @@ -72,16 +69,8 @@ public HelloWorldResponse helloWorld() { @ApiOperation(value = "Redirecting to web console if it runs on same port.") @Produces({MediaType.TEXT_HTML, MediaType.APPLICATION_XHTML_XML}) public Response redirectToWebConsole() { - if (configuration.isRestAndWebOnSamePort()) { - final URI target = URI.create(configuration.getWebPrefix()); - return Response - .temporaryRedirect(target) - .build(); - } - return Response - .ok(helloWorld()) - .type(MediaType.APPLICATION_JSON) + .temporaryRedirect(URI.create(HttpConfiguration.PATH_WEB)) .build(); } } diff --git a/graylog2-server/src/main/java/org/graylog2/shared/initializers/JerseyService.java b/graylog2-server/src/main/java/org/graylog2/shared/initializers/JerseyService.java index fe7a3fb28b7f..2531c3a38712 100644 --- a/graylog2-server/src/main/java/org/graylog2/shared/initializers/JerseyService.java +++ b/graylog2-server/src/main/java/org/graylog2/shared/initializers/JerseyService.java @@ -21,6 +21,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider; import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; +import com.google.common.net.HostAndPort; import com.google.common.util.concurrent.AbstractIdleService; import com.google.common.util.concurrent.ThreadFactoryBuilder; import org.glassfish.grizzly.http.CompressionConfig; @@ -33,9 +35,9 @@ import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.server.ServerProperties; import org.glassfish.jersey.server.model.Resource; -import org.graylog2.Configuration; import org.graylog2.audit.PluginAuditEventTypes; import org.graylog2.audit.jersey.AuditEventModelProcessor; +import org.graylog2.configuration.HttpConfiguration; import org.graylog2.jersey.PrefixAddingModelProcessor; import org.graylog2.plugin.rest.PluginRestResource; import org.graylog2.rest.filter.WebAppNotFoundResponseFilter; @@ -57,6 +59,7 @@ import javax.inject.Inject; import javax.inject.Named; +import javax.net.ssl.SSLContext; import javax.ws.rs.container.ContainerResponseFilter; import javax.ws.rs.container.DynamicFeature; import javax.ws.rs.ext.ContextResolver; @@ -69,10 +72,7 @@ import java.security.InvalidKeyException; import java.security.KeyStore; import java.security.cert.CertificateException; -import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -91,7 +91,7 @@ public class JerseyService extends AbstractIdleService { private static final Logger LOG = LoggerFactory.getLogger(JerseyService.class); private static final String RESOURCE_PACKAGE_WEB = "org.graylog2.web.resources"; - private final Configuration configuration; + private final HttpConfiguration configuration; private final Map>> pluginRestResources; private final String[] restControllerPackages; @@ -105,10 +105,9 @@ public class JerseyService extends AbstractIdleService { private final ErrorPageGenerator errorPageGenerator; private HttpServer apiHttpServer = null; - private HttpServer webHttpServer = null; @Inject - public JerseyService(final Configuration configuration, + public JerseyService(final HttpConfiguration configuration, Set> dynamicFeatures, Set> containerResponseFilters, Set> exceptionMappers, @@ -138,105 +137,59 @@ protected void startUp() throws Exception { // because the PooledMemoryManager which is default now uses 10% of the heap no matter what System.setProperty("org.glassfish.grizzly.DEFAULT_MEMORY_MANAGER", "org.glassfish.grizzly.memory.HeapMemoryManager"); startUpApi(); - if (configuration.isWebEnable() && !configuration.isRestAndWebOnSamePort()) { - startUpWeb(); - } - } - - private void startUpWeb() throws Exception { - final String[] resources = new String[]{RESOURCE_PACKAGE_WEB}; - - final SSLEngineConfigurator sslEngineConfigurator = configuration.isWebEnableTls() ? - buildSslEngineConfigurator( - configuration.getWebTlsCertFile(), - configuration.getWebTlsKeyFile(), - configuration.getWebTlsKeyPassword()) : null; - - final URI webListenUri = configuration.getWebListenUri(); - final URI listenUri = new URI( - webListenUri.getScheme(), - webListenUri.getUserInfo(), - webListenUri.getHost(), - webListenUri.getPort(), - null, - null, - null - ); - - webHttpServer = setUp("web", - listenUri, - sslEngineConfigurator, - configuration.getWebThreadPoolSize(), - configuration.getWebSelectorRunnersCount(), - configuration.getWebMaxHeaderSize(), - configuration.isWebEnableGzip(), - configuration.isWebEnableCors(), - Collections.emptySet(), - resources); - - webHttpServer.start(); - - LOG.info("Started Web Interface at <{}>", configuration.getWebListenUri()); } @Override protected void shutDown() throws Exception { - shutdownHttpServer(apiHttpServer, configuration.getRestListenUri()); - shutdownHttpServer(webHttpServer, configuration.getWebListenUri()); + shutdownHttpServer(apiHttpServer, configuration.getHttpBindAddress()); } - private void shutdownHttpServer(HttpServer httpServer, URI listenUri) { + private void shutdownHttpServer(HttpServer httpServer, HostAndPort bindAddress) { if (httpServer != null && httpServer.isStarted()) { - LOG.info("Shutting down HTTP listener at <{}>", listenUri); + LOG.info("Shutting down HTTP listener at <{}>", bindAddress); httpServer.shutdownNow(); } } private void startUpApi() throws Exception { - final boolean startWebInterface = configuration.isWebEnable() && configuration.isRestAndWebOnSamePort(); - final List resourcePackages = new ArrayList<>(Arrays.asList(restControllerPackages)); - - if (startWebInterface) { - resourcePackages.add(RESOURCE_PACKAGE_WEB); - } + final List resourcePackages = ImmutableList.builder() + .add(restControllerPackages) + .add(RESOURCE_PACKAGE_WEB) + .build(); final Set pluginResources = prefixPluginResources(PLUGIN_PREFIX, pluginRestResources); - final SSLEngineConfigurator sslEngineConfigurator = configuration.isRestEnableTls() ? + final SSLEngineConfigurator sslEngineConfigurator = configuration.isHttpEnableTls() ? buildSslEngineConfigurator( - configuration.getRestTlsCertFile(), - configuration.getRestTlsKeyFile(), - configuration.getRestTlsKeyPassword()) : null; + configuration.getHttpTlsCertFile(), + configuration.getHttpTlsKeyFile(), + configuration.getHttpTlsKeyPassword()) : null; - final URI restListenUri = configuration.getRestListenUri(); + final HostAndPort bindAddress = configuration.getHttpBindAddress(); final URI listenUri = new URI( - restListenUri.getScheme(), - restListenUri.getUserInfo(), - restListenUri.getHost(), - restListenUri.getPort(), + configuration.getUriScheme(), null, + bindAddress.getHost(), + bindAddress.getPort(), + "/", null, null ); - apiHttpServer = setUp("rest", + apiHttpServer = setUp( listenUri, sslEngineConfigurator, - configuration.getRestThreadPoolSize(), - configuration.getRestSelectorRunnersCount(), - configuration.getRestMaxHeaderSize(), - configuration.isRestEnableGzip(), - configuration.isRestEnableCors(), + configuration.getHttpThreadPoolSize(), + configuration.getHttpSelectorRunnersCount(), + configuration.getHttpMaxHeaderSize(), + configuration.isHttpEnableGzip(), + configuration.isHttpEnableCors(), pluginResources, resourcePackages.toArray(new String[0])); apiHttpServer.start(); - LOG.info("Started REST API at <{}>", configuration.getRestListenUri()); - - if (startWebInterface) { - LOG.info("Started Web Interface at <{}>", configuration.getWebListenUri()); - } + LOG.info("Started REST API at <{}>", configuration.getHttpBindAddress()); } private Set prefixPluginResources(String pluginPrefix, Map>> pluginResourceMap) { @@ -270,10 +223,10 @@ private ResourceConfig buildResourceConfig(final boolean enableCors, final String[] controllerPackages) { final Map packagePrefixes = new HashMap<>(); for (String resourcePackage : controllerPackages) { - packagePrefixes.put(resourcePackage, configuration.getRestListenUri().getPath()); + packagePrefixes.put(resourcePackage, HttpConfiguration.PATH_API); } - packagePrefixes.put(RESOURCE_PACKAGE_WEB, configuration.getWebListenUri().getPath()); - packagePrefixes.put("", configuration.getRestListenUri().getPath()); + packagePrefixes.put(RESOURCE_PACKAGE_WEB, HttpConfiguration.PATH_WEB); + packagePrefixes.put("", HttpConfiguration.PATH_API); final ResourceConfig rc = new ResourceConfig() .property(ServerProperties.BV_SEND_ERROR_IN_RESPONSE, true) @@ -299,6 +252,7 @@ public ObjectMapper getContext(Class type) { } }) .packages(true, controllerPackages) + .packages(true, RESOURCE_PACKAGE_WEB) .registerResources(additionalResources); exceptionMappers.forEach(rc::registerClasses); @@ -318,8 +272,7 @@ public ObjectMapper getContext(Class type) { return rc; } - private HttpServer setUp(String namePrefix, - URI listenUri, + private HttpServer setUp(URI listenUri, SSLEngineConfigurator sslEngineConfigurator, int threadPoolSize, int selectorRunnersCount, @@ -346,8 +299,8 @@ private HttpServer setUp(String namePrefix, listener.setMaxHttpHeaderSize(maxHeaderSize); final ExecutorService workerThreadPoolExecutor = instrumentedExecutor( - namePrefix + "-worker-executor", - namePrefix + "-worker-%d", + "http-worker-executor", + "http-worker-%d", threadPoolSize); listener.getTransport().setWorkerThreadPool(workerThreadPoolExecutor); @@ -377,17 +330,14 @@ private SSLEngineConfigurator buildSslEngineConfigurator(Path certFile, Path key throw new CertificateException("Unreadable or missing X.509 certificate: " + certFile); } - final SSLContextConfigurator sslContext = new SSLContextConfigurator(); + final SSLContextConfigurator sslContextConfigurator = new SSLContextConfigurator(); final char[] password = firstNonNull(keyPassword, "").toCharArray(); final KeyStore keyStore = PemKeyStore.buildKeyStore(certFile, keyFile, password); - sslContext.setKeyStorePass(password); - sslContext.setKeyStoreBytes(KeyStoreUtils.getBytes(keyStore, password)); - - if (!sslContext.validateConfiguration(true)) { - throw new IllegalStateException("Couldn't initialize SSL context for HTTP server"); - } + sslContextConfigurator.setKeyStorePass(password); + sslContextConfigurator.setKeyStoreBytes(KeyStoreUtils.getBytes(keyStore, password)); - return new SSLEngineConfigurator(sslContext.createSSLContext(false), false, false, false); + final SSLContext sslContext = sslContextConfigurator.createSSLContext(true); + return new SSLEngineConfigurator(sslContext, false, false, false); } private ExecutorService instrumentedExecutor(final String executorName, diff --git a/graylog2-server/src/main/java/org/graylog2/shared/rest/resources/RestResource.java b/graylog2-server/src/main/java/org/graylog2/shared/rest/resources/RestResource.java index 990915143f0c..c2cc131a55ae 100644 --- a/graylog2-server/src/main/java/org/graylog2/shared/rest/resources/RestResource.java +++ b/graylog2-server/src/main/java/org/graylog2/shared/rest/resources/RestResource.java @@ -22,6 +22,7 @@ import com.fasterxml.jackson.jaxrs.cfg.ObjectWriterInjector; import com.fasterxml.jackson.jaxrs.cfg.ObjectWriterModifier; import org.apache.shiro.subject.Subject; +import org.graylog2.configuration.HttpConfiguration; import org.graylog2.indexer.IndexSet; import org.graylog2.indexer.IndexSetRegistry; import org.graylog2.plugin.BaseConfiguration; @@ -54,7 +55,7 @@ public abstract class RestResource { protected UserService userService; @Inject - private BaseConfiguration configuration; + private HttpConfiguration configuration; @Context SecurityContext securityContext; @@ -148,9 +149,9 @@ protected User getCurrentUser() { } protected UriBuilder getUriBuilderToSelf() { - final URI restTransportUri = configuration.getRestTransportUri(); - if (restTransportUri != null) { - return UriBuilder.fromUri(restTransportUri); + final URI httpPublishUri = configuration.getHttpPublishUri(); + if (httpPublishUri != null) { + return UriBuilder.fromUri(httpPublishUri); } else { return uriInfo.getBaseUriBuilder(); } diff --git a/graylog2-server/src/main/java/org/graylog2/shared/rest/resources/documentation/DocumentationBrowserResource.java b/graylog2-server/src/main/java/org/graylog2/shared/rest/resources/documentation/DocumentationBrowserResource.java index b7143f634b65..2932f3e75565 100644 --- a/graylog2-server/src/main/java/org/graylog2/shared/rest/resources/documentation/DocumentationBrowserResource.java +++ b/graylog2-server/src/main/java/org/graylog2/shared/rest/resources/documentation/DocumentationBrowserResource.java @@ -19,7 +19,8 @@ import com.floreysoft.jmte.Engine; import com.google.common.collect.ImmutableMap; import com.google.common.io.Resources; -import org.graylog2.Configuration; +import org.graylog2.configuration.HttpConfiguration; +import org.graylog2.rest.RestTools; import org.graylog2.shared.rest.resources.RestResource; import javax.activation.MimetypesFileTypeMap; @@ -30,6 +31,7 @@ import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; @@ -43,21 +45,21 @@ @Path("/api-browser") public class DocumentationBrowserResource extends RestResource { private final MimetypesFileTypeMap mimeTypes; - private final String apiPrefix; + private final HttpConfiguration httpConfiguration; private final ClassLoader classLoader = ClassLoader.getSystemClassLoader(); private final Engine templateEngine; @Inject - public DocumentationBrowserResource(MimetypesFileTypeMap mimeTypes, Configuration configuration, Engine templateEngine) { + public DocumentationBrowserResource(MimetypesFileTypeMap mimeTypes, HttpConfiguration httpConfiguration, Engine templateEngine) { this.mimeTypes = requireNonNull(mimeTypes, "mimeTypes"); - this.apiPrefix = requireNonNull(configuration, "configuration").getRestListenUri().getPath(); + this.httpConfiguration = requireNonNull(httpConfiguration, "httpConfiguration"); this.templateEngine = requireNonNull(templateEngine, "templateEngine"); } @GET - public Response root() throws IOException { - final String index = index(); + public Response root(@Context HttpHeaders httpHeaders) throws IOException { + final String index = index(httpHeaders); return Response.ok(index, MediaType.TEXT_HTML_TYPE) .header(HttpHeaders.CONTENT_LENGTH, index.length()) .build(); @@ -66,10 +68,11 @@ public Response root() throws IOException { @GET @Produces(MediaType.TEXT_HTML) @Path("index.html") - public String index() throws IOException { + public String index(@Context HttpHeaders httpHeaders) throws IOException { final URL templateUrl = this.getClass().getResource("/swagger/index.html.template"); final String template = Resources.toString(templateUrl, StandardCharsets.UTF_8); - final Map model = ImmutableMap.of("apiPrefix", apiPrefix); + final Map model = ImmutableMap.of( + "baseUri", RestTools.buildExternalUri(httpHeaders.getRequestHeaders(), httpConfiguration.getHttpExternalUri()).resolve(HttpConfiguration.PATH_API).toString()); return templateEngine.transform(template, model); } diff --git a/graylog2-server/src/main/java/org/graylog2/shared/rest/resources/documentation/DocumentationResource.java b/graylog2-server/src/main/java/org/graylog2/shared/rest/resources/documentation/DocumentationResource.java index ced4d74c573b..aa41555d6e4b 100644 --- a/graylog2-server/src/main/java/org/graylog2/shared/rest/resources/documentation/DocumentationResource.java +++ b/graylog2-server/src/main/java/org/graylog2/shared/rest/resources/documentation/DocumentationResource.java @@ -22,7 +22,7 @@ import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; -import org.graylog2.plugin.BaseConfiguration; +import org.graylog2.configuration.HttpConfiguration; import org.graylog2.plugin.rest.PluginRestResource; import org.graylog2.rest.RestTools; import org.graylog2.shared.plugins.PluginRestResourceClasses; @@ -39,10 +39,12 @@ import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import java.net.URI; import java.util.HashMap; import java.util.Map; import java.util.Set; +import static java.util.Objects.requireNonNull; import static org.graylog2.shared.initializers.JerseyService.PLUGIN_PREFIX; @Api(value = "Documentation", description = "Documentation of this API in JSON format.") @@ -50,15 +52,15 @@ public class DocumentationResource extends RestResource { private final Generator generator; - private final BaseConfiguration configuration; + private final HttpConfiguration httpConfiguration; @Inject - public DocumentationResource(BaseConfiguration configuration, + public DocumentationResource(HttpConfiguration httpConfiguration, @Named("RestControllerPackages") String[] restControllerPackages, ObjectMapper objectMapper, PluginRestResourceClasses pluginRestResourceClasses) { - this.configuration = configuration; + this.httpConfiguration = requireNonNull(httpConfiguration, "httpConfiguration"); final ImmutableSet.Builder packageNames = ImmutableSet.builder() .add(restControllerPackages); @@ -93,8 +95,8 @@ public Response overview() { public Response route(@ApiParam(name = "route", value = "Route to fetch. For example /system", required = true) @PathParam("route") String route, @Context HttpHeaders httpHeaders) { - final String basePath = RestTools.buildEndpointUri(httpHeaders, configuration.getWebEndpointUri()); - return buildSuccessfulCORSResponse(generator.generateForRoute(route, basePath)); + final URI baseUri = RestTools.buildExternalUri(httpHeaders.getRequestHeaders(), httpConfiguration.getHttpExternalUri()).resolve(HttpConfiguration.PATH_API); + return buildSuccessfulCORSResponse(generator.generateForRoute(route, baseUri.toString())); } private Response buildSuccessfulCORSResponse(Map result) { diff --git a/graylog2-server/src/main/java/org/graylog2/web/IndexHtmlGenerator.java b/graylog2-server/src/main/java/org/graylog2/web/IndexHtmlGenerator.java index 8a0cd96907ab..8cc29728a8c5 100644 --- a/graylog2-server/src/main/java/org/graylog2/web/IndexHtmlGenerator.java +++ b/graylog2-server/src/main/java/org/graylog2/web/IndexHtmlGenerator.java @@ -19,37 +19,58 @@ import com.floreysoft.jmte.Engine; import com.google.common.collect.ImmutableMap; import com.google.common.io.Resources; -import org.graylog2.Configuration; +import org.graylog2.configuration.HttpConfiguration; +import org.graylog2.rest.RestTools; import javax.inject.Inject; import javax.inject.Singleton; +import javax.ws.rs.core.MultivaluedMap; import java.io.IOException; -import java.net.URL; import java.nio.charset.StandardCharsets; +import java.util.List; import java.util.Map; import static java.util.Objects.requireNonNull; @Singleton public class IndexHtmlGenerator { - private static final String title = "Graylog Web Interface"; - private final String content; + private final String template; + private final List cssFiles; + private final List sortedJsFiles; + private final Engine templateEngine; + private final HttpConfiguration httpConfiguration; @Inject - public IndexHtmlGenerator(PluginAssets pluginAssets, Configuration configuration, Engine templateEngine) throws IOException { - final URL templateUrl = this.getClass().getResource("/web-interface/index.html.template"); - final String template = Resources.toString(templateUrl, StandardCharsets.UTF_8); - final Map model = ImmutableMap.builder() - .put("title", title) - .put("cssFiles", pluginAssets.cssFiles()) - .put("jsFiles", pluginAssets.sortedJsFiles()) - .put("appPrefix", configuration.getWebPrefix()) - .build(); + public IndexHtmlGenerator(final PluginAssets pluginAssets, + final Engine templateEngine, + final HttpConfiguration httpConfiguration) throws IOException { + this( + Resources.toString(Resources.getResource("web-interface/index.html.template"), StandardCharsets.UTF_8), + pluginAssets.cssFiles(), + pluginAssets.sortedJsFiles(), + templateEngine, + httpConfiguration); + } - this.content = requireNonNull(templateEngine, "templateEngine").transform(template, model); + private IndexHtmlGenerator(final String template, + final List cssFiles, + final List sortedJsFiles, + final Engine templateEngine, + final HttpConfiguration httpConfiguration) throws IOException { + this.template = requireNonNull(template, "template"); + this.cssFiles = requireNonNull(cssFiles, "cssFiles"); + this.sortedJsFiles = requireNonNull(sortedJsFiles, "sortedJsFiles"); + this.templateEngine = requireNonNull(templateEngine, "templateEngine"); + this.httpConfiguration = requireNonNull(httpConfiguration, "httpConfiguration"); } - public String get() { - return this.content; + public String get(MultivaluedMap headers) { + final Map model = ImmutableMap.builder() + .put("title", "Graylog Web Interface") + .put("cssFiles", cssFiles) + .put("jsFiles", sortedJsFiles) + .put("baseUri", RestTools.buildExternalUri(headers, httpConfiguration.getHttpExternalUri())) + .build(); + return templateEngine.transform(template, model); } } diff --git a/graylog2-server/src/main/java/org/graylog2/web/resources/AppConfigResource.java b/graylog2-server/src/main/java/org/graylog2/web/resources/AppConfigResource.java index 29711d56612f..c5d2cdcb51bc 100644 --- a/graylog2-server/src/main/java/org/graylog2/web/resources/AppConfigResource.java +++ b/graylog2-server/src/main/java/org/graylog2/web/resources/AppConfigResource.java @@ -20,6 +20,7 @@ import com.google.common.collect.ImmutableMap; import com.google.common.io.Resources; import org.graylog2.Configuration; +import org.graylog2.configuration.HttpConfiguration; import org.graylog2.rest.MoreMediaTypes; import org.graylog2.rest.RestTools; @@ -30,6 +31,7 @@ import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import java.io.IOException; +import java.net.URI; import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.Map; @@ -39,11 +41,15 @@ @Path("/config.js") public class AppConfigResource { private final Configuration configuration; + private final HttpConfiguration httpConfiguration; private final Engine templateEngine; @Inject - public AppConfigResource(Configuration configuration, Engine templateEngine) { + public AppConfigResource(Configuration configuration, + HttpConfiguration httpConfiguration, + Engine templateEngine) { this.configuration = requireNonNull(configuration, "configuration"); + this.httpConfiguration = requireNonNull(httpConfiguration, "httpConfiguration"); this.templateEngine = requireNonNull(templateEngine, "templateEngine"); } @@ -58,10 +64,11 @@ public String get(@Context HttpHeaders headers) { throw new RuntimeException("Unable to read AppConfig template while generating web interface configuration: ", e); } + final URI baseUri = RestTools.buildExternalUri(headers.getRequestHeaders(), httpConfiguration.getHttpExternalUri()); final Map model = ImmutableMap.of( "rootTimeZone", configuration.getRootTimeZone(), - "serverUri", RestTools.buildEndpointUri(headers, configuration.getWebEndpointUri()), - "appPathPrefix", configuration.getWebPrefix()); + "serverUri", baseUri.resolve(HttpConfiguration.PATH_API), + "appPathPrefix", baseUri.getPath()); return templateEngine.transform(template, model); } } diff --git a/graylog2-server/src/main/java/org/graylog2/web/resources/WebInterfaceAssetsResource.java b/graylog2-server/src/main/java/org/graylog2/web/resources/WebInterfaceAssetsResource.java index 9e58d5a9901f..325e1c0f24a5 100644 --- a/graylog2-server/src/main/java/org/graylog2/web/resources/WebInterfaceAssetsResource.java +++ b/graylog2-server/src/main/java/org/graylog2/web/resources/WebInterfaceAssetsResource.java @@ -118,29 +118,30 @@ private Optional getPluginForName(String pluginName) { @Path("assets/{filename: .*}") @GET public Response get(@Context Request request, + @Context HttpHeaders headers, @PathParam("filename") String filename) { if (filename == null || filename.isEmpty() || "/".equals(filename) || "index.html".equals(filename)) { - return getDefaultResponse(); + return getDefaultResponse(headers); } try { final URL resourceUrl = getResourceUri(false, filename, this.getClass()); return getResponse(request, filename, resourceUrl, false); } catch (IOException | URISyntaxException e) { - return getDefaultResponse(); + return getDefaultResponse(headers); } } @GET @Path("index.html") - public Response getIndex() { - return getDefaultResponse(); + public Response getIndex(@Context HttpHeaders headers) { + return getDefaultResponse(headers); } @GET - public Response getIndex(@Context ContainerRequest request) { + public Response getIndex(@Context ContainerRequest request, @Context HttpHeaders headers) { final URI originalLocation = request.getRequestUri(); if (originalLocation.getPath().endsWith("/")) { - return get(request, originalLocation.getPath()); + return get(request, headers, originalLocation.getPath()); } final URI redirect = UriBuilder.fromPath(originalLocation.getPath() + "/").build(); return Response.temporaryRedirect(redirect).build(); @@ -210,9 +211,9 @@ private String pluginPrefixFilename(boolean fromPlugin, String filename) { } } - private Response getDefaultResponse() { + private Response getDefaultResponse(HttpHeaders headers) { return Response - .ok(this.indexHtmlGenerator.get()) + .ok(indexHtmlGenerator.get(headers.getRequestHeaders())) .header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_HTML) .header("X-UA-Compatible", "IE=edge") .build(); diff --git a/graylog2-server/src/main/resources/swagger/index.html.template b/graylog2-server/src/main/resources/swagger/index.html.template index 1b3cf7007c04..8012a02e47c6 100644 --- a/graylog2-server/src/main/resources/swagger/index.html.template +++ b/graylog2-server/src/main/resources/swagger/index.html.template @@ -2,31 +2,24 @@ Graylog REST API browser - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + ${foreach jsFiles jsFile} - + ${end} diff --git a/graylog2-server/src/test/java/org/graylog2/ConfigurationTest.java b/graylog2-server/src/test/java/org/graylog2/ConfigurationTest.java index 2b4e23f77cab..8bb0271b5eb9 100644 --- a/graylog2-server/src/test/java/org/graylog2/ConfigurationTest.java +++ b/graylog2-server/src/test/java/org/graylog2/ConfigurationTest.java @@ -48,88 +48,6 @@ public void setUp() throws Exception { validProperties.put("root_password_sha2", "8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918"); } - @Test - public void testRestListenUriIsRelativeURI() throws RepositoryException, ValidationException { - validProperties.put("rest_listen_uri", "/foo"); - - expectedException.expect(ValidationException.class); - expectedException.expectMessage("Parameter rest_listen_uri should be an absolute URI (found /foo)"); - - Configuration configuration = new Configuration(); - new JadConfig(new InMemoryRepository(validProperties), configuration).process(); - } - - @Test - public void testWebListenUriIsRelativeURI() throws RepositoryException, ValidationException { - validProperties.put("web_listen_uri", "/foo"); - - expectedException.expect(ValidationException.class); - expectedException.expectMessage("Parameter web_listen_uri should be an absolute URI (found /foo)"); - - Configuration configuration = new Configuration(); - new JadConfig(new InMemoryRepository(validProperties), configuration).process(); - } - - @Test - public void testRestListenUriIsAbsoluteURI() throws RepositoryException, ValidationException { - validProperties.put("rest_listen_uri", "http://www.example.com:12900/"); - - Configuration configuration = new Configuration(); - new JadConfig(new InMemoryRepository(validProperties), configuration).process(); - - assertThat(configuration.getRestListenUri()).isEqualTo(URI.create("http://www.example.com:12900/")); - } - - @Test - public void testWebListenUriIsAbsoluteURI() throws RepositoryException, ValidationException { - validProperties.put("web_listen_uri", "http://www.example.com:12900/web"); - - Configuration configuration = new Configuration(); - new JadConfig(new InMemoryRepository(validProperties), configuration).process(); - - assertThat(configuration.getWebListenUri()).isEqualTo(URI.create("http://www.example.com:12900/web/")); - } - - @Test - public void testRestListenUriWithHttpDefaultPort() throws RepositoryException, ValidationException { - validProperties.put("rest_listen_uri", "http://example.com/"); - - Configuration configuration = new Configuration(); - new JadConfig(new InMemoryRepository(validProperties), configuration).process(); - - assertThat(configuration.getRestListenUri()).hasPort(80); - } - - @Test - public void testRestListenUriWithCustomPort() throws RepositoryException, ValidationException { - validProperties.put("rest_listen_uri", "http://example.com:12900/"); - - Configuration configuration = new Configuration(); - new JadConfig(new InMemoryRepository(validProperties), configuration).process(); - - assertThat(configuration.getRestListenUri()).hasPort(12900); - } - - @Test - public void testWebListenUriWithHttpDefaultPort() throws RepositoryException, ValidationException { - validProperties.put("web_listen_uri", "http://example.com/"); - - Configuration configuration = new Configuration(); - new JadConfig(new InMemoryRepository(validProperties), configuration).process(); - - assertThat(configuration.getWebListenUri()).hasPort(80); - } - - @Test - public void testWebListenUriWithCustomPort() throws RepositoryException, ValidationException { - validProperties.put("web_listen_uri", "http://example.com:9000/"); - - Configuration configuration = new Configuration(); - new JadConfig(new InMemoryRepository(validProperties), configuration).process(); - - assertThat(configuration.getWebListenUri()).hasPort(9000); - } - @Test public void testPasswordSecretIsTooShort() throws ValidationException, RepositoryException { validProperties.put("password_secret", "too short"); @@ -163,30 +81,6 @@ public void testPasswordSecretIsNull() throws ValidationException, RepositoryExc new JadConfig(new InMemoryRepository(validProperties), configuration).process(); } - @Test - public void testApiListenerOnRootAndWebListenerOnSubPath() throws ValidationException, RepositoryException { - validProperties.put("rest_listen_uri", "http://0.0.0.0:12900/"); - validProperties.put("web_listen_uri", "http://0.0.0.0:12900/web/"); - - Configuration configuration = new Configuration(); - new JadConfig(new InMemoryRepository(validProperties), configuration).process(); - - assertThat(configuration.getRestListenUri()).isEqualTo(URI.create("http://0.0.0.0:12900/")); - assertThat(configuration.getWebListenUri()).isEqualTo(URI.create("http://0.0.0.0:12900/web/")); - } - - @Test - public void testWebListenerOnRootAndApiListenerOnSubPath() throws ValidationException, RepositoryException { - validProperties.put("rest_listen_uri", "http://0.0.0.0:9000/api/"); - validProperties.put("web_listen_uri", "http://0.0.0.0:9000/"); - - Configuration configuration = new Configuration(); - new JadConfig(new InMemoryRepository(validProperties), configuration).process(); - - assertThat(configuration.getRestListenUri()).isEqualTo(URI.create("http://0.0.0.0:9000/api/")); - assertThat(configuration.getWebListenUri()).isEqualTo(URI.create("http://0.0.0.0:9000/")); - } - @Test public void testPasswordSecretIsValid() throws ValidationException, RepositoryException { validProperties.put("password_secret", "abcdefghijklmnopqrstuvwxyz"); @@ -196,28 +90,4 @@ public void testPasswordSecretIsValid() throws ValidationException, RepositoryEx assertThat(configuration.getPasswordSecret()).isEqualTo("abcdefghijklmnopqrstuvwxyz"); } - - @Test - public void testRestApiListeningOnWildcardOnSamePortAsWebInterface() throws ValidationException, RepositoryException { - validProperties.put("rest_listen_uri", "http://0.0.0.0:9000/api/"); - validProperties.put("web_listen_uri", "http://127.0.0.1:9000/"); - - expectedException.expect(ValidationException.class); - expectedException.expectMessage("Wildcard IP addresses cannot be used if the Graylog REST API and web interface listen on the same port."); - - Configuration configuration = new Configuration(); - new JadConfig(new InMemoryRepository(validProperties), configuration).process(); - } - - @Test - public void testWebInterfaceListeningOnWildcardOnSamePortAsRestApi() throws ValidationException, RepositoryException { - validProperties.put("rest_listen_uri", "http://127.0.0.1:9000/api/"); - validProperties.put("web_listen_uri", "http://0.0.0.0:9000/"); - - expectedException.expect(ValidationException.class); - expectedException.expectMessage("Wildcard IP addresses cannot be used if the Graylog REST API and web interface listen on the same port."); - - Configuration configuration = new Configuration(); - new JadConfig(new InMemoryRepository(validProperties), configuration).process(); - } } diff --git a/graylog2-server/src/test/java/org/graylog2/configuration/HttpConfigurationTest.java b/graylog2-server/src/test/java/org/graylog2/configuration/HttpConfigurationTest.java new file mode 100644 index 000000000000..665bd78628ef --- /dev/null +++ b/graylog2-server/src/test/java/org/graylog2/configuration/HttpConfigurationTest.java @@ -0,0 +1,407 @@ +/** + * This file is part of Graylog. + * + * Graylog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Graylog is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Graylog. If not, see . + */ +package org.graylog2.configuration; + +import com.github.joschi.jadconfig.JadConfig; +import com.github.joschi.jadconfig.RepositoryException; +import com.github.joschi.jadconfig.ValidationException; +import com.github.joschi.jadconfig.guava.GuavaConverterFactory; +import com.github.joschi.jadconfig.repositories.InMemoryRepository; +import com.google.common.collect.ImmutableMap; +import com.google.common.net.HostAndPort; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +public class HttpConfigurationTest { + @Rule + public final ExpectedException expectedException = ExpectedException.none(); + @Rule + public final TemporaryFolder temporaryFolder = new TemporaryFolder(); + + private HttpConfiguration configuration; + private JadConfig jadConfig; + + @Before + public void setUp() { + configuration = new HttpConfiguration(); + jadConfig = new JadConfig().addConverterFactory(new GuavaConverterFactory()); + } + + @Test + public void testHttpBindAddressIsIPv6Address() throws RepositoryException, ValidationException { + jadConfig.setRepository(new InMemoryRepository(ImmutableMap.of("http_bind_address", "[2001:db8::1]:9000"))) + .addConfigurationBean(configuration) + .process(); + + assertThat(configuration.getHttpBindAddress()).isEqualTo(HostAndPort.fromParts("[2001:db8::1]", 9000)); + } + + @Test + public void testHttpBindAddressIsIPv6AddressWithoutBrackets() throws RepositoryException, ValidationException { + expectedException.expect(ValidationException.class); + expectedException.expectMessage("Possible bracketless IPv6 literal: 2001:db8::1"); + + jadConfig.setRepository(new InMemoryRepository(ImmutableMap.of("http_bind_address", "2001:db8::1"))) + .addConfigurationBean(configuration) + .process(); + } + + @Test + public void testHttpBindAddressIsInvalidIPv6Address() throws RepositoryException, ValidationException { + expectedException.expect(ValidationException.class); + expectedException.expectMessage("Possible bracketless IPv6 literal: ff$$::1"); + + jadConfig.setRepository(new InMemoryRepository(ImmutableMap.of("http_bind_address", "ff$$::1"))) + .addConfigurationBean(configuration) + .process(); + } + + @Test + public void testHttpBindAddressIsIPv4Address() throws RepositoryException, ValidationException { + jadConfig.setRepository(new InMemoryRepository(ImmutableMap.of("http_bind_address", "10.2.3.4:9000"))) + .addConfigurationBean(configuration) + .process(); + + assertThat(configuration.getHttpBindAddress()).isEqualTo(HostAndPort.fromParts("10.2.3.4", 9000)); + } + + @Test + public void testHttpBindAddressIsInvalidIPv4Address() throws RepositoryException, ValidationException { + expectedException.expect(ValidationException.class); + expectedException.expectMessage("1234.5.6.7: "); + + jadConfig.setRepository(new InMemoryRepository(ImmutableMap.of("http_bind_address", "1234.5.6.7:9000"))) + .addConfigurationBean(configuration) + .process(); + } + + @Test + public void testHttpBindAddressIsInvalidHostName() throws RepositoryException, ValidationException { + expectedException.expect(ValidationException.class); + expectedException.expectMessage("this-does-not-exist-42: "); + + jadConfig.setRepository(new InMemoryRepository(ImmutableMap.of("http_bind_address", "this-does-not-exist-42"))) + .addConfigurationBean(configuration) + .process(); + } + + @Test + public void testHttpBindAddressIsValidHostname() throws RepositoryException, ValidationException { + jadConfig.setRepository(new InMemoryRepository(ImmutableMap.of("http_bind_address", "example.com:9000"))) + .addConfigurationBean(configuration) + .process(); + + assertThat(configuration.getHttpBindAddress()).isEqualTo(HostAndPort.fromParts("example.com", 9000)); + } + + @Test + public void testHttpBindAddressWithDefaultPort() throws RepositoryException, ValidationException { + jadConfig.setRepository(new InMemoryRepository(ImmutableMap.of("http_bind_address", "example.com"))) + .addConfigurationBean(configuration) + .process(); + + assertThat(configuration.getHttpBindAddress()).isEqualTo(HostAndPort.fromParts("example.com", 9000)); + } + + @Test + public void testHttpBindAddressWithCustomPort() throws RepositoryException, ValidationException { + jadConfig.setRepository(new InMemoryRepository(ImmutableMap.of("http_bind_address", "example.com:12345"))) + .addConfigurationBean(configuration) + .process(); + + assertThat(configuration.getHttpBindAddress()).isEqualTo(HostAndPort.fromParts("example.com", 12345)); + } + + @Test + public void testHttpPublishUriLocalhost() throws RepositoryException, ValidationException { + jadConfig.setRepository(new InMemoryRepository(ImmutableMap.of("http_bind_address", "127.0.0.1:9000"))).addConfigurationBean(configuration).process(); + + assertThat(configuration.getDefaultHttpUri()).isEqualTo(URI.create("http://127.0.0.1:9000/")); + } + + @Test + public void testHttpBindAddressWildcard() throws RepositoryException, ValidationException { + jadConfig.setRepository(new InMemoryRepository(ImmutableMap.of("http_bind_address", "0.0.0.0:9000"))).addConfigurationBean(configuration).process(); + + assertThat(configuration.getDefaultHttpUri()) + .isNotNull() + .isNotEqualTo(URI.create("http://0.0.0.0:9000")); + } + + @Test + public void testHttpBindAddressIPv6Wildcard() throws RepositoryException, ValidationException { + jadConfig.setRepository(new InMemoryRepository(ImmutableMap.of("http_bind_address", "[::]:9000"))).addConfigurationBean(configuration).process(); + + assertThat(configuration.getDefaultHttpUri()) + .isNotNull() + .isNotEqualTo(URI.create("http://[::]:9000")); + } + + @Test + public void testHttpPublishUriWildcard() throws RepositoryException, ValidationException { + final Map properties = ImmutableMap.of( + "http_bind_address", "0.0.0.0:9000", + "http_publish_uri", "http://0.0.0.0:9000/"); + + jadConfig.setRepository(new InMemoryRepository(properties)).addConfigurationBean(configuration).process(); + + assertThat(configuration.getHttpPublishUri()).isNotEqualTo(URI.create("http://0.0.0.0:9000/")); + } + + @Test + public void testHttpPublishUriIPv6Wildcard() throws RepositoryException, ValidationException { + final Map properties = ImmutableMap.of( + "http_bind_address", "[::]:9000", + "http_publish_uri", "http://[::]:9000/"); + + jadConfig.setRepository(new InMemoryRepository(properties)).addConfigurationBean(configuration).process(); + + assertThat(configuration.getHttpPublishUri()).isNotEqualTo(URI.create("http://[::]:9000/")); + } + + @Test + public void testHttpPublishUriWildcardKeepsPath() throws RepositoryException, ValidationException { + final Map properties = ImmutableMap.of( + "http_bind_address", "0.0.0.0:9000", + "http_publish_uri", "http://0.0.0.0:9000/api/"); + + jadConfig.setRepository(new InMemoryRepository(properties)).addConfigurationBean(configuration).process(); + + assertThat(configuration.getHttpPublishUri()) + .hasPath("/api/") + .isNotEqualTo(URI.create("http://0.0.0.0:9000/api/")); + } + + @Test + public void testHttpPublishUriIPv6WildcardKeepsPath() throws RepositoryException, ValidationException { + final Map properties = ImmutableMap.of( + "http_bind_address", "[::]:9000", + "http_publish_uri", "http://[::]:9000/api/"); + + jadConfig.setRepository(new InMemoryRepository(properties)).addConfigurationBean(configuration).process(); + + assertThat(configuration.getHttpPublishUri()) + .hasPath("/api/") + .isNotEqualTo(URI.create("http://[::]:9000/api/")); + } + + @Test + public void testHttpPublishUriCustomAddress() throws RepositoryException, ValidationException { + jadConfig.setRepository(new InMemoryRepository(ImmutableMap.of("http_bind_address", "10.0.0.1:9000"))).addConfigurationBean(configuration).process(); + + assertThat(configuration.getDefaultHttpUri().toString()).isEqualTo("http://10.0.0.1:9000/"); + } + + @Test + public void testGetUriScheme() throws RepositoryException, ValidationException, IOException { + final HttpConfiguration configWithoutTls = new HttpConfiguration(); + new JadConfig(new InMemoryRepository(ImmutableMap.of("http_enable_tls", "false")), configWithoutTls) + .addConverterFactory(new GuavaConverterFactory()) + .process(); + assertThat(configWithoutTls.getUriScheme()).isEqualTo("http"); + + final Map properties = ImmutableMap.of( + "http_bind_address", "127.0.0.1:9000", + "http_enable_tls", "true", + "http_tls_key_file", temporaryFolder.newFile("graylog.key").getAbsolutePath(), + "http_tls_cert_file", temporaryFolder.newFile("graylog.crt").getAbsolutePath()); + final HttpConfiguration configWithTls = new HttpConfiguration(); + new JadConfig(new InMemoryRepository(properties), configWithTls) + .addConverterFactory(new GuavaConverterFactory()) + .process(); + assertThat(configWithTls.getUriScheme()).isEqualTo("https"); + } + + @Test + public void tlsValidationFailsIfPrivateKeyIsMissing() throws Exception { + final File privateKey = temporaryFolder.newFile("graylog.key"); + final File certificate = temporaryFolder.newFile("graylog.crt"); + + final Map properties = ImmutableMap.of( + "http_enable_tls", "true", + "http_tls_key_file", privateKey.getAbsolutePath(), + "http_tls_cert_file", certificate.getAbsolutePath()); + + assertThat(privateKey.delete()).isTrue(); + + expectedException.expect(ValidationException.class); + expectedException.expectMessage("Unreadable or missing HTTP private key: "); + + jadConfig.setRepository(new InMemoryRepository(properties)).addConfigurationBean(configuration).process(); + } + + @Test + public void tlsValidationFailsIfPrivateKeyIsDirectory() throws Exception { + final File privateKey = temporaryFolder.newFolder("graylog.key"); + final File certificate = temporaryFolder.newFile("graylog.crt"); + + final Map properties = ImmutableMap.of( + "http_enable_tls", "true", + "http_tls_key_file", privateKey.getAbsolutePath(), + "http_tls_cert_file", certificate.getAbsolutePath()); + + assertThat(privateKey.isDirectory()).isTrue(); + + expectedException.expect(ValidationException.class); + expectedException.expectMessage("Unreadable or missing HTTP private key: "); + + jadConfig.setRepository(new InMemoryRepository(properties)).addConfigurationBean(configuration).process(); + } + + @Test + public void tlsValidationFailsIfPrivateKeyIsUnreadable() throws Exception { + final File privateKey = temporaryFolder.newFile("graylog.key"); + final File certificate = temporaryFolder.newFile("graylog.crt"); + + final Map properties = ImmutableMap.of( + "http_enable_tls", "true", + "http_tls_key_file", privateKey.getAbsolutePath(), + "http_tls_cert_file", certificate.getAbsolutePath()); + + assertThat(privateKey.setReadable(false, false)).isTrue(); + + expectedException.expect(ValidationException.class); + expectedException.expectMessage("Unreadable or missing HTTP private key: "); + + jadConfig.setRepository(new InMemoryRepository(properties)).addConfigurationBean(configuration).process(); + } + + @Test + public void tlsValidationFailsIfCertificateIsMissing() throws Exception { + final File privateKey = temporaryFolder.newFile("graylog.key"); + final File certificate = temporaryFolder.newFile("graylog.crt"); + + final Map properties = ImmutableMap.of( + "http_enable_tls", "true", + "http_tls_key_file", privateKey.getAbsolutePath(), + "http_tls_cert_file", certificate.getAbsolutePath()); + + assertThat(certificate.delete()).isTrue(); + + expectedException.expect(ValidationException.class); + expectedException.expectMessage("Unreadable or missing HTTP X.509 certificate: "); + + jadConfig.setRepository(new InMemoryRepository(properties)).addConfigurationBean(configuration).process(); + } + + @Test + public void tlsValidationFailsIfCertificateIsDirectory() throws Exception { + final File privateKey = temporaryFolder.newFile("graylog.key"); + final File certificate = temporaryFolder.newFolder("graylog.crt"); + + final Map properties = ImmutableMap.of( + "http_enable_tls", "true", + "http_tls_key_file", privateKey.getAbsolutePath(), + "http_tls_cert_file", certificate.getAbsolutePath()); + + assertThat(certificate.isDirectory()).isTrue(); + + expectedException.expect(ValidationException.class); + expectedException.expectMessage("Unreadable or missing HTTP X.509 certificate: "); + + jadConfig.setRepository(new InMemoryRepository(properties)).addConfigurationBean(configuration).process(); + } + + @Test + public void tlsValidationFailsIfCertificateIsUnreadable() throws Exception { + final File privateKey = temporaryFolder.newFile("graylog.key"); + final File certificate = temporaryFolder.newFile("graylog.crt"); + + final Map properties = ImmutableMap.of( + "http_enable_tls", "true", + "http_tls_key_file", privateKey.getAbsolutePath(), + "http_tls_cert_file", certificate.getAbsolutePath()); + + assertThat(certificate.setReadable(false, false)).isTrue(); + + expectedException.expect(ValidationException.class); + expectedException.expectMessage("Unreadable or missing HTTP X.509 certificate: "); + + jadConfig.setRepository(new InMemoryRepository(properties)).addConfigurationBean(configuration).process(); + } + + @Test + public void testHttpPublishUriIsRelativeURI() throws RepositoryException, ValidationException { + expectedException.expect(ValidationException.class); + expectedException.expectMessage("Parameter http_publish_uri should be an absolute URI (found /foo)"); + + jadConfig.setRepository(new InMemoryRepository(ImmutableMap.of("http_publish_uri", "/foo"))).addConfigurationBean(configuration).process(); + } + + @Test + public void testHttpExternalUriIsRelativeURI() throws RepositoryException, ValidationException { + jadConfig.setRepository(new InMemoryRepository(ImmutableMap.of("http_external_uri", "/foo/"))).addConfigurationBean(configuration).process(); + + assertThat(configuration.getHttpExternalUri()).isEqualTo(URI.create("/foo/")); + } + + @Test + public void testHttpPublishUriIsAbsoluteURI() throws RepositoryException, ValidationException { + jadConfig.setRepository(new InMemoryRepository(ImmutableMap.of("http_publish_uri", "http://www.example.com:12900/foo/"))).addConfigurationBean(configuration).process(); + + assertThat(configuration.getHttpPublishUri()).isEqualTo(URI.create("http://www.example.com:12900/foo/")); + } + + @Test + public void testHttpPublishUriWithMissingTrailingSlash() throws RepositoryException, ValidationException { + jadConfig.setRepository(new InMemoryRepository(ImmutableMap.of("http_publish_uri", "http://www.example.com:12900/foo"))).addConfigurationBean(configuration).process(); + + assertThat(configuration.getHttpPublishUri()).isEqualTo(URI.create("http://www.example.com:12900/foo/")); + } + + @Test + public void testHttpExternalUriIsAbsoluteURI() throws RepositoryException, ValidationException { + jadConfig.setRepository(new InMemoryRepository(ImmutableMap.of("http_external_uri", "http://www.example.com:12900/foo/"))).addConfigurationBean(configuration).process(); + + assertThat(configuration.getHttpExternalUri()).isEqualTo(URI.create("http://www.example.com:12900/foo/")); + } + + @Test + public void testHttpPublishUriWithHttpDefaultPort() throws RepositoryException, ValidationException { + jadConfig.setRepository(new InMemoryRepository(ImmutableMap.of("http_publish_uri", "http://example.com/"))).addConfigurationBean(configuration).process(); + + assertThat(configuration.getHttpPublishUri()).hasPort(80); + } + + @Test + public void testHttpPublishUriWithCustomPort() throws RepositoryException, ValidationException { + jadConfig.setRepository(new InMemoryRepository(ImmutableMap.of("http_publish_uri", "http://example.com:12900/"))).addConfigurationBean(configuration).process(); + + assertThat(configuration.getHttpPublishUri()).hasPort(12900); + } + + @Test + public void testHttpPublishUriWithCustomScheme() throws RepositoryException, ValidationException { + final Map properties = ImmutableMap.of( + "http_publish_uri", "https://example.com:12900/", + "http_enable_tls", "false"); + + jadConfig.setRepository(new InMemoryRepository(properties)).addConfigurationBean(configuration).process(); + + assertThat(configuration.getHttpPublishUri()).hasScheme("https"); + } +} diff --git a/graylog2-server/src/test/java/org/graylog2/plugin/BaseConfigurationTest.java b/graylog2-server/src/test/java/org/graylog2/plugin/BaseConfigurationTest.java deleted file mode 100644 index e5082a162460..000000000000 --- a/graylog2-server/src/test/java/org/graylog2/plugin/BaseConfigurationTest.java +++ /dev/null @@ -1,466 +0,0 @@ -/** - * This file is part of Graylog. - * - * Graylog is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Graylog is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Graylog. If not, see . - */ -package org.graylog2.plugin; - -import com.github.joschi.jadconfig.JadConfig; -import com.github.joschi.jadconfig.Parameter; -import com.github.joschi.jadconfig.RepositoryException; -import com.github.joschi.jadconfig.ValidationException; -import com.github.joschi.jadconfig.repositories.InMemoryRepository; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.junit.rules.TemporaryFolder; - -import java.io.File; -import java.io.IOException; -import java.net.URI; -import java.util.HashMap; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.assertEquals; - -public class BaseConfigurationTest { - @Rule - public final TemporaryFolder temporaryFolder = new TemporaryFolder(); - @Rule - public final ExpectedException expectedException = ExpectedException.none(); - - private class Configuration extends BaseConfiguration { - @Parameter(value = "rest_listen_uri", required = true) - private URI restListenUri = URI.create("http://127.0.0.1:12900/"); - - @Parameter(value = "web_listen_uri", required = true) - private URI webListenUri = URI.create("http://127.0.0.1:9000/"); - - @Parameter(value = "node_id_file", required = false) - private String nodeIdFile = "/etc/graylog/server/node-id"; - - @Override - public String getNodeIdFile() { - return nodeIdFile; - } - - @Override - public URI getRestListenUri() { - return Tools.getUriWithPort(restListenUri, BaseConfiguration.GRAYLOG_DEFAULT_PORT); - } - - @Override - public URI getWebListenUri() { - return Tools.getUriWithPort(webListenUri, BaseConfiguration.GRAYLOG_DEFAULT_WEB_PORT); - } - } - - private Map validProperties; - - @Before - public void setUp() throws Exception { - validProperties = new HashMap<>(); - - // Required properties - validProperties.put("password_secret", "ipNUnWxmBLCxTEzXcyamrdy0Q3G7HxdKsAvyg30R9SCof0JydiZFiA3dLSkRsbLF"); - // SHA-256 of "admin" - validProperties.put("root_password_sha2", "8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918"); - } - - @Test - public void testRestTransportUriLocalhost() throws RepositoryException, ValidationException { - validProperties.put("rest_listen_uri", "http://127.0.0.1:12900"); - - Configuration configuration = new Configuration(); - new JadConfig(new InMemoryRepository(validProperties), configuration).process(); - - Assert.assertEquals("http://127.0.0.1:12900", configuration.getDefaultRestTransportUri().toString()); - } - - @Test - public void testRestListenUriWildcard() throws RepositoryException, ValidationException { - validProperties.put("rest_listen_uri", "http://0.0.0.0:12900"); - - Configuration configuration = new Configuration(); - new JadConfig(new InMemoryRepository(validProperties), configuration).process(); - - Assert.assertNotEquals("http://0.0.0.0:12900", configuration.getDefaultRestTransportUri().toString()); - Assert.assertNotNull(configuration.getDefaultRestTransportUri()); - } - - @Test - public void testRestTransportUriWildcard() throws RepositoryException, ValidationException { - validProperties.put("rest_listen_uri", "http://0.0.0.0:12900"); - validProperties.put("rest_transport_uri", "http://0.0.0.0:12900"); - - Configuration configuration = new Configuration(); - new JadConfig(new InMemoryRepository(validProperties), configuration).process(); - - Assert.assertNotEquals(URI.create("http://0.0.0.0:12900"), configuration.getRestTransportUri()); - } - - @Test - public void testRestTransportUriWildcardKeepsPath() throws RepositoryException, ValidationException { - validProperties.put("rest_listen_uri", "http://0.0.0.0:12900/api/"); - validProperties.put("rest_transport_uri", "http://0.0.0.0:12900/api/"); - - Configuration configuration = new Configuration(); - new JadConfig(new InMemoryRepository(validProperties), configuration).process(); - - Assert.assertNotEquals(URI.create("http://0.0.0.0:12900/api/"), configuration.getRestTransportUri()); - Assert.assertEquals("/api/", configuration.getRestTransportUri().getPath()); - } - - @Test - public void testRestTransportUriCustom() throws RepositoryException, ValidationException { - validProperties.put("rest_listen_uri", "http://10.0.0.1:12900"); - - Configuration configuration = new Configuration(); - new JadConfig(new InMemoryRepository(validProperties), configuration).process(); - - Assert.assertEquals("http://10.0.0.1:12900", configuration.getDefaultRestTransportUri().toString()); - } - - @Test - public void testGetRestUriScheme() throws RepositoryException, ValidationException, IOException { - validProperties.put("rest_enable_tls", "false"); - final Configuration configWithoutTls = new Configuration(); - new JadConfig(new InMemoryRepository(validProperties), configWithoutTls).process(); - - validProperties.put("rest_enable_tls", "true"); - validProperties.put("rest_tls_key_file", temporaryFolder.newFile("graylog.key").getAbsolutePath()); - validProperties.put("rest_tls_cert_file", temporaryFolder.newFile("graylog.crt").getAbsolutePath()); - final Configuration configWithTls = new Configuration(); - new JadConfig(new InMemoryRepository(validProperties), configWithTls).process(); - - assertEquals("http", configWithoutTls.getRestUriScheme()); - assertEquals("https", configWithTls.getRestUriScheme()); - } - - @Test - public void testGetWebUriScheme() throws RepositoryException, ValidationException, IOException { - validProperties.put("web_enable_tls", "false"); - final Configuration configWithoutTls = new Configuration(); - new JadConfig(new InMemoryRepository(validProperties), configWithoutTls).process(); - - validProperties.put("web_enable_tls", "true"); - validProperties.put("web_tls_key_file", temporaryFolder.newFile("graylog.key").getAbsolutePath()); - validProperties.put("web_tls_cert_file", temporaryFolder.newFile("graylog.crt").getAbsolutePath()); - final Configuration configWithTls = new Configuration(); - new JadConfig(new InMemoryRepository(validProperties), configWithTls).process(); - - assertEquals("http", configWithoutTls.getWebUriScheme()); - assertEquals("https", configWithTls.getWebUriScheme()); - } - - @Test - public void restTlsValidationFailsIfPrivateKeyIsMissing() throws Exception { - final File privateKey = temporaryFolder.newFile("graylog.key"); - final File certificate = temporaryFolder.newFile("graylog.crt"); - - validProperties.put("rest_enable_tls", "true"); - validProperties.put("rest_tls_key_file", privateKey.getAbsolutePath()); - validProperties.put("rest_tls_cert_file", certificate.getAbsolutePath()); - - assertThat(privateKey.delete()).isTrue(); - - expectedException.expect(ValidationException.class); - expectedException.expectMessage("Unreadable or missing REST API private key: "); - - new JadConfig(new InMemoryRepository(validProperties), new Configuration()).process(); - } - - @Test - public void restTlsValidationFailsIfPrivateKeyIsDirectory() throws Exception { - final File privateKey = temporaryFolder.newFolder("graylog.key"); - final File certificate = temporaryFolder.newFile("graylog.crt"); - - validProperties.put("rest_enable_tls", "true"); - validProperties.put("rest_tls_key_file", privateKey.getAbsolutePath()); - validProperties.put("rest_tls_cert_file", certificate.getAbsolutePath()); - - assertThat(privateKey.isDirectory()).isTrue(); - - expectedException.expect(ValidationException.class); - expectedException.expectMessage("Unreadable or missing REST API private key: "); - - new JadConfig(new InMemoryRepository(validProperties), new Configuration()).process(); - } - - @Test - public void restTlsValidationFailsIfPrivateKeyIsUnreadable() throws Exception { - final File privateKey = temporaryFolder.newFile("graylog.key"); - final File certificate = temporaryFolder.newFile("graylog.crt"); - - validProperties.put("rest_enable_tls", "true"); - validProperties.put("rest_tls_key_file", privateKey.getAbsolutePath()); - validProperties.put("rest_tls_cert_file", certificate.getAbsolutePath()); - - assertThat(privateKey.setReadable(false, false)).isTrue(); - - expectedException.expect(ValidationException.class); - expectedException.expectMessage("Unreadable or missing REST API private key: "); - - new JadConfig(new InMemoryRepository(validProperties), new Configuration()).process(); - } - - @Test - public void restTlsValidationFailsIfCertificateIsMissing() throws Exception { - final File privateKey = temporaryFolder.newFile("graylog.key"); - final File certificate = temporaryFolder.newFile("graylog.crt"); - - validProperties.put("rest_enable_tls", "true"); - validProperties.put("rest_tls_key_file", privateKey.getAbsolutePath()); - validProperties.put("rest_tls_cert_file", certificate.getAbsolutePath()); - - assertThat(certificate.delete()).isTrue(); - - expectedException.expect(ValidationException.class); - expectedException.expectMessage("Unreadable or missing REST API X.509 certificate: "); - - new JadConfig(new InMemoryRepository(validProperties), new Configuration()).process(); - } - - @Test - public void restTlsValidationFailsIfCertificateIsDirectory() throws Exception { - final File privateKey = temporaryFolder.newFile("graylog.key"); - final File certificate = temporaryFolder.newFolder("graylog.crt"); - - validProperties.put("rest_enable_tls", "true"); - validProperties.put("rest_tls_key_file", privateKey.getAbsolutePath()); - validProperties.put("rest_tls_cert_file", certificate.getAbsolutePath()); - - assertThat(certificate.isDirectory()).isTrue(); - - expectedException.expect(ValidationException.class); - expectedException.expectMessage("Unreadable or missing REST API X.509 certificate: "); - - new JadConfig(new InMemoryRepository(validProperties), new Configuration()).process(); - } - - @Test - public void restTlsValidationFailsIfCertificateIsUnreadable() throws Exception { - final File privateKey = temporaryFolder.newFile("graylog.key"); - final File certificate = temporaryFolder.newFile("graylog.crt"); - - validProperties.put("rest_enable_tls", "true"); - validProperties.put("rest_tls_key_file", privateKey.getAbsolutePath()); - validProperties.put("rest_tls_cert_file", certificate.getAbsolutePath()); - - assertThat(certificate.setReadable(false, false)).isTrue(); - - expectedException.expect(ValidationException.class); - expectedException.expectMessage("Unreadable or missing REST API X.509 certificate: "); - - new JadConfig(new InMemoryRepository(validProperties), new Configuration()).process(); - } - - @Test - public void webTlsValidationFailsIfPrivateKeyIsMissing() throws Exception { - final File privateKey = temporaryFolder.newFile("graylog.key"); - final File certificate = temporaryFolder.newFile("graylog.crt"); - - validProperties.put("web_enable_tls", "true"); - validProperties.put("web_tls_key_file", privateKey.getAbsolutePath()); - validProperties.put("web_tls_cert_file", certificate.getAbsolutePath()); - - assertThat(privateKey.delete()).isTrue(); - - expectedException.expect(ValidationException.class); - expectedException.expectMessage("Unreadable or missing web interface private key: "); - - new JadConfig(new InMemoryRepository(validProperties), new Configuration()).process(); - } - - @Test - public void webTlsValidationFailsIfPrivateKeyIsDirectory() throws Exception { - final File privateKey = temporaryFolder.newFolder("graylog.key"); - final File certificate = temporaryFolder.newFile("graylog.crt"); - - validProperties.put("web_enable_tls", "true"); - validProperties.put("web_tls_key_file", privateKey.getAbsolutePath()); - validProperties.put("web_tls_cert_file", certificate.getAbsolutePath()); - - assertThat(privateKey.isDirectory()).isTrue(); - - expectedException.expect(ValidationException.class); - expectedException.expectMessage("Unreadable or missing web interface private key: "); - - new JadConfig(new InMemoryRepository(validProperties), new Configuration()).process(); - } - - @Test - public void webTlsValidationFailsIfPrivateKeyIsUnreadable() throws Exception { - final File privateKey = temporaryFolder.newFile("graylog.key"); - final File certificate = temporaryFolder.newFile("graylog.crt"); - - validProperties.put("web_enable_tls", "true"); - validProperties.put("web_tls_key_file", privateKey.getAbsolutePath()); - validProperties.put("web_tls_cert_file", certificate.getAbsolutePath()); - - assertThat(privateKey.setReadable(false, false)).isTrue(); - - expectedException.expect(ValidationException.class); - expectedException.expectMessage("Unreadable or missing web interface private key: "); - - new JadConfig(new InMemoryRepository(validProperties), new Configuration()).process(); - } - - @Test - public void webTlsValidationFailsIfCertificateIsMissing() throws Exception { - final File privateKey = temporaryFolder.newFile("graylog.key"); - final File certificate = temporaryFolder.newFile("graylog.crt"); - - validProperties.put("web_enable_tls", "true"); - validProperties.put("web_tls_key_file", privateKey.getAbsolutePath()); - validProperties.put("web_tls_cert_file", certificate.getAbsolutePath()); - - assertThat(certificate.delete()).isTrue(); - - expectedException.expect(ValidationException.class); - expectedException.expectMessage("Unreadable or missing web interface X.509 certificate: "); - - new JadConfig(new InMemoryRepository(validProperties), new Configuration()).process(); - } - - @Test - public void webTlsValidationFailsIfCertificateIsDirectory() throws Exception { - final File privateKey = temporaryFolder.newFile("graylog.key"); - final File certificate = temporaryFolder.newFolder("graylog.crt"); - - validProperties.put("web_enable_tls", "true"); - validProperties.put("web_tls_key_file", privateKey.getAbsolutePath()); - validProperties.put("web_tls_cert_file", certificate.getAbsolutePath()); - - assertThat(certificate.isDirectory()).isTrue(); - - expectedException.expect(ValidationException.class); - expectedException.expectMessage("Unreadable or missing web interface X.509 certificate: "); - - new JadConfig(new InMemoryRepository(validProperties), new Configuration()).process(); - } - - @Test - public void webTlsValidationFailsIfCertificateIsUnreadable() throws Exception { - final File privateKey = temporaryFolder.newFile("graylog.key"); - final File certificate = temporaryFolder.newFile("graylog.crt"); - - validProperties.put("web_enable_tls", "true"); - validProperties.put("web_tls_key_file", privateKey.getAbsolutePath()); - validProperties.put("web_tls_cert_file", certificate.getAbsolutePath()); - - assertThat(certificate.setReadable(false, false)).isTrue(); - - expectedException.expect(ValidationException.class); - expectedException.expectMessage("Unreadable or missing web interface X.509 certificate: "); - - new JadConfig(new InMemoryRepository(validProperties), new Configuration()).process(); - } - - @Test - public void testRestTransportUriIsRelativeURI() throws RepositoryException, ValidationException { - validProperties.put("rest_transport_uri", "/foo"); - - expectedException.expect(ValidationException.class); - expectedException.expectMessage("Parameter rest_transport_uri should be an absolute URI (found /foo)"); - - Configuration configuration = new Configuration(); - new JadConfig(new InMemoryRepository(validProperties), configuration).process(); - } - - @Test - public void testWebEndpointUriIsRelativeURI() throws RepositoryException, ValidationException { - validProperties.put("web_endpoint_uri", "/foo"); - - Configuration configuration = new Configuration(); - new JadConfig(new InMemoryRepository(validProperties), configuration).process(); - - assertEquals(URI.create("/foo"), configuration.getWebEndpointUri()); - } - - @Test - public void testRestTransportUriIsAbsoluteURI() throws RepositoryException, ValidationException { - validProperties.put("rest_transport_uri", "http://www.example.com:12900/foo"); - - Configuration configuration = new Configuration(); - new JadConfig(new InMemoryRepository(validProperties), configuration).process(); - - assertEquals(URI.create("http://www.example.com:12900/foo/"), configuration.getRestTransportUri()); - } - - @Test - public void testWebEndpointUriIsAbsoluteURI() throws RepositoryException, ValidationException { - validProperties.put("web_endpoint_uri", "http://www.example.com:12900/foo"); - - Configuration configuration = new Configuration(); - new JadConfig(new InMemoryRepository(validProperties), configuration).process(); - - assertEquals(URI.create("http://www.example.com:12900/foo"), configuration.getWebEndpointUri()); - } - - @Test - public void testRestTransportUriWithHttpDefaultPort() throws RepositoryException, ValidationException { - validProperties.put("rest_transport_uri", "http://example.com/"); - - org.graylog2.Configuration configuration = new org.graylog2.Configuration(); - new JadConfig(new InMemoryRepository(validProperties), configuration).process(); - - assertThat(configuration.getRestTransportUri()).hasPort(80); - } - - @Test - public void testRestTransportUriWithCustomPort() throws RepositoryException, ValidationException { - validProperties.put("rest_transport_uri", "http://example.com:12900/"); - - org.graylog2.Configuration configuration = new org.graylog2.Configuration(); - new JadConfig(new InMemoryRepository(validProperties), configuration).process(); - - assertThat(configuration.getRestTransportUri()).hasPort(12900); - } - - @Test - public void testRestTransportUriWithCustomScheme() throws RepositoryException, ValidationException { - validProperties.put("rest_transport_uri", "https://example.com:12900/"); - validProperties.put("rest_enable_tls", "false"); - - org.graylog2.Configuration configuration = new org.graylog2.Configuration(); - new JadConfig(new InMemoryRepository(validProperties), configuration).process(); - - assertThat(configuration.getRestTransportUri()).hasScheme("https"); - } - - @Test - public void testRestListenUriAndWebListenUriWithSameScheme() throws Exception { - final File privateKey = temporaryFolder.newFile("graylog.key"); - final File certificate = temporaryFolder.newFile("graylog.crt"); - - validProperties.put("rest_listen_uri", "https://127.0.0.1:8000/api"); - validProperties.put("rest_transport_uri", "https://127.0.0.1:8000/api"); - validProperties.put("rest_enable_tls", "true"); - validProperties.put("rest_tls_key_file", privateKey.getAbsolutePath()); - validProperties.put("rest_tls_cert_file", certificate.getAbsolutePath()); - validProperties.put("web_listen_uri", "https://127.0.0.1:8000/"); - validProperties.put("web_enable_tls", "true"); - - org.graylog2.Configuration configuration = new org.graylog2.Configuration(); - new JadConfig(new InMemoryRepository(validProperties), configuration).process(); - - assertThat(configuration.getRestListenUri()).hasScheme("https"); - assertThat(configuration.getRestTransportUri()).hasScheme("https"); - assertThat(configuration.getWebListenUri()).hasScheme("https"); - } -} diff --git a/graylog2-server/src/test/java/org/graylog2/plugin/ToolsTest.java b/graylog2-server/src/test/java/org/graylog2/plugin/ToolsTest.java index 3ef4caeb4c64..74ca67ace275 100644 --- a/graylog2-server/src/test/java/org/graylog2/plugin/ToolsTest.java +++ b/graylog2-server/src/test/java/org/graylog2/plugin/ToolsTest.java @@ -18,12 +18,14 @@ import com.google.common.collect.Lists; import com.google.common.io.Resources; +import com.google.common.net.InetAddresses; import org.graylog2.inputs.TestHelper; import org.joda.time.DateTime; import org.junit.Test; import java.io.EOFException; import java.io.IOException; +import java.net.InetAddress; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; @@ -323,4 +325,15 @@ public void normalizeURIReturnsNormalizedURI() { public void normalizeURIReturnsNullIfURIIsNull() { assertNull(Tools.normalizeURI(null, "http", 1234, "/baz")); } + + @Test + public void isWildcardAddress() { + assertTrue(Tools.isWildcardInetAddress(InetAddresses.forString("0.0.0.0"))); + assertTrue(Tools.isWildcardInetAddress(InetAddresses.forString("::"))); + assertFalse(Tools.isWildcardInetAddress(null)); + assertFalse(Tools.isWildcardInetAddress(InetAddresses.forString("127.0.0.1"))); + assertFalse(Tools.isWildcardInetAddress(InetAddresses.forString("::1"))); + assertFalse(Tools.isWildcardInetAddress(InetAddresses.forString("198.51.100.23"))); + assertFalse(Tools.isWildcardInetAddress(InetAddresses.forString("2001:DB8::42"))); + } } diff --git a/graylog2-server/src/test/java/org/graylog2/rest/RestToolsTest.java b/graylog2-server/src/test/java/org/graylog2/rest/RestToolsTest.java index d4b076720d5b..0137b9109897 100644 --- a/graylog2-server/src/test/java/org/graylog2/rest/RestToolsTest.java +++ b/graylog2-server/src/test/java/org/graylog2/rest/RestToolsTest.java @@ -18,15 +18,16 @@ import com.google.common.collect.ImmutableList; import org.glassfish.grizzly.http.server.Request; +import org.graylog2.configuration.HttpConfiguration; import org.graylog2.utilities.IpSubnet; import org.junit.Test; -import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MultivaluedHashMap; +import javax.ws.rs.core.MultivaluedMap; import java.net.URI; import java.util.Collections; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -59,36 +60,34 @@ public void getRemoteAddrFromRequestReturnsClientAddressWithXForwardedForHeaderF } @Test - public void buildEndpointUriReturnsDefaultUriIfHeaderIsMissing() throws Exception { - final HttpHeaders httpHeaders = mock(HttpHeaders.class); - when(httpHeaders.getRequestHeader(anyString())).thenReturn(ImmutableList.of()); - final URI endpointUri = URI.create("http://graylog.example.com"); - assertThat(RestTools.buildEndpointUri(httpHeaders, endpointUri)).isEqualTo(endpointUri.toString()); + public void buildExternalUriReturnsDefaultUriIfHeaderIsMissing() throws Exception { + final MultivaluedMap httpHeaders = new MultivaluedHashMap<>(); + final URI externalUri = URI.create("http://graylog.example.com"); + assertThat(RestTools.buildExternalUri(httpHeaders, externalUri)).isEqualTo(externalUri); } @Test - public void buildEndpointUriReturnsDefaultUriIfHeaderIsEmpty() throws Exception { - final HttpHeaders httpHeaders = mock(HttpHeaders.class); - when(httpHeaders.getRequestHeader(anyString())).thenReturn(ImmutableList.of("")); - final URI endpointUri = URI.create("http://graylog.example.com"); - assertThat(RestTools.buildEndpointUri(httpHeaders, endpointUri)).isEqualTo(endpointUri.toString()); + public void buildExternalUriReturnsDefaultUriIfHeaderIsEmpty() throws Exception { + final MultivaluedMap httpHeaders = new MultivaluedHashMap<>(); + httpHeaders.putSingle(HttpConfiguration.OVERRIDE_HEADER, ""); + final URI externalUri = URI.create("http://graylog.example.com"); + assertThat(RestTools.buildExternalUri(httpHeaders, externalUri)).isEqualTo(externalUri); } @Test - public void buildEndpointUriReturnsHeaderValueIfHeaderIsPresent() throws Exception { - final HttpHeaders httpHeaders = mock(javax.ws.rs.core.HttpHeaders.class); - when(httpHeaders.getRequestHeader(anyString())).thenReturn(ImmutableList.of("http://header.example.com")); - final URI endpointUri = URI.create("http://graylog.example.com"); - assertThat(RestTools.buildEndpointUri(httpHeaders, endpointUri)).isEqualTo("http://header.example.com"); + public void buildExternalUriReturnsHeaderValueIfHeaderIsPresent() throws Exception { + final MultivaluedMap httpHeaders = new MultivaluedHashMap<>(); + httpHeaders.putSingle(HttpConfiguration.OVERRIDE_HEADER, "http://header.example.com"); + final URI externalUri = URI.create("http://graylog.example.com"); + assertThat(RestTools.buildExternalUri(httpHeaders, externalUri)).isEqualTo(URI.create("http://header.example.com")); } @Test public void buildEndpointUriReturnsFirstHeaderValueIfMultipleHeadersArePresent() throws Exception { - final HttpHeaders httpHeaders = mock(HttpHeaders.class); - when(httpHeaders.getRequestHeader(anyString())).thenReturn( - ImmutableList.of("http://header1.example.com", "http://header2.example.com")); + final MultivaluedMap httpHeaders = new MultivaluedHashMap<>(); + httpHeaders.put(HttpConfiguration.OVERRIDE_HEADER, ImmutableList.of("http://header1.example.com", "http://header2.example.com")); final URI endpointUri = URI.create("http://graylog.example.com"); - assertThat(RestTools.buildEndpointUri(httpHeaders, endpointUri)).isEqualTo("http://header1.example.com"); + assertThat(RestTools.buildExternalUri(httpHeaders, endpointUri)).isEqualTo(URI.create("http://header1.example.com")); } @Test diff --git a/graylog2-server/src/test/java/org/graylog2/rest/filter/WebAppNotFoundResponseFilterTest.java b/graylog2-server/src/test/java/org/graylog2/rest/filter/WebAppNotFoundResponseFilterTest.java index 46a32d92ed16..54f749c505d4 100644 --- a/graylog2-server/src/test/java/org/graylog2/rest/filter/WebAppNotFoundResponseFilterTest.java +++ b/graylog2-server/src/test/java/org/graylog2/rest/filter/WebAppNotFoundResponseFilterTest.java @@ -16,7 +16,6 @@ */ package org.graylog2.rest.filter; -import org.graylog2.Configuration; import org.graylog2.web.IndexHtmlGenerator; import org.junit.Before; import org.junit.Rule; @@ -37,6 +36,7 @@ import java.util.List; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -50,18 +50,6 @@ public class WebAppNotFoundResponseFilterTest { @Rule public final MockitoRule mockitoRule = MockitoJUnit.rule(); - private final Configuration configuration = new Configuration() { - @Override - public URI getRestListenUri() { - return URI.create("http://example.com/api/"); - } - - @Override - public URI getWebListenUri() { - return URI.create("http://example.com/web/"); - } - }; - @Mock private ContainerRequestContext requestContext; @Mock @@ -75,9 +63,9 @@ public URI getWebListenUri() { @Before public void setUp() throws Exception { responseHeaders = new MultivaluedHashMap<>(); - when(indexHtmlGenerator.get()).thenReturn("index.html"); + when(indexHtmlGenerator.get(any())).thenReturn("index.html"); when(responseContext.getHeaders()).thenReturn(responseHeaders); - filter = new WebAppNotFoundResponseFilter(configuration, indexHtmlGenerator); + filter = new WebAppNotFoundResponseFilter(indexHtmlGenerator); } @Test diff --git a/graylog2-server/src/test/java/org/graylog2/rest/resources/HelloWorldResourceTest.java b/graylog2-server/src/test/java/org/graylog2/rest/resources/HelloWorldResourceTest.java index 629ddd484369..f3d0756c278f 100644 --- a/graylog2-server/src/test/java/org/graylog2/rest/resources/HelloWorldResourceTest.java +++ b/graylog2-server/src/test/java/org/graylog2/rest/resources/HelloWorldResourceTest.java @@ -16,7 +16,7 @@ */ package org.graylog2.rest.resources; -import org.graylog2.Configuration; +import org.graylog2.configuration.HttpConfiguration; import org.graylog2.plugin.cluster.ClusterConfigService; import org.graylog2.plugin.cluster.ClusterId; import org.graylog2.plugin.system.NodeId; @@ -39,14 +39,12 @@ public class HelloWorldResourceTest extends RestResourceBaseTest { private HelloWorldResource helloWorldResource; private NodeId nodeId; private ClusterConfigService clusterConfigService; - private Configuration configuration; @Before public void setUp() throws Exception { this.nodeId = mock(NodeId.class); this.clusterConfigService = mock(ClusterConfigService.class); - this.configuration = mock(Configuration.class); - this.helloWorldResource = new HelloWorldResource(nodeId, clusterConfigService, configuration); + this.helloWorldResource = new HelloWorldResource(nodeId, clusterConfigService); when(clusterConfigService.getOrDefault(eq(ClusterId.class), any(ClusterId.class))).thenReturn(ClusterId.create(CK_CLUSTER_ID)); when(nodeId.toString()).thenReturn(CK_NODE_ID); @@ -64,27 +62,11 @@ public void rootResourceShouldReturnGeneralStats() throws Exception { @Test public void rootResourceShouldRedirectToWebInterfaceIfHtmlIsRequested() throws Exception { - when(configuration.isRestAndWebOnSamePort()).thenReturn(true); - final String pathToWebIf = "/path_to_web_if"; - when(configuration.getWebPrefix()).thenReturn(pathToWebIf); - - final Response response = helloWorldResource.redirectToWebConsole(); - - assertThat(response).isNotNull(); - - final String locationHeader = response.getHeaderString("Location"); - assertThat(locationHeader).isNotNull().isEqualTo(pathToWebIf); - } - - @Test - public void rootResourceShouldNotRedirectToWebInterfaceIfNotRunningOnSamePort() throws Exception { - when(configuration.isRestAndWebOnSamePort()).thenReturn(false); - final Response response = helloWorldResource.redirectToWebConsole(); assertThat(response).isNotNull(); final String locationHeader = response.getHeaderString("Location"); - assertThat(locationHeader).isNull(); + assertThat(locationHeader).isNotNull().isEqualTo(HttpConfiguration.PATH_WEB); } } diff --git a/graylog2-web-interface/src/components/sources/SourceOverview.jsx b/graylog2-web-interface/src/components/sources/SourceOverview.jsx index 85b58a020d3d..798876f170c9 100644 --- a/graylog2-web-interface/src/components/sources/SourceOverview.jsx +++ b/graylog2-web-interface/src/components/sources/SourceOverview.jsx @@ -297,7 +297,7 @@ const SourceOverview = React.createClass({ if (keepChangeInHistory) { window.location.hash = `#${effectiveRange}`; } else { - window.location.replace(`#${effectiveRange}`); + window.location.replace(`sources#${effectiveRange}`); } this.setState({ range: effectiveRange, histogramDataAvailable: true, loading: true }, () => this.loadData()); }, diff --git a/graylog2-web-interface/src/routing/Routes.jsx b/graylog2-web-interface/src/routing/Routes.jsx index 3e65f1f27ddf..f7c223b97e1b 100644 --- a/graylog2-web-interface/src/routing/Routes.jsx +++ b/graylog2-web-interface/src/routing/Routes.jsx @@ -1,6 +1,6 @@ -import AppConfig from 'util/AppConfig'; -import { PluginStore } from 'graylog-web-plugin/plugin'; -import URI from 'urijs'; +import AppConfig from "util/AppConfig"; +import {PluginStore} from "graylog-web-plugin/plugin"; +import URI from "urijs"; /* * Global registry of plugin routes. Route names are generated automatically from the route path, by removing @@ -196,12 +196,12 @@ const qualifyUrls = (routes, appPrefix) => { Object.keys(routes).forEach((routeName) => { switch (typeof routes[routeName]) { case 'string': - qualifiedRoutes[routeName] = `${appPrefix}${routes[routeName]}`; + qualifiedRoutes[routeName] = new URI(appPrefix + '/' + routes[routeName]).normalizePath().path(); break; case 'function': qualifiedRoutes[routeName] = (...params) => { const result = routes[routeName](...params); - return `${appPrefix}${result}`; + return new URI(appPrefix + '/' + result).normalizePath().path(); }; break; case 'object': diff --git a/misc/graylog.conf b/misc/graylog.conf index 0dc312dbd8c5..483d294455ca 100644 --- a/misc/graylog.conf +++ b/misc/graylog.conf @@ -76,92 +76,95 @@ root_password_sha2 = # Set plugin directory here (relative or absolute) plugin_dir = plugin -# REST API listen URI. Must be reachable by other Graylog server nodes if you run a cluster. -# When using Graylog Collectors, this URI will be used to receive heartbeat messages and must be accessible for all collectors. -rest_listen_uri = http://127.0.0.1:9000/api/ - -# REST API transport address. Defaults to the value of rest_listen_uri. Exception: If rest_listen_uri -# is set to a wildcard IP address (0.0.0.0) the first non-loopback IPv4 system address is used. -# If set, this will be promoted in the cluster discovery APIs, so other nodes may try to connect on -# this address and it is used to generate URLs addressing entities in the REST API. (see rest_listen_uri) -# You will need to define this, if your Graylog server is running behind a HTTP proxy that is rewriting -# the scheme, host name or URI. -# This must not contain a wildcard address (0.0.0.0). -#rest_transport_uri = http://192.168.1.1:9000/api/ - -# Enable CORS headers for REST API. This is necessary for JS-clients accessing the server directly. -# If these are disabled, modern browsers will not be able to retrieve resources from the server. -# This is enabled by default. Uncomment the next line to disable it. -#rest_enable_cors = false +############### +# HTTP settings +############### -# Enable GZIP support for REST API. This compresses API responses and therefore helps to reduce -# overall round trip times. This is enabled by default. Uncomment the next line to disable it. -#rest_enable_gzip = false +#### HTTP bind address +# +# The network interface used by the Graylog HTTP interface. +# +# This network interface must be accessible by all Graylog nodes in the cluster and by all clients +# using the Graylog web interface. +# +# If the port is omitted, Graylog will use port 9000 by default. +# +# Default: 127.0.0.1:9000 +#http_bind_address = 127.0.0.1:9000 +#http_bind_address = [2001:db8::1]:9000 -# Enable HTTPS support for the REST API. This secures the communication with the REST API with -# TLS to prevent request forgery and eavesdropping. This is disabled by default. Uncomment the -# next line to enable it. -#rest_enable_tls = true +#### HTTP publish URI +# +# The HTTP URI of this Graylog node which is used to communicate with the other Graylog nodes in the cluster and by all +# clients using the Graylog web interface. +# +# The URI will be published in the cluster discovery APIs, so that other Graylog nodes will be able to find and connect to this Graylog node. +# +# This configuration setting has to be used if this Graylog node is available on another network interface than $http_bind_address, +# for example if the machine has multiple network interfaces or is behind a NAT gateway. +# +# If $http_bind_address contains a wildcard IPv4 address (0.0.0.0), the first non-loopback IPv4 address of this machine will be used. +# This configuration setting *must not* contain a wildcard address! +# +# Default: http://$http_bind_address/ +#http_publish_uri = http://192.168.1.1:9000/ -# The X.509 certificate chain file in PEM format to use for securing the REST API. -#rest_tls_cert_file = /path/to/graylog.crt +#### External Graylog URI +# +# The public URI of Graylog which will be used by the Graylog web interface to communicate with the Graylog REST API. +# +# The external Graylog URI usually has to be specified, if Graylog is running behind a reverse proxy or load-balancer +# and it will be used to generate URLs addressing entities in the Graylog REST API (see $http_bind_address). +# +# When using Graylog Collector, this URI will be used to receive heartbeat messages and must be accessible for all collectors. +# +# This setting can be overriden on a per-request basis with the "X-Graylog-Server-URL" HTTP request header. +# +# Default: $http_publish_uri +#http_external_uri = -# The PKCS#8 private key file in PEM format to use for securing the REST API. -#rest_tls_key_file = /path/to/graylog.key +#### Enable CORS headers for HTTP interface +# +# This is necessary for JS-clients accessing the server directly. +# If these are disabled, modern browsers will not be able to retrieve resources from the server. +# This is enabled by default. Uncomment the next line to disable it. +#http_enable_cors = false -# The password to unlock the private key used for securing the REST API. -#rest_tls_key_password = secret +#### Enable GZIP support for HTTP interface +# +# This compresses API responses and therefore helps to reduce +# overall round trip times. This is enabled by default. Uncomment the next line to disable it. +#http_enable_gzip = false # The maximum size of the HTTP request headers in bytes. -#rest_max_header_size = 8192 - -# The size of the thread pool used exclusively for serving the REST API. -#rest_thread_pool_size = 16 - -# Comma separated list of trusted proxies that are allowed to set the client address with X-Forwarded-For -# header. May be subnets, or hosts. -#trusted_proxies = 127.0.0.1/32, 0:0:0:0:0:0:0:1/128 - -# Enable the embedded Graylog web interface. -# Default: true -#web_enable = false +#http_max_header_size = 8192 -# Web interface listen URI. -# Configuring a path for the URI here effectively prefixes all URIs in the web interface. This is a replacement -# for the application.context configuration parameter in pre-2.0 versions of the Graylog web interface. -#web_listen_uri = http://127.0.0.1:9000/ +# The size of the thread pool used exclusively for serving the HTTP interface. +#http_thread_pool_size = 16 -# Web interface endpoint URI. This setting can be overriden on a per-request basis with the X-Graylog-Server-URL header. -# Default: $rest_transport_uri -#web_endpoint_uri = - -# Enable CORS headers for the web interface. This is necessary for JS-clients accessing the server directly. -# If these are disabled, modern browsers will not be able to retrieve resources from the server. -#web_enable_cors = false +# HTTPS settings +################ -# Enable/disable GZIP support for the web interface. This compresses HTTP responses and therefore helps to reduce -# overall round trip times. This is enabled by default. Uncomment the next line to disable it. -#web_enable_gzip = false +#### Enable HTTPS support for the HTTP interface +# +# This secures the communication with the HTTP interface with TLS to prevent request forgery and eavesdropping. +# +# Default: false +#http_enable_tls = true -# Enable HTTPS support for the web interface. This secures the communication of the web browser with the web interface -# using TLS to prevent request forgery and eavesdropping. -# This is disabled by default. Uncomment the next line to enable it and see the other related configuration settings. -#web_enable_tls = true +# The X.509 certificate chain file in PEM format to use for securing the HTTP interface. +#http_tls_cert_file = /path/to/graylog.crt -# The X.509 certificate chain file in PEM format to use for securing the web interface. -#web_tls_cert_file = /path/to/graylog-web.crt +# The PKCS#8 private key file in PEM format to use for securing the HTTP interface. +#http_tls_key_file = /path/to/graylog.key -# The PKCS#8 private key file in PEM format to use for securing the web interface. -#web_tls_key_file = /path/to/graylog-web.key +# The password to unlock the private key used for securing the HTTP interface. +#http_tls_key_password = secret -# The password to unlock the private key used for securing the web interface. -#web_tls_key_password = secret -# The maximum size of the HTTP request headers in bytes. -#web_max_header_size = 8192 - -# The size of the thread pool used exclusively for serving the web interface. -#web_thread_pool_size = 16 +# Comma separated list of trusted proxies that are allowed to set the client address with X-Forwarded-For +# header. May be subnets, or hosts. +#trusted_proxies = 127.0.0.1/32, 0:0:0:0:0:0:0:1/128 # List of Elasticsearch hosts Graylog should connect to. # Need to be specified as a comma-separated list of valid URIs for the http ports of your elasticsearch nodes. @@ -557,5 +560,5 @@ content_packs_auto_load = grok-patterns.json # For some cluster-related REST requests, the node must query all other nodes in the cluster. This is the maximum number # of threads available for this. Increase it, if '/cluster/*' requests take long to complete. -# Should be rest_thread_pool_size * average_cluster_size if you have a high number of concurrent users. +# Should be http_thread_pool_size * average_cluster_size if you have a high number of concurrent users. proxied_requests_thread_pool_size = 32