diff --git a/CHANGELOG.md b/CHANGELOG.md index 376c693..303cbfc 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,10 +17,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Security -## [2.0.1] - 2021-03-11 +## [2.0.1] - 2021-05-27 +### Added +- [2021-05-27] Added SNI check to prevent people from simply scanning nodes [@carbotaniuman]. + ### Changed - [2021-05-21] Update metrics and fix cache directory leak [@carbotaniuman]. - [2021-05-21] Change headers to be wildcards [@carbotaniuman]. +- [2021-05-27] Make sending the `Server` header configurable but off by default [@carbotaniuman]. ## [2.0.0] - 2021-03-11 ### Changed @@ -393,6 +397,7 @@ This release contains many breaking changes! Of note are the changes to the cach - [2020-06-11] Tweaked logging configuration to reduce log file sizes by [@carbotaniuman]. [Unreleased]: https://gitlab.com/mangadex/mangadex_at_home/-/compare/2.0.0...HEAD +[2.0.1]: https://gitlab.com/mangadex/mangadex_at_home/-/compare/2.0.0...2.0.1 [2.0.0]: https://gitlab.com/mangadex/mangadex_at_home/-/compare/2.0.0-rc14...2.0.0 [2.0.0-rc14]: https://gitlab.com/mangadex/mangadex_at_home/-/compare/2.0.0-rc13...2.0.0-rc14 [2.0.0-rc13]: https://gitlab.com/mangadex/mangadex_at_home/-/compare/2.0.0-rc12...2.0.0-rc13 diff --git a/src/main/kotlin/mdnet/ServerManager.kt b/src/main/kotlin/mdnet/ServerManager.kt index 03f7427..b024523 100644 --- a/src/main/kotlin/mdnet/ServerManager.kt +++ b/src/main/kotlin/mdnet/ServerManager.kt @@ -264,6 +264,7 @@ class ServerManager( storage, remoteSettings, settings.serverSettings, + settings.devSettings, settings.metricsSettings, statistics, registry, diff --git a/src/main/kotlin/mdnet/netty/ApplicationNetty.kt b/src/main/kotlin/mdnet/netty/ApplicationNetty.kt index 73a6f4b..081d1e3 100644 --- a/src/main/kotlin/mdnet/netty/ApplicationNetty.kt +++ b/src/main/kotlin/mdnet/netty/ApplicationNetty.kt @@ -30,6 +30,8 @@ import io.netty.handler.codec.DecoderException import io.netty.handler.codec.http.HttpObjectAggregator import io.netty.handler.codec.http.HttpServerCodec import io.netty.handler.codec.http.HttpServerKeepAliveHandler +import io.netty.handler.ssl.SniCompletionEvent +import io.netty.handler.ssl.SniHandler import io.netty.handler.ssl.SslContextBuilder import io.netty.handler.stream.ChunkedWriteHandler import io.netty.handler.timeout.ReadTimeoutException @@ -41,6 +43,7 @@ import io.netty.handler.traffic.TrafficCounter import io.netty.incubator.channel.uring.IOUring import io.netty.incubator.channel.uring.IOUringEventLoopGroup import io.netty.incubator.channel.uring.IOUringServerSocketChannel +import io.netty.util.DomainWildcardMappingBuilder import io.netty.util.concurrent.DefaultEventExecutorGroup import io.netty.util.internal.SystemPropertyUtil import mdnet.Constants @@ -48,6 +51,7 @@ import mdnet.data.Statistics import mdnet.logging.info import mdnet.logging.trace import mdnet.logging.warn +import mdnet.settings.DevSettings import mdnet.settings.ServerSettings import mdnet.settings.TlsCert import org.http4k.core.HttpHandler @@ -138,6 +142,7 @@ sealed class NettyTransport(threads: Int) { class Netty( private val tls: TlsCert, private val serverSettings: ServerSettings, + private val devSettings: DevSettings, private val statistics: Statistics ) : ServerConfig { override fun toServer(http: HttpHandler): Http4kServer = object : Http4kServer { @@ -167,7 +172,29 @@ class Netty( .channelFactory(transport.factory) .childHandler(object : ChannelInitializer() { public override fun initChannel(ch: SocketChannel) { - ch.pipeline().addLast("ssl", sslContext.newHandler(ch.alloc())) + ch.pipeline().addLast( + "ssl", + SniHandler(DomainWildcardMappingBuilder(sslContext).build()) + ) + + ch.pipeline().addLast( + "dropHostname", + object : ChannelInboundHandlerAdapter() { + override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { + if (evt is SniCompletionEvent) { + if (!devSettings.disableSniCheck) { + if (!evt.hostname().endsWith("mangadex.network") && + !evt.hostname().endsWith("localhost") + ) { + ctx.close() + } + } + } else { + ctx.fireUserEventTriggered(evt) + } + } + } + ) ch.pipeline().addLast("codec", HttpServerCodec()) ch.pipeline().addLast("keepAlive", HttpServerKeepAliveHandler()) diff --git a/src/main/kotlin/mdnet/server/ImageServer.kt b/src/main/kotlin/mdnet/server/ImageServer.kt index 1af0f1a..6671b2f 100644 --- a/src/main/kotlin/mdnet/server/ImageServer.kt +++ b/src/main/kotlin/mdnet/server/ImageServer.kt @@ -31,6 +31,7 @@ import mdnet.logging.warn import mdnet.metrics.GeoIpMetricsFilterBuilder import mdnet.metrics.PostTransactionLabeler import mdnet.netty.Netty +import mdnet.settings.DevSettings import mdnet.settings.MetricsSettings import mdnet.settings.RemoteSettings import mdnet.settings.ServerSettings @@ -46,6 +47,7 @@ fun getServer( storage: ImageStorage, remoteSettings: RemoteSettings, serverSettings: ServerSettings, + devSettings: DevSettings, metricsSettings: MetricsSettings, statistics: Statistics, registry: PrometheusMeterRegistry, @@ -109,7 +111,7 @@ fun getServer( ) return timeRequest() - .then(addCommonHeaders()) + .then(addCommonHeaders(devSettings.sendServerHeader)) .then(catchAllHideDetails()) .then( routes( @@ -142,7 +144,7 @@ fun getServer( GeoIpMetricsFilterBuilder(metricsSettings.enableGeoip, metricsSettings.geoipLicenseKey, registry).build() ) ) - .asServer(Netty(remoteSettings.tls!!, serverSettings, statistics)) + .asServer(Netty(remoteSettings.tls!!, serverSettings, devSettings, statistics)) } private val LOGGER = LoggerFactory.getLogger(ImageServer::class.java) diff --git a/src/main/kotlin/mdnet/server/common.kt b/src/main/kotlin/mdnet/server/common.kt index aeae7fd..9cd4e72 100644 --- a/src/main/kotlin/mdnet/server/common.kt +++ b/src/main/kotlin/mdnet/server/common.kt @@ -35,12 +35,17 @@ import java.util.* private val HTTP_TIME_FORMATTER = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss O", Locale.ENGLISH) private val LOGGER = LoggerFactory.getLogger("Application") -fun addCommonHeaders(): Filter { +fun addCommonHeaders(sendServerHeader: Boolean): Filter { return Filter { next: HttpHandler -> { request: Request -> val response = next(request) - response.header("Date", HTTP_TIME_FORMATTER.format(ZonedDateTime.now(ZoneOffset.UTC))) - .header("Server", "MangaDex@Home Node ${BuildInfo.VERSION} (${Constants.CLIENT_BUILD})") + response.header("Date", HTTP_TIME_FORMATTER.format(ZonedDateTime.now(ZoneOffset.UTC))).let { + if (sendServerHeader) { + it.header("Server", "MangaDex@Home Node ${BuildInfo.VERSION} (${Constants.CLIENT_BUILD})") + } else { + it + } + } } } } diff --git a/src/main/kotlin/mdnet/settings/ClientSettings.kt b/src/main/kotlin/mdnet/settings/ClientSettings.kt index 5f416e6..e8a52d5 100644 --- a/src/main/kotlin/mdnet/settings/ClientSettings.kt +++ b/src/main/kotlin/mdnet/settings/ClientSettings.kt @@ -47,7 +47,9 @@ data class ServerSettings( @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy::class) data class DevSettings( - val devUrl: String? = null + val devUrl: String? = null, + val disableSniCheck: Boolean = false, + val sendServerHeader: Boolean = false, ) @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy::class) diff --git a/src/test/kotlin/mdnet/server/ImageServerTest.kt b/src/test/kotlin/mdnet/server/ImageServerTest.kt index 10cf4a7..60567ba 100644 --- a/src/test/kotlin/mdnet/server/ImageServerTest.kt +++ b/src/test/kotlin/mdnet/server/ImageServerTest.kt @@ -39,6 +39,7 @@ import org.http4k.core.Response import org.http4k.core.Status import org.http4k.kotest.shouldHaveHeader import org.http4k.kotest.shouldHaveStatus +import org.http4k.kotest.shouldNotHaveHeader import org.http4k.routing.bind import org.http4k.routing.routes import org.ktorm.database.Database @@ -106,6 +107,12 @@ class ImageServerTest : FreeSpec() { response.close() } } + + "should not have Server header" { + val response = handler(Request(Method.GET, "/data/02181a8f5fe8cd408720a771dd129fd8/T2.png")) + response.shouldNotHaveHeader("Server") + response.close() + } } "with real cache" - {