Added server support for proxy protocol
This commit is contained in:
@@ -5,7 +5,7 @@ on:
|
|||||||
- 'dev'
|
- 'dev'
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: hostinger
|
runs-on: woryzen
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout sources
|
- name: Checkout sources
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -18,12 +18,6 @@ jobs:
|
|||||||
- name: Get project version
|
- name: Get project version
|
||||||
id: retrieve-version
|
id: retrieve-version
|
||||||
run: ./gradlew -q version >> "$GITHUB_OUTPUT"
|
run: ./gradlew -q version >> "$GITHUB_OUTPUT"
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
with:
|
|
||||||
driver: docker-container
|
|
||||||
- name: Login to Gitea container registry
|
- name: Login to Gitea container registry
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
@@ -34,6 +28,7 @@ jobs:
|
|||||||
name: Build rbcs Docker image
|
name: Build rbcs Docker image
|
||||||
uses: docker/build-push-action@v5.3.0
|
uses: docker/build-push-action@v5.3.0
|
||||||
with:
|
with:
|
||||||
|
builder: "multiplatform-builder"
|
||||||
context: "docker/build/docker"
|
context: "docker/build/docker"
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
push: true
|
push: true
|
||||||
@@ -41,11 +36,11 @@ jobs:
|
|||||||
tags: |
|
tags: |
|
||||||
gitea.woggioni.net/woggioni/rbcs:vanilla-dev
|
gitea.woggioni.net/woggioni/rbcs:vanilla-dev
|
||||||
target: release-vanilla
|
target: release-vanilla
|
||||||
cache-from: type=registry,ref=gitea.woggioni.net/woggioni/rbcs:buildx
|
|
||||||
-
|
-
|
||||||
name: Build rbcs memcache Docker image
|
name: Build rbcs memcache Docker image
|
||||||
uses: docker/build-push-action@v5.3.0
|
uses: docker/build-push-action@v5.3.0
|
||||||
with:
|
with:
|
||||||
|
builder: "multiplatform-builder"
|
||||||
context: "docker/build/docker"
|
context: "docker/build/docker"
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
push: true
|
push: true
|
||||||
@@ -53,12 +48,11 @@ jobs:
|
|||||||
tags: |
|
tags: |
|
||||||
gitea.woggioni.net/woggioni/rbcs:memcache-dev
|
gitea.woggioni.net/woggioni/rbcs:memcache-dev
|
||||||
target: release-memcache
|
target: release-memcache
|
||||||
cache-from: type=registry,ref=gitea.woggioni.net/woggioni/rbcs:buildx
|
|
||||||
cache-to: type=registry,mode=max,compression=zstd,image-manifest=true,oci-mediatypes=true,ref=gitea.woggioni.net/woggioni/rbcs:buildx
|
|
||||||
-
|
-
|
||||||
name: Build rbcs native Docker image
|
name: Build rbcs native Docker image
|
||||||
uses: docker/build-push-action@v5.3.0
|
uses: docker/build-push-action@v5.3.0
|
||||||
with:
|
with:
|
||||||
|
builder: "multiplatform-builder"
|
||||||
context: "docker/build/docker"
|
context: "docker/build/docker"
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
push: true
|
push: true
|
||||||
@@ -70,6 +64,7 @@ jobs:
|
|||||||
name: Build rbcs jlink Docker image
|
name: Build rbcs jlink Docker image
|
||||||
uses: docker/build-push-action@v5.3.0
|
uses: docker/build-push-action@v5.3.0
|
||||||
with:
|
with:
|
||||||
|
builder: "multiplatform-builder"
|
||||||
context: "docker/build/docker"
|
context: "docker/build/docker"
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
push: true
|
push: true
|
||||||
|
|||||||
@@ -18,12 +18,6 @@ jobs:
|
|||||||
- name: Get project version
|
- name: Get project version
|
||||||
id: retrieve-version
|
id: retrieve-version
|
||||||
run: ./gradlew -q version >> "$GITHUB_OUTPUT"
|
run: ./gradlew -q version >> "$GITHUB_OUTPUT"
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
with:
|
|
||||||
driver: docker-container
|
|
||||||
- name: Login to Gitea container registry
|
- name: Login to Gitea container registry
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
@@ -34,6 +28,7 @@ jobs:
|
|||||||
name: Build rbcs Docker image
|
name: Build rbcs Docker image
|
||||||
uses: docker/build-push-action@v5.3.0
|
uses: docker/build-push-action@v5.3.0
|
||||||
with:
|
with:
|
||||||
|
builder: "multiplatform-builder"
|
||||||
context: "docker/build/docker"
|
context: "docker/build/docker"
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
push: true
|
push: true
|
||||||
@@ -42,11 +37,11 @@ jobs:
|
|||||||
gitea.woggioni.net/woggioni/rbcs:vanilla
|
gitea.woggioni.net/woggioni/rbcs:vanilla
|
||||||
gitea.woggioni.net/woggioni/rbcs:vanilla-${{ steps.retrieve-version.outputs.VERSION }}
|
gitea.woggioni.net/woggioni/rbcs:vanilla-${{ steps.retrieve-version.outputs.VERSION }}
|
||||||
target: release-vanilla
|
target: release-vanilla
|
||||||
cache-from: type=registry,ref=gitea.woggioni.net/woggioni/rbcs:buildx
|
|
||||||
-
|
-
|
||||||
name: Build rbcs memcache Docker image
|
name: Build rbcs memcache Docker image
|
||||||
uses: docker/build-push-action@v5.3.0
|
uses: docker/build-push-action@v5.3.0
|
||||||
with:
|
with:
|
||||||
|
builder: "multiplatform-builder"
|
||||||
context: "docker/build/docker"
|
context: "docker/build/docker"
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
push: true
|
push: true
|
||||||
@@ -57,12 +52,11 @@ jobs:
|
|||||||
gitea.woggioni.net/woggioni/rbcs:memcache
|
gitea.woggioni.net/woggioni/rbcs:memcache
|
||||||
gitea.woggioni.net/woggioni/rbcs:memcache-${{ steps.retrieve-version.outputs.VERSION }}
|
gitea.woggioni.net/woggioni/rbcs:memcache-${{ steps.retrieve-version.outputs.VERSION }}
|
||||||
target: release-memcache
|
target: release-memcache
|
||||||
cache-from: type=registry,ref=gitea.woggioni.net/woggioni/rbcs:buildx
|
|
||||||
cache-to: type=registry,mode=max,compression=zstd,image-manifest=true,oci-mediatypes=true,ref=gitea.woggioni.net/woggioni/rbcs:buildx
|
|
||||||
-
|
-
|
||||||
name: Build rbcs native Docker image
|
name: Build rbcs native Docker image
|
||||||
uses: docker/build-push-action@v5.3.0
|
uses: docker/build-push-action@v5.3.0
|
||||||
with:
|
with:
|
||||||
|
builder: "multiplatform-builder"
|
||||||
context: "docker/build/docker"
|
context: "docker/build/docker"
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
push: true
|
push: true
|
||||||
@@ -75,6 +69,7 @@ jobs:
|
|||||||
name: Build rbcs jlink Docker image
|
name: Build rbcs jlink Docker image
|
||||||
uses: docker/build-push-action@v5.3.0
|
uses: docker/build-push-action@v5.3.0
|
||||||
with:
|
with:
|
||||||
|
builder: "multiplatform-builder"
|
||||||
context: "docker/build/docker"
|
context: "docker/build/docker"
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
push: true
|
push: true
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ ENTRYPOINT ["/usr/bin/rbcs-cli", "-XX:MaximumHeapSizePercent=70"]
|
|||||||
FROM debian:12-slim AS release-jlink
|
FROM debian:12-slim AS release-jlink
|
||||||
RUN mkdir -p /usr/share/java/rbcs
|
RUN mkdir -p /usr/share/java/rbcs
|
||||||
RUN --mount=type=bind,source=.,target=/build/distributions tar -xf /build/distributions/rbcs-cli*.tar -C /usr/share/java/rbcs
|
RUN --mount=type=bind,source=.,target=/build/distributions tar -xf /build/distributions/rbcs-cli*.tar -C /usr/share/java/rbcs
|
||||||
|
RUN chmod 755 /usr/share/java/rbcs/bin/*
|
||||||
ADD --chmod=755 rbcs-cli.sh /usr/local/bin/rbcs-cli
|
ADD --chmod=755 rbcs-cli.sh /usr/local/bin/rbcs-cli
|
||||||
RUN adduser -u 1000 luser
|
RUN adduser -u 1000 luser
|
||||||
USER luser
|
USER luser
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
DIR=/usr/share/java/rbcs
|
DIR=/usr/share/java/rbcs
|
||||||
$DIR/bin/java $JAVA_OPTS -m net.woggioni.rbcs.cli "$@"
|
exec $DIR/bin/java $JAVA_OPTS -m net.woggioni.rbcs.cli $@
|
||||||
@@ -4,7 +4,7 @@ org.gradle.caching=true
|
|||||||
|
|
||||||
rbcs.version = 0.3.7
|
rbcs.version = 0.3.7
|
||||||
|
|
||||||
lys.version = 2025.12.26
|
lys.version = 2025.12.27
|
||||||
|
|
||||||
gitea.maven.url = https://gitea.woggioni.net/api/packages/woggioni/maven
|
gitea.maven.url = https://gitea.woggioni.net/api/packages/woggioni/maven
|
||||||
docker.registry.url=gitea.woggioni.net
|
docker.registry.url=gitea.woggioni.net
|
||||||
|
|||||||
@@ -4,10 +4,12 @@ package net.woggioni.rbcs.api;
|
|||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
|
import net.woggioni.rbcs.common.Cidr;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@@ -16,6 +18,8 @@ import java.util.stream.Collectors;
|
|||||||
public class Configuration {
|
public class Configuration {
|
||||||
String host;
|
String host;
|
||||||
int port;
|
int port;
|
||||||
|
boolean proxyProtocolEnabled;
|
||||||
|
List<Cidr> trustedProxyIPs;
|
||||||
int incomingConnectionsBacklogSize;
|
int incomingConnectionsBacklogSize;
|
||||||
String serverPath;
|
String serverPath;
|
||||||
@NonNull
|
@NonNull
|
||||||
@@ -30,6 +34,7 @@ public class Configuration {
|
|||||||
Authentication authentication;
|
Authentication authentication;
|
||||||
Tls tls;
|
Tls tls;
|
||||||
|
|
||||||
|
|
||||||
@Value
|
@Value
|
||||||
public static class RateLimiter {
|
public static class RateLimiter {
|
||||||
boolean delayRequest;
|
boolean delayRequest;
|
||||||
@@ -140,6 +145,8 @@ public class Configuration {
|
|||||||
public static Configuration of(
|
public static Configuration of(
|
||||||
String host,
|
String host,
|
||||||
int port,
|
int port,
|
||||||
|
boolean proxyProtocolEnabled,
|
||||||
|
List<Cidr> trustedProxyIPs,
|
||||||
int incomingConnectionsBacklogSize,
|
int incomingConnectionsBacklogSize,
|
||||||
String serverPath,
|
String serverPath,
|
||||||
EventExecutor eventExecutor,
|
EventExecutor eventExecutor,
|
||||||
@@ -154,6 +161,8 @@ public class Configuration {
|
|||||||
return new Configuration(
|
return new Configuration(
|
||||||
host,
|
host,
|
||||||
port,
|
port,
|
||||||
|
proxyProtocolEnabled,
|
||||||
|
trustedProxyIPs,
|
||||||
incomingConnectionsBacklogSize,
|
incomingConnectionsBacklogSize,
|
||||||
serverPath != null && !serverPath.isEmpty() && !serverPath.equals("/") ? serverPath : null,
|
serverPath != null && !serverPath.isEmpty() && !serverPath.equals("/") ? serverPath : null,
|
||||||
eventExecutor,
|
eventExecutor,
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ Provider<UpxTask> upxTaskProvider = tasks.named(NativeImagePlugin.UPX_TASK_NAME,
|
|||||||
|
|
||||||
Provider<JlinkTask> jlinkTaskProvider = tasks.named(JlinkPlugin.JLINK_TASK_NAME, JlinkTask) {
|
Provider<JlinkTask> jlinkTaskProvider = tasks.named(JlinkPlugin.JLINK_TASK_NAME, JlinkTask) {
|
||||||
toolchain {
|
toolchain {
|
||||||
languageVersion = JavaLanguageVersion.of(21)
|
languageVersion = JavaLanguageVersion.of(25)
|
||||||
vendor = JvmVendorSpec.GRAAL_VM
|
vendor = JvmVendorSpec.GRAAL_VM
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -97,6 +97,8 @@ object GraalNativeImageConfiguration {
|
|||||||
val serverConfiguration = Configuration(
|
val serverConfiguration = Configuration(
|
||||||
"127.0.0.1",
|
"127.0.0.1",
|
||||||
serverPort,
|
serverPort,
|
||||||
|
false,
|
||||||
|
emptyList(),
|
||||||
100,
|
100,
|
||||||
null,
|
null,
|
||||||
Configuration.EventExecutor(true),
|
Configuration.EventExecutor(true),
|
||||||
|
|||||||
62
rbcs-common/src/main/kotlin/net/woggioni/rbcs/common/Cidr.kt
Normal file
62
rbcs-common/src/main/kotlin/net/woggioni/rbcs/common/Cidr.kt
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
package net.woggioni.rbcs.common
|
||||||
|
|
||||||
|
import java.net.InetAddress
|
||||||
|
|
||||||
|
data class Cidr private constructor(
|
||||||
|
val networkAddress: InetAddress,
|
||||||
|
val prefixLength: Int
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
fun from(cidr: String) : Cidr {
|
||||||
|
val separator = cidr.indexOf("/")
|
||||||
|
if(separator < 0) {
|
||||||
|
throw IllegalArgumentException("Invalid CIDR format: $cidr")
|
||||||
|
}
|
||||||
|
val networkAddress = InetAddress.getByName(cidr.substring(0, separator))
|
||||||
|
val prefixLength = cidr.substring(separator + 1, cidr.length).toInt()
|
||||||
|
|
||||||
|
|
||||||
|
// Validate prefix length
|
||||||
|
val maxPrefix = if (networkAddress.address.size == 4) 32 else 128
|
||||||
|
require(prefixLength in 0..maxPrefix) { "Invalid prefix length: $prefixLength" }
|
||||||
|
return Cidr(networkAddress, prefixLength)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun contains(address: InetAddress): Boolean {
|
||||||
|
val networkBytes = networkAddress.address
|
||||||
|
val addressBytes = address.address
|
||||||
|
|
||||||
|
if (networkBytes.size != addressBytes.size) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Calculate how many full bytes and remaining bits to check
|
||||||
|
val fullBytes = prefixLength / 8
|
||||||
|
val remainingBits = prefixLength % 8
|
||||||
|
|
||||||
|
|
||||||
|
// Check full bytes
|
||||||
|
for (i in 0..<fullBytes) {
|
||||||
|
if (networkBytes[i] != addressBytes[i]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Check remaining bits if any
|
||||||
|
if (remainingBits > 0 && fullBytes < networkBytes.size) {
|
||||||
|
val mask = (0xFF shl (8 - remainingBits)).toByte()
|
||||||
|
if ((networkBytes[fullBytes].toInt() and mask.toInt()) != (addressBytes[fullBytes].toInt() and mask.toInt())) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return networkAddress.hostAddress + "/" + prefixLength
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -81,9 +81,6 @@ inline fun Logger.log(level: Level, channel: Channel, crossinline messageBuilder
|
|||||||
)
|
)
|
||||||
withMDC(params) {
|
withMDC(params) {
|
||||||
val builder = makeLoggingEventBuilder(level)
|
val builder = makeLoggingEventBuilder(level)
|
||||||
// for ((key, value) in params) {
|
|
||||||
// builder.addKeyValue(key, value)
|
|
||||||
// }
|
|
||||||
messageBuilder(builder)
|
messageBuilder(builder)
|
||||||
builder.log()
|
builder.log()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ dependencies {
|
|||||||
implementation catalog.netty.handler
|
implementation catalog.netty.handler
|
||||||
implementation catalog.netty.buffer
|
implementation catalog.netty.buffer
|
||||||
implementation catalog.netty.transport
|
implementation catalog.netty.transport
|
||||||
|
implementation catalog.netty.codec.haproxy
|
||||||
|
|
||||||
api project(':rbcs-common')
|
api project(':rbcs-common')
|
||||||
api project(':rbcs-api')
|
api project(':rbcs-api')
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ module net.woggioni.rbcs.server {
|
|||||||
requires io.netty.buffer;
|
requires io.netty.buffer;
|
||||||
requires io.netty.common;
|
requires io.netty.common;
|
||||||
requires io.netty.codec;
|
requires io.netty.codec;
|
||||||
|
requires io.netty.codec.haproxy;
|
||||||
requires org.slf4j;
|
requires org.slf4j;
|
||||||
|
|
||||||
exports net.woggioni.rbcs.server;
|
exports net.woggioni.rbcs.server;
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import io.netty.channel.socket.nio.NioDatagramChannel
|
|||||||
import io.netty.channel.socket.nio.NioServerSocketChannel
|
import io.netty.channel.socket.nio.NioServerSocketChannel
|
||||||
import io.netty.channel.socket.nio.NioSocketChannel
|
import io.netty.channel.socket.nio.NioSocketChannel
|
||||||
import io.netty.handler.codec.compression.CompressionOptions
|
import io.netty.handler.codec.compression.CompressionOptions
|
||||||
|
import io.netty.handler.codec.haproxy.HAProxyMessageDecoder
|
||||||
import io.netty.handler.codec.http.DefaultHttpContent
|
import io.netty.handler.codec.http.DefaultHttpContent
|
||||||
import io.netty.handler.codec.http.HttpContentCompressor
|
import io.netty.handler.codec.http.HttpContentCompressor
|
||||||
import io.netty.handler.codec.http.HttpDecoderConfig
|
import io.netty.handler.codec.http.HttpDecoderConfig
|
||||||
@@ -57,6 +58,7 @@ import javax.net.ssl.SSLPeerUnverifiedException
|
|||||||
import net.woggioni.rbcs.api.AsyncCloseable
|
import net.woggioni.rbcs.api.AsyncCloseable
|
||||||
import net.woggioni.rbcs.api.Configuration
|
import net.woggioni.rbcs.api.Configuration
|
||||||
import net.woggioni.rbcs.api.exception.ConfigurationException
|
import net.woggioni.rbcs.api.exception.ConfigurationException
|
||||||
|
import net.woggioni.rbcs.common.Cidr
|
||||||
import net.woggioni.rbcs.common.PasswordSecurity.decodePasswordHash
|
import net.woggioni.rbcs.common.PasswordSecurity.decodePasswordHash
|
||||||
import net.woggioni.rbcs.common.PasswordSecurity.hashPassword
|
import net.woggioni.rbcs.common.PasswordSecurity.hashPassword
|
||||||
import net.woggioni.rbcs.common.RBCS.getTrustManager
|
import net.woggioni.rbcs.common.RBCS.getTrustManager
|
||||||
@@ -73,6 +75,7 @@ import net.woggioni.rbcs.server.configuration.Parser
|
|||||||
import net.woggioni.rbcs.server.configuration.Serializer
|
import net.woggioni.rbcs.server.configuration.Serializer
|
||||||
import net.woggioni.rbcs.server.exception.ExceptionHandler
|
import net.woggioni.rbcs.server.exception.ExceptionHandler
|
||||||
import net.woggioni.rbcs.server.handler.MaxRequestSizeHandler
|
import net.woggioni.rbcs.server.handler.MaxRequestSizeHandler
|
||||||
|
import net.woggioni.rbcs.server.handler.ProxyProtocolHandler
|
||||||
import net.woggioni.rbcs.server.handler.ReadTriggerDuplexHandler
|
import net.woggioni.rbcs.server.handler.ReadTriggerDuplexHandler
|
||||||
import net.woggioni.rbcs.server.handler.ServerHandler
|
import net.woggioni.rbcs.server.handler.ServerHandler
|
||||||
import net.woggioni.rbcs.server.throttling.BucketManager
|
import net.woggioni.rbcs.server.throttling.BucketManager
|
||||||
@@ -85,6 +88,7 @@ class RemoteBuildCacheServer(private val cfg: Configuration) {
|
|||||||
|
|
||||||
val userAttribute: AttributeKey<Configuration.User> = AttributeKey.valueOf("user")
|
val userAttribute: AttributeKey<Configuration.User> = AttributeKey.valueOf("user")
|
||||||
val groupAttribute: AttributeKey<Set<Configuration.Group>> = AttributeKey.valueOf("group")
|
val groupAttribute: AttributeKey<Set<Configuration.Group>> = AttributeKey.valueOf("group")
|
||||||
|
val clientIp: AttributeKey<InetSocketAddress> = AttributeKey.valueOf("client-ip")
|
||||||
|
|
||||||
val DEFAULT_CONFIGURATION_URL by lazy { "jpms://net.woggioni.rbcs.server/net/woggioni/rbcs/server/rbcs-default.xml".toUrl() }
|
val DEFAULT_CONFIGURATION_URL by lazy { "jpms://net.woggioni.rbcs.server/net/woggioni/rbcs/server/rbcs-default.xml".toUrl() }
|
||||||
private const val SSL_HANDLER_NAME = "sslHandler"
|
private const val SSL_HANDLER_NAME = "sslHandler"
|
||||||
@@ -234,6 +238,7 @@ class RemoteBuildCacheServer(private val cfg: Configuration) {
|
|||||||
else ClientAuth.OPTIONAL
|
else ClientAuth.OPTIONAL
|
||||||
} ?: ClientAuth.NONE
|
} ?: ClientAuth.NONE
|
||||||
clientAuth(clientAuth)
|
clientAuth(clientAuth)
|
||||||
|
|
||||||
}.build()
|
}.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -259,6 +264,9 @@ class RemoteBuildCacheServer(private val cfg: Configuration) {
|
|||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val proxyProtocolEnabled: Boolean = cfg.isProxyProtocolEnabled
|
||||||
|
private val trustedProxyIPs: List<Cidr> = cfg.trustedProxyIPs
|
||||||
|
|
||||||
private val sslContext: SslContext? = cfg.tls?.let(Companion::createSslCtx)
|
private val sslContext: SslContext? = cfg.tls?.let(Companion::createSslCtx)
|
||||||
|
|
||||||
private fun userExtractor(authentication: Configuration.ClientCertificateAuthentication) =
|
private fun userExtractor(authentication: Configuration.ClientCertificateAuthentication) =
|
||||||
@@ -290,6 +298,7 @@ class RemoteBuildCacheServer(private val cfg: Configuration) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun initChannel(ch: Channel) {
|
override fun initChannel(ch: Channel) {
|
||||||
|
ch.attr(clientIp).set(ch.remoteAddress() as InetSocketAddress)
|
||||||
log.debug {
|
log.debug {
|
||||||
"Created connection ${ch.id().asShortText()} with ${ch.remoteAddress()}"
|
"Created connection ${ch.id().asShortText()} with ${ch.remoteAddress()}"
|
||||||
}
|
}
|
||||||
@@ -338,6 +347,10 @@ class RemoteBuildCacheServer(private val cfg: Configuration) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
if(proxyProtocolEnabled) {
|
||||||
|
pipeline.addLast(HAProxyMessageDecoder())
|
||||||
|
pipeline.addLast(ProxyProtocolHandler(trustedProxyIPs))
|
||||||
|
}
|
||||||
sslContext?.newHandler(ch.alloc())?.also {
|
sslContext?.newHandler(ch.alloc())?.also {
|
||||||
pipeline.addLast(SSL_HANDLER_NAME, it)
|
pipeline.addLast(SSL_HANDLER_NAME, it)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,5 +72,4 @@ abstract class AbstractNettyHttpAuthenticator(private val authorizer: Authorizer
|
|||||||
ReferenceCountUtil.release(msg)
|
ReferenceCountUtil.release(msg)
|
||||||
ctx.writeAndFlush(NOT_AUTHORIZED.retainedDuplicate()).addListener(ChannelFutureListener.CLOSE_ON_FAILURE)
|
ctx.writeAndFlush(NOT_AUTHORIZED.retainedDuplicate()).addListener(ChannelFutureListener.CLOSE_ON_FAILURE)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -2,8 +2,6 @@ package net.woggioni.rbcs.server.cache
|
|||||||
|
|
||||||
import io.netty.buffer.ByteBuf
|
import io.netty.buffer.ByteBuf
|
||||||
import io.netty.channel.ChannelHandlerContext
|
import io.netty.channel.ChannelHandlerContext
|
||||||
import java.io.ByteArrayInputStream
|
|
||||||
import java.io.ByteArrayOutputStream
|
|
||||||
import java.util.zip.Deflater
|
import java.util.zip.Deflater
|
||||||
import java.util.zip.DeflaterOutputStream
|
import java.util.zip.DeflaterOutputStream
|
||||||
import java.util.zip.InflaterOutputStream
|
import java.util.zip.InflaterOutputStream
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import net.woggioni.rbcs.api.Configuration.TrustStore
|
|||||||
import net.woggioni.rbcs.api.Configuration.User
|
import net.woggioni.rbcs.api.Configuration.User
|
||||||
import net.woggioni.rbcs.api.Role
|
import net.woggioni.rbcs.api.Role
|
||||||
import net.woggioni.rbcs.api.exception.ConfigurationException
|
import net.woggioni.rbcs.api.exception.ConfigurationException
|
||||||
|
import net.woggioni.rbcs.common.Cidr
|
||||||
import net.woggioni.rbcs.common.Xml.Companion.asIterable
|
import net.woggioni.rbcs.common.Xml.Companion.asIterable
|
||||||
import net.woggioni.rbcs.common.Xml.Companion.renderAttribute
|
import net.woggioni.rbcs.common.Xml.Companion.renderAttribute
|
||||||
import org.w3c.dom.Document
|
import org.w3c.dom.Document
|
||||||
@@ -38,6 +39,8 @@ object Parser {
|
|||||||
var cache: Cache? = null
|
var cache: Cache? = null
|
||||||
var host = "127.0.0.1"
|
var host = "127.0.0.1"
|
||||||
var port = 11080
|
var port = 11080
|
||||||
|
var proxyProtocolEnabled = false
|
||||||
|
var trustedProxies = emptyList<Cidr>()
|
||||||
var users: Map<String, User> = mapOf(anonymousUser.name to anonymousUser)
|
var users: Map<String, User> = mapOf(anonymousUser.name to anonymousUser)
|
||||||
var groups = emptyMap<String, Group>()
|
var groups = emptyMap<String, Group>()
|
||||||
var tls: Tls? = null
|
var tls: Tls? = null
|
||||||
@@ -98,9 +101,23 @@ object Parser {
|
|||||||
"bind" -> {
|
"bind" -> {
|
||||||
host = child.renderAttribute("host") ?: throw ConfigurationException("host attribute is required")
|
host = child.renderAttribute("host") ?: throw ConfigurationException("host attribute is required")
|
||||||
port = Integer.parseInt(child.renderAttribute("port"))
|
port = Integer.parseInt(child.renderAttribute("port"))
|
||||||
|
proxyProtocolEnabled = child.renderAttribute("proxy-protocol")
|
||||||
|
?.let(String::toBoolean) ?: false
|
||||||
incomingConnectionsBacklogSize = child.renderAttribute("incoming-connections-backlog-size")
|
incomingConnectionsBacklogSize = child.renderAttribute("incoming-connections-backlog-size")
|
||||||
?.let(Integer::parseInt)
|
?.let(Integer::parseInt)
|
||||||
?: 1024
|
?: 1024
|
||||||
|
|
||||||
|
for(grandChild in child.asIterable()) {
|
||||||
|
when(grandChild.localName) {
|
||||||
|
"trusted-proxies" -> {
|
||||||
|
trustedProxies = parseTrustedProxies(grandChild)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
child.asIterable().filter {
|
||||||
|
it.localName == "trusted-proxies"
|
||||||
|
}.firstOrNull()?.let(::parseTrustedProxies)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"cache" -> {
|
"cache" -> {
|
||||||
@@ -195,6 +212,8 @@ object Parser {
|
|||||||
return Configuration.of(
|
return Configuration.of(
|
||||||
host,
|
host,
|
||||||
port,
|
port,
|
||||||
|
proxyProtocolEnabled,
|
||||||
|
trustedProxies,
|
||||||
incomingConnectionsBacklogSize,
|
incomingConnectionsBacklogSize,
|
||||||
serverPath,
|
serverPath,
|
||||||
eventExecutor,
|
eventExecutor,
|
||||||
@@ -217,6 +236,15 @@ object Parser {
|
|||||||
}
|
}
|
||||||
}.toSet()
|
}.toSet()
|
||||||
|
|
||||||
|
private fun parseTrustedProxies(root: Element) = root.asIterable().asSequence().map {
|
||||||
|
when (it.localName) {
|
||||||
|
"allow" -> it.renderAttribute("cidr")
|
||||||
|
?.let(Cidr::from)
|
||||||
|
?: throw ConfigurationException("Missing 'cidr' attribute")
|
||||||
|
else -> throw ConfigurationException("Unrecognized tag '${it.localName}'")
|
||||||
|
}
|
||||||
|
}.toList()
|
||||||
|
|
||||||
private fun parseUserRefs(root: Element) = root.asIterable().asSequence().map {
|
private fun parseUserRefs(root: Element) = root.asIterable().asSequence().map {
|
||||||
when (it.localName) {
|
when (it.localName) {
|
||||||
"user" -> it.renderAttribute("ref")
|
"user" -> it.renderAttribute("ref")
|
||||||
|
|||||||
@@ -33,6 +33,17 @@ object Serializer {
|
|||||||
attr("host", conf.host)
|
attr("host", conf.host)
|
||||||
attr("port", conf.port.toString())
|
attr("port", conf.port.toString())
|
||||||
attr("incoming-connections-backlog-size", conf.incomingConnectionsBacklogSize.toString())
|
attr("incoming-connections-backlog-size", conf.incomingConnectionsBacklogSize.toString())
|
||||||
|
attr("proxy-protocol", conf.isProxyProtocolEnabled.toString())
|
||||||
|
|
||||||
|
if (conf.trustedProxyIPs.isNotEmpty()) {
|
||||||
|
node("trusted-proxies") {
|
||||||
|
for(trustedProxy in conf.trustedProxyIPs) {
|
||||||
|
node("allow") {
|
||||||
|
attr("cidr", trustedProxy.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
node("connection") {
|
node("connection") {
|
||||||
conf.connection.let { connection ->
|
conf.connection.let { connection ->
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package net.woggioni.rbcs.server.handler
|
||||||
|
|
||||||
|
import io.netty.channel.ChannelHandlerContext
|
||||||
|
import io.netty.channel.SimpleChannelInboundHandler
|
||||||
|
import io.netty.handler.codec.haproxy.HAProxyMessage
|
||||||
|
import java.net.InetAddress
|
||||||
|
import java.net.InetSocketAddress
|
||||||
|
import net.woggioni.rbcs.common.Cidr
|
||||||
|
import net.woggioni.rbcs.common.createLogger
|
||||||
|
import net.woggioni.rbcs.common.trace
|
||||||
|
import net.woggioni.rbcs.server.RemoteBuildCacheServer
|
||||||
|
|
||||||
|
|
||||||
|
class ProxyProtocolHandler(private val trustedProxyIPs : List<Cidr>) : SimpleChannelInboundHandler<HAProxyMessage>() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val log = createLogger<ProxyProtocolHandler>()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun channelRead0(
|
||||||
|
ctx: ChannelHandlerContext,
|
||||||
|
msg: HAProxyMessage
|
||||||
|
) {
|
||||||
|
val sourceAddress = ctx.channel().remoteAddress()
|
||||||
|
if (sourceAddress is InetSocketAddress &&
|
||||||
|
trustedProxyIPs.isEmpty() ||
|
||||||
|
trustedProxyIPs.any { it.contains((sourceAddress as InetSocketAddress).address) }) {
|
||||||
|
val proxiedClientAddress = InetSocketAddress(
|
||||||
|
InetAddress.ofLiteral(msg.sourceAddress()),
|
||||||
|
msg.sourcePort()
|
||||||
|
)
|
||||||
|
if(log.isTraceEnabled) {
|
||||||
|
log.trace {
|
||||||
|
"Received proxied request from $sourceAddress forwarded for $proxiedClientAddress"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.channel().attr(RemoteBuildCacheServer.clientIp).set(proxiedClientAddress)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -197,7 +197,8 @@ class ThrottlingHandler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (user == null && groups.isEmpty()) {
|
if (user == null && groups.isEmpty()) {
|
||||||
bucketManager.getBucketByAddress(ctx.channel().remoteAddress() as InetSocketAddress)?.let(buckets::add)
|
val clientAddress = ctx.channel().attr<InetSocketAddress>(RemoteBuildCacheServer.clientIp).get()
|
||||||
|
bucketManager.getBucketByAddress(clientAddress)?.let(buckets::add)
|
||||||
}
|
}
|
||||||
|
|
||||||
var nextAttempt = -1L
|
var nextAttempt = -1L
|
||||||
|
|||||||
@@ -62,6 +62,9 @@
|
|||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
|
|
||||||
<xs:complexType name="bindType">
|
<xs:complexType name="bindType">
|
||||||
|
<xs:sequence minOccurs="0">
|
||||||
|
<xs:element name="trusted-proxies" type="rbcs:trustedProxiesType"/>
|
||||||
|
</xs:sequence>
|
||||||
<xs:attribute name="host" type="xs:token" use="required">
|
<xs:attribute name="host" type="xs:token" use="required">
|
||||||
<xs:annotation>
|
<xs:annotation>
|
||||||
<xs:documentation>Server bind address</xs:documentation>
|
<xs:documentation>Server bind address</xs:documentation>
|
||||||
@@ -72,6 +75,12 @@
|
|||||||
<xs:documentation>Server port number</xs:documentation>
|
<xs:documentation>Server port number</xs:documentation>
|
||||||
</xs:annotation>
|
</xs:annotation>
|
||||||
</xs:attribute>
|
</xs:attribute>
|
||||||
|
<xs:attribute name="proxy-protocol" type="xs:boolean" use="optional">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Enable proxy protocol</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
|
||||||
<xs:attribute name="incoming-connections-backlog-size" type="xs:unsignedInt" use="optional" default="1024">
|
<xs:attribute name="incoming-connections-backlog-size" type="xs:unsignedInt" use="optional" default="1024">
|
||||||
<xs:annotation>
|
<xs:annotation>
|
||||||
<xs:documentation>
|
<xs:documentation>
|
||||||
@@ -83,6 +92,16 @@
|
|||||||
</xs:attribute>
|
</xs:attribute>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
|
|
||||||
|
<xs:complexType name="trustedProxiesType">
|
||||||
|
<xs:sequence minOccurs="0">
|
||||||
|
<xs:element name="allow" type="rbcs:allowType" maxOccurs="unbounded"/>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
|
||||||
|
<xs:complexType name="allowType">
|
||||||
|
<xs:attribute name="cidr" type="rbcs:cidr"/>
|
||||||
|
</xs:complexType>
|
||||||
|
|
||||||
<xs:complexType name="connectionType">
|
<xs:complexType name="connectionType">
|
||||||
<xs:attribute name="idle-timeout" type="xs:duration" use="optional" default="PT30S">
|
<xs:attribute name="idle-timeout" type="xs:duration" use="optional" default="PT30S">
|
||||||
<xs:annotation>
|
<xs:annotation>
|
||||||
@@ -681,4 +700,20 @@
|
|||||||
</xs:restriction>
|
</xs:restriction>
|
||||||
</xs:simpleType>
|
</xs:simpleType>
|
||||||
|
|
||||||
|
<xs:simpleType name="cidrIPv4">
|
||||||
|
<xs:restriction base="xs:string">
|
||||||
|
<xs:pattern value="(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\/(3[0-2]|[12]?[0-9])" />
|
||||||
|
</xs:restriction>
|
||||||
|
</xs:simpleType>
|
||||||
|
|
||||||
|
<xs:simpleType name="cidrIPv6">
|
||||||
|
<xs:restriction base="xs:string">
|
||||||
|
<xs:pattern value="((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(:([0-9A-Fa-f]{1,4}){1,7}|:)))(%[\p{L}\p{N}_-]+)?\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])" />
|
||||||
|
</xs:restriction>
|
||||||
|
</xs:simpleType>
|
||||||
|
|
||||||
|
<xs:simpleType name="cidr">
|
||||||
|
<xs:union memberTypes="rbcs:cidrIPv4 rbcs:cidrIPv6" />
|
||||||
|
</xs:simpleType>
|
||||||
|
|
||||||
</xs:schema>
|
</xs:schema>
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ abstract class AbstractBasicAuthServerTest : AbstractServerTest() {
|
|||||||
cfg = Configuration.of(
|
cfg = Configuration.of(
|
||||||
"127.0.0.1",
|
"127.0.0.1",
|
||||||
getFreePort(),
|
getFreePort(),
|
||||||
|
false,
|
||||||
|
emptyList(),
|
||||||
50,
|
50,
|
||||||
serverPath,
|
serverPath,
|
||||||
Configuration.EventExecutor(false),
|
Configuration.EventExecutor(false),
|
||||||
|
|||||||
@@ -140,6 +140,9 @@ abstract class AbstractTlsServerTest : AbstractServerTest() {
|
|||||||
cfg = Configuration(
|
cfg = Configuration(
|
||||||
"127.0.0.1",
|
"127.0.0.1",
|
||||||
getFreePort(),
|
getFreePort(),
|
||||||
|
false,
|
||||||
|
emptyList(),
|
||||||
|
|
||||||
100,
|
100,
|
||||||
serverPath,
|
serverPath,
|
||||||
Configuration.EventExecutor(false),
|
Configuration.EventExecutor(false),
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ class NoAuthServerTest : AbstractServerTest() {
|
|||||||
cfg = Configuration(
|
cfg = Configuration(
|
||||||
"127.0.0.1",
|
"127.0.0.1",
|
||||||
getFreePort(),
|
getFreePort(),
|
||||||
|
false,
|
||||||
|
emptyList(),
|
||||||
100,
|
100,
|
||||||
serverPath,
|
serverPath,
|
||||||
Configuration.EventExecutor(false),
|
Configuration.EventExecutor(false),
|
||||||
|
|||||||
@@ -2,7 +2,13 @@
|
|||||||
<rbcs:server xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
|
<rbcs:server xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xmlns:rbcs="urn:net.woggioni.rbcs.server"
|
xmlns:rbcs="urn:net.woggioni.rbcs.server"
|
||||||
xs:schemaLocation="urn:net.woggioni.rbcs.server jpms://net.woggioni.rbcs.server/net/woggioni/rbcs/server/schema/rbcs-server.xsd">
|
xs:schemaLocation="urn:net.woggioni.rbcs.server jpms://net.woggioni.rbcs.server/net/woggioni/rbcs/server/schema/rbcs-server.xsd">
|
||||||
<bind host="127.0.0.1" port="11443" incoming-connections-backlog-size="22"/>
|
<bind host="127.0.0.1" port="11443" incoming-connections-backlog-size="22" proxy-protocol="true">
|
||||||
|
<trusted-proxies>
|
||||||
|
<allow cidr="192.168.0.11/32"/>
|
||||||
|
<allow cidr="::1/128"/>
|
||||||
|
<allow cidr="fda7:9b54:5678::2f9/128"/>
|
||||||
|
</trusted-proxies>
|
||||||
|
</bind>
|
||||||
<connection
|
<connection
|
||||||
read-idle-timeout="PT10M"
|
read-idle-timeout="PT10M"
|
||||||
write-idle-timeout="PT11M"
|
write-idle-timeout="PT11M"
|
||||||
|
|||||||
Reference in New Issue
Block a user