Added authentication for the node against the api server. Cleaned up routes

This commit is contained in:
2025-03-01 17:32:43 +01:00
parent 6d61e026c1
commit ef7f866ded
15 changed files with 678 additions and 260 deletions

View File

@@ -24,11 +24,7 @@ public partial class Server
// Fetching remote configuration
var remoteService = ServiceProvider.GetRequiredService<RemoteService>();
using var remoteHttpClient = await remoteService.CreateHttpClient();
var installData =
await remoteHttpClient.GetJson<ServerInstallDataResponse>(
$"api/servers/remote/servers/{Configuration.Id}/install");
var installData = await remoteService.GetServerInstallation(Configuration.Id);
var dockerImageService = ServiceProvider.GetRequiredService<DockerImageService>();

View File

@@ -22,6 +22,7 @@ public class AppConfiguration
public class SecurityData
{
public string Token { get; set; }
public string TokenId { get; set; }
}
public class StorageData

View File

@@ -1,215 +0,0 @@
using Docker.DotNet.Models;
using Mono.Unix.Native;
using MoonCore.Helpers;
using MoonlightServers.Daemon.Configuration;
using MoonlightServers.Daemon.Models.Cache;
namespace MoonlightServers.Daemon.Helpers;
public static class ServerConfigurationHelper
{
public static void ApplyRuntimeOptions(CreateContainerParameters parameters, ServerConfiguration configuration, AppConfiguration appConfiguration)
{
ApplySharedOptions(parameters, configuration);
// -- Cap drops
parameters.HostConfig.CapDrop = new List<string>()
{
"setpcap", "mknod", "audit_write", "net_raw", "dac_override",
"fowner", "fsetid", "net_bind_service", "sys_chroot", "setfcap"
};
// -- More security options
parameters.HostConfig.ReadonlyRootfs = true;
parameters.HostConfig.SecurityOpt = new List<string>()
{
"no-new-privileges"
};
// - Name
var name = $"moonlight-runtime-{configuration.Id}";
parameters.Name = name;
parameters.Hostname = name;
// - Image
parameters.Image = configuration.DockerImage;
// - Env
parameters.Env = ConstructEnv(configuration)
.Select(x => $"{x.Key}={x.Value}")
.ToList();
// -- Working directory
parameters.WorkingDir = "/home/container";
// - User
var userId = Syscall.getuid();
if (userId == 0)
{
// We are running as root, so we need to run the container as another user and chown the files when we make changes
parameters.User = $"998:998";
}
else
{
// We are not running as root, so we start the container as the same user,
// as we are not able to chown the container content to a different user
parameters.User = $"{userId}:{userId}";
}
// -- Mounts
parameters.HostConfig.Mounts = new List<Mount>();
parameters.HostConfig.Mounts.Add(new()
{
Source = GetRuntimeVolume(configuration, appConfiguration),
Target = "/home/container",
ReadOnly = false,
Type = "bind"
});
// -- Ports
//var config = configService.Get();
if (true) // TODO: Add network toggle
{
parameters.ExposedPorts = new Dictionary<string, EmptyStruct>();
parameters.HostConfig.PortBindings = new Dictionary<string, IList<PortBinding>>();
foreach (var allocation in configuration.Allocations)
{
parameters.ExposedPorts.Add($"{allocation.Port}/tcp", new());
parameters.ExposedPorts.Add($"{allocation.Port}/udp", new());
parameters.HostConfig.PortBindings.Add($"{allocation.Port}/tcp", new List<PortBinding>
{
new()
{
HostPort = allocation.Port.ToString(),
HostIP = allocation.IpAddress
}
});
parameters.HostConfig.PortBindings.Add($"{allocation.Port}/udp", new List<PortBinding>
{
new()
{
HostPort = allocation.Port.ToString(),
HostIP = allocation.IpAddress
}
});
}
}
}
public static void ApplySharedOptions(CreateContainerParameters parameters, ServerConfiguration configuration)
{
// - Input, output & error streams and tty
parameters.Tty = true;
parameters.AttachStderr = true;
parameters.AttachStdin = true;
parameters.AttachStdout = true;
parameters.OpenStdin = true;
// - Host config
parameters.HostConfig = new HostConfig();
// -- CPU limits
parameters.HostConfig.CPUQuota = configuration.Cpu * 1000;
parameters.HostConfig.CPUPeriod = 100000;
parameters.HostConfig.CPUShares = 1024;
// -- Memory and swap limits
var memoryLimit = configuration.Memory;
// The overhead multiplier gives the container a little bit more memory to prevent crashes
var memoryOverhead = memoryLimit + (memoryLimit * 0.05f); // TODO: Config
long swapLimit = -1;
/*
// If swap is enabled globally and not disabled on this server, set swap
if (!configuration.Limits.DisableSwap && config.Server.EnableSwap)
swapLimit = (long)(memoryOverhead + memoryOverhead * config.Server.SwapMultiplier);
co
*/
// Finalize limits by converting and updating the host config
parameters.HostConfig.Memory = ByteConverter.FromMegaBytes((long)memoryOverhead, 1000).Bytes;
parameters.HostConfig.MemoryReservation = ByteConverter.FromMegaBytes(memoryLimit, 1000).Bytes;
parameters.HostConfig.MemorySwap = swapLimit == -1 ? swapLimit : ByteConverter.FromMegaBytes(swapLimit, 1000).Bytes;
// -- Other limits
parameters.HostConfig.BlkioWeight = 100;
//container.HostConfig.PidsLimit = configuration.Limits.PidsLimit;
parameters.HostConfig.OomKillDisable = true; //!configuration.Limits.EnableOomKill;
// -- DNS
parameters.HostConfig.DNS = /*config.Docker.DnsServers.Any() ? config.Docker.DnsServers :*/ new List<string>()
{
"1.1.1.1",
"9.9.9.9"
};
// -- Tmpfs
parameters.HostConfig.Tmpfs = new Dictionary<string, string>()
{
{ "/tmp", $"rw,exec,nosuid,size=100M" } // TODO: Config
};
// -- Logging
parameters.HostConfig.LogConfig = new()
{
Type = "json-file", // We need to use this provider, as the GetLogs endpoint needs it
Config = new Dictionary<string, string>()
};
// - Labels
parameters.Labels = new Dictionary<string, string>();
parameters.Labels.Add("Software", "Moonlight-Panel");
parameters.Labels.Add("ServerId", configuration.Id.ToString());
}
public static Dictionary<string, string> ConstructEnv(ServerConfiguration configuration)
{
var result = new Dictionary<string, string>();
// Default environment variables
//TODO: Add timezone, add server ip
result.Add("STARTUP", configuration.StartupCommand);
result.Add("SERVER_MEMORY", configuration.Memory.ToString());
if (configuration.Allocations.Length > 0)
{
var mainAllocation = configuration.Allocations.First();
result.Add("SERVER_IP", mainAllocation.IpAddress);
result.Add("SERVER_PORT", mainAllocation.Port.ToString());
}
// Handle additional allocation variables
var i = 1;
foreach (var additionalAllocation in configuration.Allocations)
{
result.Add($"ML_PORT_{i}", additionalAllocation.Port.ToString());
i++;
}
// Copy variables as env vars
foreach (var variable in configuration.Variables)
result.Add(variable.Key, variable.Value);
return result;
}
public static string GetRuntimeVolume(ServerConfiguration configuration, AppConfiguration appConfiguration)
{
var localPath = PathBuilder.Dir(appConfiguration.Storage.Volumes, configuration.Id.ToString());
var absolutePath = Path.GetFullPath(localPath);
return absolutePath;
}
}

View File

@@ -1,40 +1,87 @@
using System.IdentityModel.Tokens.Jwt;
using System.Text;
using Microsoft.IdentityModel.Tokens;
using MoonCore.Attributes;
using MoonCore.Helpers;
using MoonCore.Models;
using MoonlightServers.Daemon.Configuration;
using MoonlightServers.DaemonShared.PanelSide.Http.Responses;
namespace MoonlightServers.Daemon.Services;
[Singleton]
public class RemoteService
{
private readonly AppConfiguration Configuration;
private readonly HttpApiClient ApiClient;
public RemoteService(AppConfiguration configuration)
{
Configuration = configuration;
}
public Task<HttpApiClient> CreateHttpClient()
{
var formattedUrl = Configuration.Remote.Url.EndsWith('/')
? Configuration.Remote.Url
: Configuration.Remote.Url + "/";
var httpClient = new HttpClient()
{
BaseAddress = new Uri(formattedUrl)
};
httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {Configuration.Security.Token}");
var apiClient = new HttpApiClient(httpClient);
return Task.FromResult(apiClient);
ApiClient = CreateHttpClient(configuration);
}
public async Task GetStatus()
{
using var apiClient = await CreateHttpClient();
await apiClient.Get("api/servers/remote/node/trip");
await ApiClient.Get("api/remote/servers/node/trip");
}
public async Task<PagedData<ServerDataResponse>> GetServers(int page, int perPage)
{
return await ApiClient.GetJson<PagedData<ServerDataResponse>>(
$"api/remote/servers?page={page}&pageSize={perPage}"
);
}
public async Task<ServerInstallDataResponse> GetServerInstallation(int serverId)
{
return await ApiClient.GetJson<ServerInstallDataResponse>(
$"api/remote/servers/{serverId}/install"
);
}
#region Helpers
private HttpApiClient CreateHttpClient(AppConfiguration configuration)
{
var formattedUrl = configuration.Remote.Url.EndsWith('/')
? configuration.Remote.Url
: configuration.Remote.Url + "/";
var httpClient = new HttpClient()
{
BaseAddress = new Uri(formattedUrl)
};
var jwt = GenerateJwt(configuration);
httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {jwt}");
return new HttpApiClient(httpClient);
}
private string GenerateJwt(AppConfiguration configuration)
{
var jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
var securityTokenDesc = new SecurityTokenDescriptor()
{
Expires = DateTime.UtcNow.AddYears(1), // TODO: Document somewhere
IssuedAt = DateTime.UtcNow,
Issuer = configuration.Security.TokenId,
Audience = configuration.Remote.Url,
NotBefore = DateTime.UtcNow.AddSeconds(-1),
SigningCredentials = new SigningCredentials(
new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(configuration.Security.Token)
),
SecurityAlgorithms.HmacSha256
)
};
var securityToken = jwtSecurityTokenHandler.CreateJwtSecurityToken(securityTokenDesc);
securityToken.Header.Add("kid", configuration.Security.TokenId);
return jwtSecurityTokenHandler.WriteToken(securityToken);
}
#endregion
}

View File

@@ -40,12 +40,9 @@ public class ServerService : IHostedLifecycleService
// Loading models and converting them
Logger.LogInformation("Fetching servers from panel");
using var apiClient = await RemoteService.CreateHttpClient();
var servers = await PagedData<ServerDataResponse>.All(async (page, pageSize) =>
await apiClient.GetJson<PagedData<ServerDataResponse>>(
$"api/servers/remote/servers?page={page}&pageSize={pageSize}"
)
await RemoteService.GetServers(page, pageSize)
);
var configurations = servers.Select(x => new ServerConfiguration()