From a7668aff872c359182cd0320eec4c2fe711a3a09 Mon Sep 17 00:00:00 2001 From: TheMeinerLP Date: Wed, 26 Aug 2026 23:28:10 +0200 Subject: [PATCH] feat: enable Velocity modern forwarding via -Dminestom-velocity-secret Minestom binds the Auth to the ServerProcess in MinecraftServer.init(Auth) and offers no way to swap it afterwards, and ExtensionBootstrap.init() used to hardcode Auth.Offline - so neither :game nor :setup could ever run behind a Velocity proxy. minestom-extensions 2.2.0 adds the init(Auth) overload this needs. ServiceBootstrap.resolveAuth() reads -Dminestom-velocity-secret, the forwarding.secret of the proxy in front of the service. Absent or blank, the server keeps authenticating in offline mode: an empty value is a start script whose variable did not expand, not a deliberate choice, and Minestom would reject the empty key with an exception naming neither the property nor where it came from. The secret itself is never logged. --- .../common/bootstrap/ServiceBootstrap.java | 42 +++++++++++++++++-- .../bootstrap/ServiceBootstrapTest.java | 39 +++++++++++++++++ .../onelitefeather/cygnus/CygnusLoader.java | 5 ++- settings.gradle.kts | 2 +- .../cygnus/setup/SetupLoader.java | 5 ++- 5 files changed, 85 insertions(+), 8 deletions(-) diff --git a/common/src/main/java/net/onelitefeather/cygnus/common/bootstrap/ServiceBootstrap.java b/common/src/main/java/net/onelitefeather/cygnus/common/bootstrap/ServiceBootstrap.java index 822b3f60..33adbb0c 100644 --- a/common/src/main/java/net/onelitefeather/cygnus/common/bootstrap/ServiceBootstrap.java +++ b/common/src/main/java/net/onelitefeather/cygnus/common/bootstrap/ServiceBootstrap.java @@ -1,5 +1,6 @@ package net.onelitefeather.cygnus.common.bootstrap; +import net.minestom.server.Auth; import net.minestom.server.MinecraftServer; import net.minestom.server.command.CommandManager; import org.slf4j.Logger; @@ -14,11 +15,11 @@ /** * Wires the parts a CloudNet-managed service process needs: reading the bind address CloudNet - * assigns per-service, and reacting to CloudNet's stdin-based stop signal instead of being killed - * after a timeout. + * assigns per-service, resolving how incoming connections are authenticated, and reacting to + * CloudNet's stdin-based stop signal instead of being killed after a timeout. * * @author TheMeinerLP - * @version 1.0.0 + * @version 1.1.0 * @since 2.6.7 **/ public final class ServiceBootstrap { @@ -27,6 +28,7 @@ public final class ServiceBootstrap { private static final String DEFAULT_BIND_HOST = "localhost"; private static final int DEFAULT_BIND_PORT = 25565; private static final String DEFAULT_WORKING_DIR = ""; + static final String VELOCITY_SECRET_PROPERTY = "minestom-velocity-secret"; private ServiceBootstrap() { } @@ -61,6 +63,40 @@ public static Path resolveWorkingDirectory() { return Paths.get(System.getProperty("service.working.dir", DEFAULT_WORKING_DIR)); } + /** + * Resolves how incoming connections are authenticated. + *

+ * Passing {@code -Dminestom-velocity-secret=} puts the server behind a Velocity proxy: + * the secret is the {@code forwarding.secret} of that proxy, and players are then expected to + * arrive through it rather than connect directly. Without the property the server keeps + * authenticating in offline mode, which is what a standalone run needs. + *

+ *

+ * The result has to reach {@code MinecraftServer.init(Auth)} - Minestom binds the {@link Auth} + * to the server process at that point and offers no way to switch it on afterwards. + *

+ * + * @return {@link Auth.Velocity} carrying the configured secret, or {@link Auth.Offline} if the + * property is absent or blank + */ + public static Auth resolveAuth() { + String secret = System.getProperty(VELOCITY_SECRET_PROPERTY); + if (secret == null) { + return new Auth.Offline(); + } + secret = secret.trim(); + if (secret.isEmpty()) { + // An empty secret is never a deliberate choice - it is a start script whose variable did + // not expand. Refusing it beats handing Minestom a key it rejects with an exception that + // says nothing about where the value came from. + LOGGER.warn("{} is set but empty - falling back to offline mode authentication", + VELOCITY_SECRET_PROPERTY); + return new Auth.Offline(); + } + LOGGER.info("Velocity modern forwarding enabled - authenticating players through the proxy"); + return new Auth.Velocity(secret); + } + /** * Registers the {@link StopCommand} and starts a daemon thread reading commands from stdin, * so CloudNet can stop the service cleanly instead of killing it after a timeout. Should be diff --git a/common/src/test/java/net/onelitefeather/cygnus/common/bootstrap/ServiceBootstrapTest.java b/common/src/test/java/net/onelitefeather/cygnus/common/bootstrap/ServiceBootstrapTest.java index 2fc7be1c..68253e5b 100644 --- a/common/src/test/java/net/onelitefeather/cygnus/common/bootstrap/ServiceBootstrapTest.java +++ b/common/src/test/java/net/onelitefeather/cygnus/common/bootstrap/ServiceBootstrapTest.java @@ -1,5 +1,6 @@ package net.onelitefeather.cygnus.common.bootstrap; +import net.minestom.server.Auth; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledIfSystemProperty; @@ -7,6 +8,7 @@ import java.nio.file.Paths; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; class ServiceBootstrapTest { @@ -16,6 +18,43 @@ void clearSystemProperties() { System.clearProperty("service.bind.port"); } + @AfterEach + void clearVelocitySecretProperty() { + System.clearProperty(ServiceBootstrap.VELOCITY_SECRET_PROPERTY); + } + + @Test + @DisabledIfSystemProperty(named = ServiceBootstrap.VELOCITY_SECRET_PROPERTY, matches = ".+") + void testAuthDefaultsToOfflineWithoutVelocitySecret() { + assertInstanceOf(Auth.Offline.class, ServiceBootstrap.resolveAuth()); + } + + @Test + void testAuthUsesVelocityWhenSecretIsSet() { + System.setProperty(ServiceBootstrap.VELOCITY_SECRET_PROPERTY, "a-velocity-secret"); + + Auth auth = ServiceBootstrap.resolveAuth(); + + assertInstanceOf(Auth.Velocity.class, auth); + assertEquals(new Auth.Velocity("a-velocity-secret"), auth); + } + + @Test + void testAuthTrimsVelocitySecret() { + System.setProperty(ServiceBootstrap.VELOCITY_SECRET_PROPERTY, " a-velocity-secret\n"); + + assertEquals(new Auth.Velocity("a-velocity-secret"), ServiceBootstrap.resolveAuth()); + } + + @Test + void testAuthFallsBackToOfflineOnBlankVelocitySecret() { + // A start script whose $VELOCITY_SECRET never expanded - Auth.Velocity would reject the + // empty key with an exception that says nothing about the property it came from. + System.setProperty(ServiceBootstrap.VELOCITY_SECRET_PROPERTY, " "); + + assertInstanceOf(Auth.Offline.class, ServiceBootstrap.resolveAuth()); + } + @Test @DisabledIfSystemProperty(named = "service.bind.host", matches = ".+") void testDefaultBindHost() { diff --git a/game/src/main/java/net/onelitefeather/cygnus/CygnusLoader.java b/game/src/main/java/net/onelitefeather/cygnus/CygnusLoader.java index cf2e01c8..174c3521 100644 --- a/game/src/main/java/net/onelitefeather/cygnus/CygnusLoader.java +++ b/game/src/main/java/net/onelitefeather/cygnus/CygnusLoader.java @@ -11,8 +11,9 @@ public final class CygnusLoader { public static void main(String[] args) { // minestom-extensions loads platform extensions - the CloudNet bridge and our // :bridge permission extension among them - from the extensions/ folder. Running - // standalone simply loads none. This also performs MinecraftServer.init(). - ExtensionBootstrap bootstrap = ExtensionBootstrap.init(); + // standalone simply loads none. This also performs MinecraftServer.init(Auth), which is the + // only point at which Velocity forwarding can still be turned on. + ExtensionBootstrap bootstrap = ExtensionBootstrap.init(ServiceBootstrap.resolveAuth()); LuckPermsSupport.bootstrap(); BlockHandlers.registerAll(); String customDimensions = System.getProperty("cygnus.customDimension", "false"); diff --git a/settings.gradle.kts b/settings.gradle.kts index 6bcd1c3c..4d010be5 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -46,7 +46,7 @@ dependencyResolutionManagement { version("luckperms-minestom-loader", "5.6-SNAPSHOT") version("guava", "33.7.1-jre") version("falco", "2.1.0") - version("minestom-extensions", "2.1.1") + version("minestom-extensions", "2.2.0") library("aonyx.bom", "net.onelitefeather", "aonyx-bom").versionRef("aonyx") library("slf4j.api", "org.slf4j", "slf4j-api").versionRef("slf4j") diff --git a/setup/src/main/java/net/onelitefeather/cygnus/setup/SetupLoader.java b/setup/src/main/java/net/onelitefeather/cygnus/setup/SetupLoader.java index 8fa941fb..eabefee3 100644 --- a/setup/src/main/java/net/onelitefeather/cygnus/setup/SetupLoader.java +++ b/setup/src/main/java/net/onelitefeather/cygnus/setup/SetupLoader.java @@ -11,8 +11,9 @@ public class SetupLoader { static void main() { // minestom-extensions loads platform extensions - the CloudNet bridge and our // :bridge permission extension among them - from the extensions/ folder. Running - // standalone simply loads none. This also performs MinecraftServer.init(). - ExtensionBootstrap bootstrap = ExtensionBootstrap.init(); + // standalone simply loads none. This also performs MinecraftServer.init(Auth), which is the + // only point at which Velocity forwarding can still be turned on. + ExtensionBootstrap bootstrap = ExtensionBootstrap.init(ServiceBootstrap.resolveAuth()); LuckPermsSupport.bootstrap(); new SetupExtension(); MinecraftServer.getConnectionManager().setPlayerProvider(new SetupPlayerProvider());