Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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 {
Expand All @@ -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() {
}
Expand Down Expand Up @@ -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.
* <p>
* Passing {@code -Dminestom-velocity-secret=<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.
* </p>
* <p>
* 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.
* </p>
*
* @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
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
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;

import java.nio.file.Paths;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;

class ServiceBootstrapTest {

Expand All @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
2 changes: 1 addition & 1 deletion settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
Loading