mirror of
https://gitlab.com/mangadex-pub/mangadex_at_home.git
synced 2024-01-19 02:48:37 +00:00
Add support for proxy protocol IP forwarding
This commit is contained in:
parent
10e394db1e
commit
f3d35b60eb
|
@ -46,6 +46,7 @@ dependencies {
|
||||||
implementation group: "org.http4k", name: "http4k-client-okhttp"
|
implementation group: "org.http4k", name: "http4k-client-okhttp"
|
||||||
implementation group: "org.http4k", name: "http4k-metrics-micrometer"
|
implementation group: "org.http4k", name: "http4k-metrics-micrometer"
|
||||||
implementation group: "org.http4k", name: "http4k-server-netty"
|
implementation group: "org.http4k", name: "http4k-server-netty"
|
||||||
|
implementation group: "io.netty", name: "netty-codec-haproxy"
|
||||||
implementation group: "io.netty", name: "netty-transport-native-epoll", classifier: "linux-x86_64"
|
implementation group: "io.netty", name: "netty-transport-native-epoll", classifier: "linux-x86_64"
|
||||||
implementation group: "io.netty.incubator", name: "netty-incubator-transport-native-io_uring", version: "0.0.3.Final", classifier: "linux-x86_64"
|
implementation group: "io.netty.incubator", name: "netty-incubator-transport-native-io_uring", version: "0.0.3.Final", classifier: "linux-x86_64"
|
||||||
testImplementation group: "org.http4k", name: "http4k-testing-kotest"
|
testImplementation group: "org.http4k", name: "http4k-testing-kotest"
|
||||||
|
|
|
@ -80,6 +80,13 @@ server_settings:
|
||||||
# 0 defaults to (2 * your available processors)
|
# 0 defaults to (2 * your available processors)
|
||||||
# https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Runtime.html#availableProcessors()
|
# https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Runtime.html#availableProcessors()
|
||||||
threads: 0
|
threads: 0
|
||||||
|
# Whether to enable support for HAProxy Proxy Protocol
|
||||||
|
# If using a reverse proxy to forward requests to MD@H via
|
||||||
|
# ssl passthrough, you can use Proxy Protocol to preserve
|
||||||
|
# original IP if your reverse proxy supports it. This
|
||||||
|
# will allow geo location metrics to work correctly.
|
||||||
|
# https://www.haproxy.com/blog/haproxy/proxy-protocol/
|
||||||
|
enable_proxy_protocol: false
|
||||||
|
|
||||||
|
|
||||||
# Settings intended for advanced use cases or tinkering
|
# Settings intended for advanced use cases or tinkering
|
||||||
|
|
|
@ -19,6 +19,7 @@ along with this MangaDex@Home. If not, see <http://www.gnu.org/licenses/>.
|
||||||
package mdnet.netty
|
package mdnet.netty
|
||||||
|
|
||||||
import io.netty.bootstrap.ServerBootstrap
|
import io.netty.bootstrap.ServerBootstrap
|
||||||
|
import io.netty.buffer.ByteBuf
|
||||||
import io.netty.channel.*
|
import io.netty.channel.*
|
||||||
import io.netty.channel.epoll.Epoll
|
import io.netty.channel.epoll.Epoll
|
||||||
import io.netty.channel.epoll.EpollEventLoopGroup
|
import io.netty.channel.epoll.EpollEventLoopGroup
|
||||||
|
@ -27,6 +28,12 @@ import io.netty.channel.nio.NioEventLoopGroup
|
||||||
import io.netty.channel.socket.SocketChannel
|
import io.netty.channel.socket.SocketChannel
|
||||||
import io.netty.channel.socket.nio.NioServerSocketChannel
|
import io.netty.channel.socket.nio.NioServerSocketChannel
|
||||||
import io.netty.handler.codec.DecoderException
|
import io.netty.handler.codec.DecoderException
|
||||||
|
import io.netty.handler.codec.ProtocolDetectionResult
|
||||||
|
import io.netty.handler.codec.ProtocolDetectionState
|
||||||
|
import io.netty.handler.codec.haproxy.HAProxyMessage
|
||||||
|
import io.netty.handler.codec.haproxy.HAProxyMessageDecoder
|
||||||
|
import io.netty.handler.codec.haproxy.HAProxyProtocolVersion
|
||||||
|
import io.netty.handler.codec.http.FullHttpRequest
|
||||||
import io.netty.handler.codec.http.HttpObjectAggregator
|
import io.netty.handler.codec.http.HttpObjectAggregator
|
||||||
import io.netty.handler.codec.http.HttpServerCodec
|
import io.netty.handler.codec.http.HttpServerCodec
|
||||||
import io.netty.handler.codec.http.HttpServerKeepAliveHandler
|
import io.netty.handler.codec.http.HttpServerKeepAliveHandler
|
||||||
|
@ -43,7 +50,10 @@ import io.netty.handler.traffic.TrafficCounter
|
||||||
import io.netty.incubator.channel.uring.IOUring
|
import io.netty.incubator.channel.uring.IOUring
|
||||||
import io.netty.incubator.channel.uring.IOUringEventLoopGroup
|
import io.netty.incubator.channel.uring.IOUringEventLoopGroup
|
||||||
import io.netty.incubator.channel.uring.IOUringServerSocketChannel
|
import io.netty.incubator.channel.uring.IOUringServerSocketChannel
|
||||||
|
import io.netty.util.AttributeKey
|
||||||
|
import io.netty.util.AttributeMap
|
||||||
import io.netty.util.DomainWildcardMappingBuilder
|
import io.netty.util.DomainWildcardMappingBuilder
|
||||||
|
import io.netty.util.ReferenceCountUtil
|
||||||
import io.netty.util.concurrent.DefaultEventExecutorGroup
|
import io.netty.util.concurrent.DefaultEventExecutorGroup
|
||||||
import io.netty.util.internal.SystemPropertyUtil
|
import io.netty.util.internal.SystemPropertyUtil
|
||||||
import mdnet.Constants
|
import mdnet.Constants
|
||||||
|
@ -173,6 +183,35 @@ class Netty(
|
||||||
.channelFactory(transport.factory)
|
.channelFactory(transport.factory)
|
||||||
.childHandler(object : ChannelInitializer<SocketChannel>() {
|
.childHandler(object : ChannelInitializer<SocketChannel>() {
|
||||||
public override fun initChannel(ch: SocketChannel) {
|
public override fun initChannel(ch: SocketChannel) {
|
||||||
|
if (serverSettings.enableProxyProtocol) {
|
||||||
|
ch.pipeline().addLast(
|
||||||
|
"proxyProtocol",
|
||||||
|
object : ChannelInboundHandlerAdapter() {
|
||||||
|
override fun channelRead(ctx: ChannelHandlerContext, msg: Any) {
|
||||||
|
if (msg is ByteBuf) {
|
||||||
|
// Since the builtin `HAProxyMessageDecoder` will break non Proxy Protocol requests
|
||||||
|
// we need to use its detection capabilities to only add it when needed.
|
||||||
|
val result: ProtocolDetectionResult<HAProxyProtocolVersion> = HAProxyMessageDecoder.detectProtocol(msg)
|
||||||
|
if (result.state() == ProtocolDetectionState.DETECTED) {
|
||||||
|
ctx.pipeline().addAfter("proxyProtocol", null, HAProxyMessageDecoder())
|
||||||
|
ctx.pipeline().remove(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.channelRead(ctx, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
ch.pipeline().addLast(
|
||||||
|
"saveOriginalIp",
|
||||||
|
object : SimpleChannelInboundHandler<HAProxyMessage>() {
|
||||||
|
override fun channelRead0(ctx: ChannelHandlerContext, msg: HAProxyMessage) {
|
||||||
|
// Store proxy IP in an attribute for later use after HTTP request is extracted.
|
||||||
|
// Using an attribute ensures the value is scoped to this channel.
|
||||||
|
(ctx as AttributeMap).attr(HAPROXY_SOURCE).set(msg.sourceAddress())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
ch.pipeline().addLast(
|
ch.pipeline().addLast(
|
||||||
"ssl",
|
"ssl",
|
||||||
SniHandler(DomainWildcardMappingBuilder(sslContext).build())
|
SniHandler(DomainWildcardMappingBuilder(sslContext).build())
|
||||||
|
@ -206,6 +245,26 @@ class Netty(
|
||||||
ch.pipeline().addLast("keepAlive", HttpServerKeepAliveHandler())
|
ch.pipeline().addLast("keepAlive", HttpServerKeepAliveHandler())
|
||||||
ch.pipeline().addLast("aggregator", HttpObjectAggregator(65536))
|
ch.pipeline().addLast("aggregator", HttpObjectAggregator(65536))
|
||||||
|
|
||||||
|
if (serverSettings.enableProxyProtocol) {
|
||||||
|
ch.pipeline().addLast(
|
||||||
|
"setForwardHeader",
|
||||||
|
object : SimpleChannelInboundHandler<FullHttpRequest>(false) {
|
||||||
|
override fun channelRead0(ctx: ChannelHandlerContext, request: FullHttpRequest) {
|
||||||
|
// The geo location code already supports the `Forwarded header so setting
|
||||||
|
// it is the easiest way to introduce the original IP downstream.
|
||||||
|
if ((ctx as AttributeMap).hasAttr(HAPROXY_SOURCE)) {
|
||||||
|
val addr = (ctx as AttributeMap).attr(HAPROXY_SOURCE).get()
|
||||||
|
request.headers().set("Forwarded", addr)
|
||||||
|
}
|
||||||
|
// Since we're modifying the request without handling it, we must
|
||||||
|
// call retain to ensure it will still be available downstream.
|
||||||
|
ReferenceCountUtil.retain(request)
|
||||||
|
ctx.fireChannelRead(request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
ch.pipeline().addLast("burstLimiter", burstLimiter)
|
ch.pipeline().addLast("burstLimiter", burstLimiter)
|
||||||
|
|
||||||
ch.pipeline().addLast(
|
ch.pipeline().addLast(
|
||||||
|
@ -256,6 +315,7 @@ class Netty(
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val LOGGER = LoggerFactory.getLogger(Netty::class.java)
|
private val LOGGER = LoggerFactory.getLogger(Netty::class.java)
|
||||||
|
private val HAPROXY_SOURCE = AttributeKey.newInstance<String>("haproxy_source")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,7 @@ data class ServerSettings(
|
||||||
val externalIp: String? = null,
|
val externalIp: String? = null,
|
||||||
val port: Int = 443,
|
val port: Int = 443,
|
||||||
val threads: Int = 0,
|
val threads: Int = 0,
|
||||||
|
val enableProxyProtocol: Boolean = false,
|
||||||
)
|
)
|
||||||
|
|
||||||
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy::class)
|
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy::class)
|
||||||
|
|
Loading…
Reference in a new issue