mirror of
https://github.com/chylex/Minecraft-Phantom-Panel.git
synced 2024-11-25 16:42:54 +01:00
Compare commits
2 Commits
340b236282
...
de76a5ca8c
Author | SHA1 | Date | |
---|---|---|---|
de76a5ca8c | |||
7291495579 |
@ -5,14 +5,13 @@
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/.workdir/Controller" />
|
||||
<option name="PASS_PARENT_ENVS" value="1" />
|
||||
<envs>
|
||||
<env name="ASPNETCORE_ENVIRONMENT" value="Development" />
|
||||
<env name="AGENT_RPC_SERVER_HOST" value="localhost" />
|
||||
<env name="PG_DATABASE" value="postgres" />
|
||||
<env name="PG_HOST" value="localhost" />
|
||||
<env name="PG_PASS" value="development" />
|
||||
<env name="PG_PORT" value="9403" />
|
||||
<env name="PG_USER" value="postgres" />
|
||||
<env name="RPC_SERVER_HOST" value="localhost" />
|
||||
<env name="WEB_SERVER_HOST" value="localhost" />
|
||||
<env name="WEB_RPC_SERVER_HOST" value="localhost" />
|
||||
</envs>
|
||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||
<option name="USE_MONO" value="0" />
|
||||
|
2
.workdir/Controller/secrets/web.key
Normal file
2
.workdir/Controller/secrets/web.key
Normal file
@ -0,0 +1,2 @@
|
||||
±™h?־<>ֹBx
|
||||
<02>
–f-<2D>¢יא<01>“ש"8”כיJ–<4A>Jn/וda
|
1
.workdir/Controller/secrets/web.secret
Normal file
1
.workdir/Controller/secrets/web.secret
Normal file
@ -0,0 +1 @@
|
||||
TΦ./gϋΏNρ°t<C2B0>$Ν!Β(ƒρ#η~ΖΞ}<14><:
|
@ -13,7 +13,7 @@ using Serilog.Events;
|
||||
namespace Phantom.Agent.Rpc;
|
||||
|
||||
public sealed class RpcLauncher : RpcRuntime<ClientSocket> {
|
||||
public static Task Launch(RpcConfiguration config, AgentAuthToken authToken, AgentInfo agentInfo, Func<RpcServerConnection, IMessageToAgentListener> listenerFactory, SemaphoreSlim disconnectSemaphore, CancellationToken receiveCancellationToken) {
|
||||
public static Task Launch(RpcConfiguration config, AuthToken authToken, AgentInfo agentInfo, Func<RpcServerConnection, IMessageToAgentListener> listenerFactory, SemaphoreSlim disconnectSemaphore, CancellationToken receiveCancellationToken) {
|
||||
var socket = new ClientSocket();
|
||||
var options = socket.Options;
|
||||
|
||||
|
@ -10,7 +10,7 @@ namespace Phantom.Agent;
|
||||
static class AgentKey {
|
||||
private static ILogger Logger { get; } = PhantomLogger.Create(nameof(AgentKey));
|
||||
|
||||
public static Task<(NetMQCertificate, AgentAuthToken)?> Load(string? agentKeyToken, string? agentKeyFilePath) {
|
||||
public static Task<(NetMQCertificate, AuthToken)?> Load(string? agentKeyToken, string? agentKeyFilePath) {
|
||||
if (agentKeyFilePath != null) {
|
||||
return LoadFromFile(agentKeyFilePath);
|
||||
}
|
||||
@ -22,7 +22,7 @@ static class AgentKey {
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<(NetMQCertificate, AgentAuthToken)?> LoadFromFile(string agentKeyFilePath) {
|
||||
private static async Task<(NetMQCertificate, AuthToken)?> LoadFromFile(string agentKeyFilePath) {
|
||||
if (!File.Exists(agentKeyFilePath)) {
|
||||
Logger.Fatal("Missing agent key file: {AgentKeyFilePath}", agentKeyFilePath);
|
||||
return null;
|
||||
@ -41,7 +41,7 @@ static class AgentKey {
|
||||
}
|
||||
}
|
||||
|
||||
private static (NetMQCertificate, AgentAuthToken)? LoadFromToken(string agentKey) {
|
||||
private static (NetMQCertificate, AuthToken)? LoadFromToken(string agentKey) {
|
||||
try {
|
||||
return LoadFromBytes(TokenGenerator.DecodeBytes(agentKey));
|
||||
} catch (Exception) {
|
||||
@ -50,8 +50,8 @@ static class AgentKey {
|
||||
}
|
||||
}
|
||||
|
||||
private static (NetMQCertificate, AgentAuthToken)? LoadFromBytes(byte[] agentKey) {
|
||||
var (publicKey, agentToken) = AgentKeyData.FromBytes(agentKey);
|
||||
private static (NetMQCertificate, AuthToken)? LoadFromBytes(byte[] agentKey) {
|
||||
var (publicKey, agentToken) = ConnectionCommonKey.FromBytes(agentKey);
|
||||
var controllerCertificate = NetMQCertificate.FromPublicKey(publicKey);
|
||||
|
||||
Logger.Information("Loaded agent key.");
|
||||
|
@ -1,18 +0,0 @@
|
||||
namespace Phantom.Common.Data.Agent;
|
||||
|
||||
public static class AgentKeyData {
|
||||
private const byte TokenLength = AgentAuthToken.Length;
|
||||
|
||||
public static byte[] ToBytes(byte[] publicKey, AgentAuthToken agentToken) {
|
||||
Span<byte> agentKey = stackalloc byte[TokenLength + publicKey.Length];
|
||||
agentToken.WriteTo(agentKey[..TokenLength]);
|
||||
publicKey.CopyTo(agentKey[TokenLength..]);
|
||||
return agentKey.ToArray();
|
||||
}
|
||||
|
||||
public static (byte[] PublicKey, AgentAuthToken AgentToken) FromBytes(byte[] agentKey) {
|
||||
var token = new AgentAuthToken(agentKey[..TokenLength]);
|
||||
var publicKey = agentKey[TokenLength..];
|
||||
return (publicKey, token);
|
||||
}
|
||||
}
|
@ -6,14 +6,14 @@ namespace Phantom.Common.Data.Agent;
|
||||
|
||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
|
||||
public sealed partial class AgentAuthToken {
|
||||
public sealed partial class AuthToken {
|
||||
internal const int Length = 12;
|
||||
|
||||
[MemoryPackOrder(0)]
|
||||
[MemoryPackInclude]
|
||||
private readonly byte[] bytes;
|
||||
|
||||
internal AgentAuthToken(byte[]? bytes) {
|
||||
internal AuthToken(byte[]? bytes) {
|
||||
if (bytes == null) {
|
||||
throw new ArgumentNullException(nameof(bytes));
|
||||
}
|
||||
@ -25,7 +25,7 @@ public sealed partial class AgentAuthToken {
|
||||
this.bytes = bytes;
|
||||
}
|
||||
|
||||
public bool FixedTimeEquals(AgentAuthToken providedAuthToken) {
|
||||
public bool FixedTimeEquals(AuthToken providedAuthToken) {
|
||||
return CryptographicOperations.FixedTimeEquals(bytes, providedAuthToken.bytes);
|
||||
}
|
||||
|
||||
@ -33,7 +33,7 @@ public sealed partial class AgentAuthToken {
|
||||
bytes.CopyTo(span);
|
||||
}
|
||||
|
||||
public static AgentAuthToken Generate() {
|
||||
return new AgentAuthToken(RandomNumberGenerator.GetBytes(Length));
|
||||
public static AuthToken Generate() {
|
||||
return new AuthToken(RandomNumberGenerator.GetBytes(Length));
|
||||
}
|
||||
}
|
18
Common/Phantom.Common.Data/Agent/ConnectionCommonKey.cs
Normal file
18
Common/Phantom.Common.Data/Agent/ConnectionCommonKey.cs
Normal file
@ -0,0 +1,18 @@
|
||||
namespace Phantom.Common.Data.Agent;
|
||||
|
||||
public readonly record struct ConnectionCommonKey(byte[] CertificatePublicKey, AuthToken AuthToken) {
|
||||
private const byte TokenLength = AuthToken.Length;
|
||||
|
||||
public byte[] ToBytes() {
|
||||
Span<byte> result = stackalloc byte[TokenLength + CertificatePublicKey.Length];
|
||||
AuthToken.WriteTo(result[..TokenLength]);
|
||||
CertificatePublicKey.CopyTo(result[TokenLength..]);
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
public static ConnectionCommonKey FromBytes(byte[] agentKey) {
|
||||
var authToken = new AuthToken(agentKey[..TokenLength]);
|
||||
var certificatePublicKey = agentKey[TokenLength..];
|
||||
return new ConnectionCommonKey(certificatePublicKey, authToken);
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@ namespace Phantom.Common.Messages.ToServer;
|
||||
|
||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||
public sealed partial record RegisterAgentMessage(
|
||||
[property: MemoryPackOrder(0)] AgentAuthToken AuthToken,
|
||||
[property: MemoryPackOrder(0)] AuthToken AuthToken,
|
||||
[property: MemoryPackOrder(1)] AgentInfo AgentInfo
|
||||
) : IMessageToServer {
|
||||
public Task<NoReply> Accept(IMessageToServerListener listener) {
|
||||
|
@ -26,10 +26,10 @@ public sealed class AgentManager {
|
||||
public EventSubscribers<ImmutableArray<Agent>> AgentsChanged => agents.Subs;
|
||||
|
||||
private readonly CancellationToken cancellationToken;
|
||||
private readonly AgentAuthToken authToken;
|
||||
private readonly AuthToken authToken;
|
||||
private readonly IDatabaseProvider databaseProvider;
|
||||
|
||||
public AgentManager(AgentAuthToken authToken, IDatabaseProvider databaseProvider, TaskManager taskManager, CancellationToken cancellationToken) {
|
||||
public AgentManager(AuthToken authToken, IDatabaseProvider databaseProvider, TaskManager taskManager, CancellationToken cancellationToken) {
|
||||
this.authToken = authToken;
|
||||
this.databaseProvider = databaseProvider;
|
||||
this.cancellationToken = cancellationToken;
|
||||
@ -52,7 +52,7 @@ public sealed class AgentManager {
|
||||
return agents.ByGuid.ToImmutable();
|
||||
}
|
||||
|
||||
internal async Task<bool> RegisterAgent(AgentAuthToken authToken, AgentInfo agentInfo, InstanceManager instanceManager, RpcClientConnection connection) {
|
||||
internal async Task<bool> RegisterAgent(AuthToken authToken, AgentInfo agentInfo, InstanceManager instanceManager, RpcClientConnection connection) {
|
||||
if (!this.authToken.FixedTimeEquals(authToken)) {
|
||||
await connection.Send(new RegisterAgentFailureMessage(RegisterAgentFailure.InvalidToken));
|
||||
return false;
|
||||
|
@ -32,7 +32,7 @@ public sealed class ControllerServices {
|
||||
private readonly IDatabaseProvider databaseProvider;
|
||||
private readonly CancellationToken cancellationToken;
|
||||
|
||||
public ControllerServices(IDatabaseProvider databaseProvider, AgentAuthToken agentAuthToken, CancellationToken shutdownCancellationToken) {
|
||||
public ControllerServices(IDatabaseProvider databaseProvider, AuthToken agentAuthToken, CancellationToken shutdownCancellationToken) {
|
||||
this.TaskManager = new TaskManager(PhantomLogger.Create<TaskManager, ControllerServices>());
|
||||
this.MinecraftVersions = new MinecraftVersions();
|
||||
|
||||
|
@ -1,87 +0,0 @@
|
||||
using NetMQ;
|
||||
using Phantom.Common.Data.Agent;
|
||||
using Phantom.Common.Logging;
|
||||
using Phantom.Utils.Cryptography;
|
||||
using Phantom.Utils.IO;
|
||||
using Serilog;
|
||||
|
||||
namespace Phantom.Controller;
|
||||
|
||||
static class CertificateFiles {
|
||||
private static ILogger Logger { get; } = PhantomLogger.Create(nameof(CertificateFiles));
|
||||
|
||||
private const string SecretKeyFileName = "secret.key";
|
||||
private const string AgentKeyFileName = "agent.key";
|
||||
|
||||
public static async Task<(NetMQCertificate, AgentAuthToken)?> CreateOrLoad(string folderPath) {
|
||||
string secretKeyFilePath = Path.Combine(folderPath, SecretKeyFileName);
|
||||
string agentKeyFilePath = Path.Combine(folderPath, AgentKeyFileName);
|
||||
|
||||
bool secretKeyFileExists = File.Exists(secretKeyFilePath);
|
||||
bool agentKeyFileExists = File.Exists(agentKeyFilePath);
|
||||
|
||||
if (secretKeyFileExists && agentKeyFileExists) {
|
||||
try {
|
||||
return await LoadCertificatesFromFiles(secretKeyFilePath, agentKeyFilePath);
|
||||
} catch (IOException e) {
|
||||
Logger.Fatal("Error reading certificate files.");
|
||||
Logger.Fatal(e.Message);
|
||||
return null;
|
||||
} catch (Exception) {
|
||||
Logger.Fatal("Certificate files contain invalid data.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (secretKeyFileExists || agentKeyFileExists) {
|
||||
string existingKeyFilePath = secretKeyFileExists ? secretKeyFilePath : agentKeyFilePath;
|
||||
string missingKeyFileName = secretKeyFileExists ? AgentKeyFileName : SecretKeyFileName;
|
||||
Logger.Fatal("The certificate file {ExistingKeyFilePath} exists but {MissingKeyFileName} does not. Please delete it to regenerate both certificate files.", existingKeyFilePath, missingKeyFileName);
|
||||
return null;
|
||||
}
|
||||
|
||||
Logger.Information("Creating certificate files in: {FolderPath}", folderPath);
|
||||
|
||||
try {
|
||||
return await GenerateCertificateFiles(secretKeyFilePath, agentKeyFilePath);
|
||||
} catch (Exception e) {
|
||||
Logger.Fatal("Error creating certificate files.");
|
||||
Logger.Fatal(e.Message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<(NetMQCertificate, AgentAuthToken)?> LoadCertificatesFromFiles(string secretKeyFilePath, string agentKeyFilePath) {
|
||||
byte[] secretKey = await ReadCertificateFile(secretKeyFilePath);
|
||||
byte[] agentKey = await ReadCertificateFile(agentKeyFilePath);
|
||||
|
||||
var (publicKey, agentToken) = AgentKeyData.FromBytes(agentKey);
|
||||
var certificate = new NetMQCertificate(secretKey, publicKey);
|
||||
|
||||
LogAgentConnectionInfo("Loaded existing certificate files.", agentKeyFilePath, agentKey);
|
||||
return (certificate, agentToken);
|
||||
}
|
||||
|
||||
private static Task<byte[]> ReadCertificateFile(string filePath) {
|
||||
Files.RequireMaximumFileSize(filePath, 64);
|
||||
return File.ReadAllBytesAsync(filePath);
|
||||
}
|
||||
|
||||
private static async Task<(NetMQCertificate, AgentAuthToken)> GenerateCertificateFiles(string secretKeyFilePath, string agentKeyFilePath) {
|
||||
var certificate = new NetMQCertificate();
|
||||
var agentToken = AgentAuthToken.Generate();
|
||||
var agentKey = AgentKeyData.ToBytes(certificate.PublicKey, agentToken);
|
||||
|
||||
await Files.WriteBytesAsync(secretKeyFilePath, certificate.SecretKey, FileMode.Create, Chmod.URW_GR);
|
||||
await Files.WriteBytesAsync(agentKeyFilePath, agentKey, FileMode.Create, Chmod.URW_GR);
|
||||
|
||||
LogAgentConnectionInfo("Created new certificate files.", agentKeyFilePath, agentKey);
|
||||
return (certificate, agentToken);
|
||||
}
|
||||
|
||||
private static void LogAgentConnectionInfo(string message, string agentKeyFilePath, byte[] agentKey) {
|
||||
Logger.Information(message + " Agents will need the agent key to connect.");
|
||||
Logger.Information("Agent key file: {AgentKeyFilePath}", agentKeyFilePath);
|
||||
Logger.Information("Agent key: {AgentKey}", TokenGenerator.EncodeBytes(agentKey));
|
||||
}
|
||||
}
|
6
Controller/Phantom.Controller/ConnectionKeyData.cs
Normal file
6
Controller/Phantom.Controller/ConnectionKeyData.cs
Normal file
@ -0,0 +1,6 @@
|
||||
using NetMQ;
|
||||
using Phantom.Common.Data.Agent;
|
||||
|
||||
namespace Phantom.Controller;
|
||||
|
||||
readonly record struct ConnectionKeyData(NetMQCertificate Certificate, AuthToken AuthToken);
|
113
Controller/Phantom.Controller/ConnectionKeyFiles.cs
Normal file
113
Controller/Phantom.Controller/ConnectionKeyFiles.cs
Normal file
@ -0,0 +1,113 @@
|
||||
using NetMQ;
|
||||
using Phantom.Common.Data.Agent;
|
||||
using Phantom.Common.Logging;
|
||||
using Phantom.Utils.Cryptography;
|
||||
using Phantom.Utils.IO;
|
||||
using Serilog;
|
||||
|
||||
namespace Phantom.Controller;
|
||||
|
||||
abstract class ConnectionKeyFiles {
|
||||
private const string CommonKeyFileExtension = ".key";
|
||||
private const string SecretKeyFileExtension = ".secret";
|
||||
|
||||
private readonly ILogger logger;
|
||||
private readonly string commonKeyFileName;
|
||||
private readonly string secretKeyFileName;
|
||||
|
||||
private ConnectionKeyFiles(ILogger logger, string name) {
|
||||
this.logger = logger;
|
||||
this.commonKeyFileName = name + CommonKeyFileExtension;
|
||||
this.secretKeyFileName = name + SecretKeyFileExtension;
|
||||
}
|
||||
|
||||
public async Task<ConnectionKeyData?> CreateOrLoad(string folderPath) {
|
||||
string commonKeyFilePath = Path.Combine(folderPath, commonKeyFileName);
|
||||
string secretKeyFilePath = Path.Combine(folderPath, secretKeyFileName);
|
||||
|
||||
bool commonKeyFileExists = File.Exists(commonKeyFilePath);
|
||||
bool secretKeyFileExists = File.Exists(secretKeyFilePath);
|
||||
|
||||
if (commonKeyFileExists && secretKeyFileExists) {
|
||||
try {
|
||||
return await ReadKeyFiles(commonKeyFilePath, secretKeyFilePath);
|
||||
} catch (IOException e) {
|
||||
logger.Fatal("Error reading connection key files.");
|
||||
logger.Fatal(e.Message);
|
||||
return null;
|
||||
} catch (Exception) {
|
||||
logger.Fatal("Connection key files contain invalid data.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (commonKeyFileExists || secretKeyFileExists) {
|
||||
string existingKeyFilePath = commonKeyFileExists ? commonKeyFilePath : secretKeyFilePath;
|
||||
string missingKeyFileName = commonKeyFileExists ? secretKeyFileName : commonKeyFileName;
|
||||
logger.Fatal("The connection key file {ExistingKeyFilePath} exists but {MissingKeyFileName} does not. Please delete it to regenerate both files.", existingKeyFilePath, missingKeyFileName);
|
||||
return null;
|
||||
}
|
||||
|
||||
logger.Information("Creating connection key files in: {FolderPath}", folderPath);
|
||||
|
||||
try {
|
||||
return await GenerateKeyFiles(commonKeyFilePath, secretKeyFilePath);
|
||||
} catch (Exception e) {
|
||||
logger.Fatal("Error creating connection key files.");
|
||||
logger.Fatal(e.Message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<ConnectionKeyData?> ReadKeyFiles(string commonKeyFilePath, string secretKeyFilePath) {
|
||||
byte[] commonKeyBytes = await ReadKeyFile(commonKeyFilePath);
|
||||
byte[] secretKeyBytes = await ReadKeyFile(secretKeyFilePath);
|
||||
|
||||
var (publicKey, authToken) = ConnectionCommonKey.FromBytes(commonKeyBytes);
|
||||
var certificate = new NetMQCertificate(secretKeyBytes, publicKey);
|
||||
|
||||
logger.Information("Loaded connection key files.");
|
||||
LogCommonKey(commonKeyFilePath, TokenGenerator.EncodeBytes(commonKeyBytes));
|
||||
|
||||
return new ConnectionKeyData(certificate, authToken);
|
||||
}
|
||||
|
||||
private static Task<byte[]> ReadKeyFile(string filePath) {
|
||||
Files.RequireMaximumFileSize(filePath, 64);
|
||||
return File.ReadAllBytesAsync(filePath);
|
||||
}
|
||||
|
||||
private async Task<ConnectionKeyData> GenerateKeyFiles(string commonKeyFilePath, string secretKeyFilePath) {
|
||||
var certificate = new NetMQCertificate();
|
||||
var authToken = AuthToken.Generate();
|
||||
var commonKey = new ConnectionCommonKey(certificate.PublicKey, authToken).ToBytes();
|
||||
|
||||
await Files.WriteBytesAsync(secretKeyFilePath, certificate.SecretKey, FileMode.Create, Chmod.URW_GR);
|
||||
await Files.WriteBytesAsync(commonKeyFilePath, commonKey, FileMode.Create, Chmod.URW_GR);
|
||||
|
||||
logger.Information("Created new connection key files.");
|
||||
LogCommonKey(commonKeyFilePath, TokenGenerator.EncodeBytes(commonKey));
|
||||
|
||||
return new ConnectionKeyData(certificate, authToken);
|
||||
}
|
||||
|
||||
protected abstract void LogCommonKey(string commonKeyFilePath, string commonKeyEncoded);
|
||||
|
||||
internal sealed class Agent : ConnectionKeyFiles {
|
||||
public Agent() : base(PhantomLogger.Create<ConnectionKeyFiles, Agent>(), "agent") {}
|
||||
|
||||
protected override void LogCommonKey(string commonKeyFilePath, string commonKeyEncoded) {
|
||||
logger.Information("Agent key file: {AgentKeyFilePath}", commonKeyFilePath);
|
||||
logger.Information("Agent key: {AgentKey}", commonKeyEncoded);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class Web : ConnectionKeyFiles {
|
||||
public Web() : base(PhantomLogger.Create<ConnectionKeyFiles, Web>(), "web") {}
|
||||
|
||||
protected override void LogCommonKey(string commonKeyFilePath, string commonKeyEncoded) {
|
||||
logger.Information("Web key file: {WebKeyFilePath}", commonKeyFilePath);
|
||||
logger.Information("Web key: {WebKey}", commonKeyEncoded);
|
||||
}
|
||||
}
|
||||
}
|
@ -33,25 +33,29 @@ try {
|
||||
PhantomLogger.Root.InformationHeading("Initializing Phantom Panel controller...");
|
||||
PhantomLogger.Root.Information("Controller version: {Version}", fullVersion);
|
||||
|
||||
var (rpcServerHost, rpcServerPort, sqlConnectionString) = Variables.LoadOrStop();
|
||||
var (agentRpcServerHost, agentRpcServerPort, webRpcServerHost, webRpcServerPort, sqlConnectionString) = Variables.LoadOrStop();
|
||||
|
||||
string secretsPath = Path.GetFullPath("./secrets");
|
||||
CreateFolderOrStop(secretsPath, Chmod.URWX_GRX);
|
||||
|
||||
var certificateData = await CertificateFiles.CreateOrLoad(secretsPath);
|
||||
if (certificateData == null) {
|
||||
var agentKeyDataResult = await new ConnectionKeyFiles.Agent().CreateOrLoad(secretsPath);
|
||||
if (agentKeyDataResult is not {} agentKeyData) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
var webKeyDataResult = await new ConnectionKeyFiles.Web().CreateOrLoad(secretsPath);
|
||||
if (webKeyDataResult is not {} webKeyData) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
var (certificate, agentAuthToken) = certificateData.Value;
|
||||
var dbContextFactory = new ApplicationDbContextFactory(sqlConnectionString);
|
||||
var controllerServices = new ControllerServices(dbContextFactory, agentAuthToken, shutdownCancellationToken);
|
||||
var controllerServices = new ControllerServices(dbContextFactory, agentKeyData.AuthToken, shutdownCancellationToken);
|
||||
|
||||
PhantomLogger.Root.InformationHeading("Launching Phantom Panel server...");
|
||||
|
||||
await controllerServices.Initialize();
|
||||
|
||||
var rpcConfiguration = new RpcConfiguration(PhantomLogger.Create("Rpc"), PhantomLogger.Create<TaskManager>("Rpc"), rpcServerHost, rpcServerPort, certificate);
|
||||
var rpcConfiguration = new RpcConfiguration(PhantomLogger.Create("Rpc", "Agent"), PhantomLogger.Create<TaskManager>("Rpc"), agentRpcServerHost, agentRpcServerPort, agentKeyData.Certificate);
|
||||
var rpcTask = RpcLauncher.Launch(rpcConfiguration, controllerServices.CreateMessageToServerListener, shutdownCancellationToken);
|
||||
try {
|
||||
await rpcTask.WaitAsync(shutdownCancellationToken);
|
||||
|
@ -5,8 +5,10 @@ using Phantom.Utils.Runtime;
|
||||
namespace Phantom.Controller;
|
||||
|
||||
sealed record Variables(
|
||||
string RpcServerHost,
|
||||
ushort RpcServerPort,
|
||||
string AgentRpcServerHost,
|
||||
ushort AgentRpcServerPort,
|
||||
string WebRpcServerHost,
|
||||
ushort WebRpcServerPort,
|
||||
string SqlConnectionString
|
||||
) {
|
||||
private static Variables LoadOrThrow() {
|
||||
@ -19,8 +21,10 @@ sealed record Variables(
|
||||
};
|
||||
|
||||
return new Variables(
|
||||
EnvironmentVariables.GetString("RPC_SERVER_HOST").WithDefault("0.0.0.0"),
|
||||
EnvironmentVariables.GetPortNumber("RPC_SERVER_PORT").WithDefault(9401),
|
||||
EnvironmentVariables.GetString("AGENT_RPC_SERVER_HOST").WithDefault("0.0.0.0"),
|
||||
EnvironmentVariables.GetPortNumber("AGENT_RPC_SERVER_PORT").WithDefault(9401),
|
||||
EnvironmentVariables.GetString("WEB_RPC_SERVER_HOST").WithDefault("0.0.0.0"),
|
||||
EnvironmentVariables.GetPortNumber("WEB_RPC_SERVER_PORT").WithDefault(9402),
|
||||
connectionStringBuilder.ToString()
|
||||
);
|
||||
}
|
||||
|
12
README.md
12
README.md
@ -44,13 +44,19 @@ The configuration for these is set via environment variables.
|
||||
|
||||
### Agent & Web Keys
|
||||
|
||||
When the Controller starts for the first time, it will generate an **Agent Key** and **Web Key**. These contain encryption certificates and authorization tokens, which are needed for the Agents and Web to connect to the Controller.
|
||||
When the Controller starts for the first time, it will generate two pairs of key files. Each pair consists of a **common** and a **secret** key file. One pair is generated for **Agents**, and one for the **Web**.
|
||||
|
||||
Each key has two forms:
|
||||
The **common keys** contain encryption certificates and authorization tokens, which are needed to connect to the Controller. Both the Controller and the connecting Agent or Web must have access to the appropriate **common key**.
|
||||
|
||||
* A binary file stored in `/data/secrets/agent.key` or `/data/secrets/web.key` that can be distributed to the other services.
|
||||
The **secret keys** contain information the Controller needs to establish an encrypted communication channel. These files should only be accessible by the Controller itself.
|
||||
|
||||
The **common keys** have two forms:
|
||||
|
||||
* A binary file `/data/secrets/agent.key` or `/data/secrets/web.key` that can be distributed to the other services.
|
||||
* A plaintext-encoded version printed into the logs on every startup, that can be passed to the other services in an environment variable.
|
||||
|
||||
The **secret keys** are stored as binary files `/data/secrets/agent.secret` and `/data/secrets/web.secret`.
|
||||
|
||||
### Storage
|
||||
|
||||
Use volumes to persist the whole `/data` folder.
|
||||
|
Loading…
Reference in New Issue
Block a user