Store node daemon tokens in an encrypted manner
This commit is contained in:
parent
2ac82af25a
commit
7557dddf49
26 changed files with 222 additions and 827 deletions
|
@ -148,8 +148,8 @@ class NodeViewController extends Controller
|
||||||
public function servers(Request $request, Node $node)
|
public function servers(Request $request, Node $node)
|
||||||
{
|
{
|
||||||
$this->plainInject([
|
$this->plainInject([
|
||||||
'node' => Collection::wrap($node->makeVisible('daemonSecret'))
|
'node' => Collection::wrap($node->makeVisible(['daemon_token_id', 'daemon_token']))
|
||||||
->only(['scheme', 'fqdn', 'daemonListen', 'daemonSecret']),
|
->only(['scheme', 'fqdn', 'daemonListen', 'daemon_token_id', 'daemon_token']),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return $this->view->make('admin.nodes.view.servers', [
|
return $this->view->make('admin.nodes.view.servers', [
|
||||||
|
|
|
@ -67,7 +67,7 @@ class StatisticsController extends Controller
|
||||||
|
|
||||||
$tokens = [];
|
$tokens = [];
|
||||||
foreach ($nodes as $node) {
|
foreach ($nodes as $node) {
|
||||||
$tokens[$node->id] = $node->daemonSecret;
|
$tokens[$node->id] = decrypt($node->daemon_token);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->injectJavascript([
|
$this->injectJavascript([
|
||||||
|
|
|
@ -145,7 +145,7 @@ class ServerTransferController extends Controller
|
||||||
->canOnlyBeUsedAfter($now->getTimestamp())
|
->canOnlyBeUsedAfter($now->getTimestamp())
|
||||||
->expiresAt($now->addMinutes(15)->getTimestamp())
|
->expiresAt($now->addMinutes(15)->getTimestamp())
|
||||||
->relatedTo($server->uuid, true)
|
->relatedTo($server->uuid, true)
|
||||||
->getToken($signer, new Key($server->node->daemonSecret));
|
->getToken($signer, new Key($server->node->getDecryptedKey()));
|
||||||
|
|
||||||
// On the daemon transfer repository, make sure to set the node after the server
|
// On the daemon transfer repository, make sure to set the node after the server
|
||||||
// because setServer() tells the repository to use the server's node and not the one
|
// because setServer() tells the repository to use the server's node and not the one
|
||||||
|
|
|
@ -1,107 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Http\Controllers\Daemon;
|
|
||||||
|
|
||||||
use Cache;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Pterodactyl\Models\Node;
|
|
||||||
use Illuminate\Http\Response;
|
|
||||||
use Illuminate\Http\JsonResponse;
|
|
||||||
use Pterodactyl\Http\Controllers\Controller;
|
|
||||||
use Pterodactyl\Repositories\Eloquent\ServerRepository;
|
|
||||||
use Pterodactyl\Events\Server\Installed as ServerInstalled;
|
|
||||||
use Illuminate\Contracts\Events\Dispatcher as EventDispatcher;
|
|
||||||
use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
|
|
||||||
|
|
||||||
class ActionController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var \Illuminate\Contracts\Events\Dispatcher
|
|
||||||
*/
|
|
||||||
private $eventDispatcher;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var \Pterodactyl\Repositories\Eloquent\ServerRepository
|
|
||||||
*/
|
|
||||||
private $repository;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ActionController constructor.
|
|
||||||
*
|
|
||||||
* @param \Pterodactyl\Repositories\Eloquent\ServerRepository $repository
|
|
||||||
* @param \Illuminate\Contracts\Events\Dispatcher $eventDispatcher
|
|
||||||
*/
|
|
||||||
public function __construct(ServerRepository $repository, EventDispatcher $eventDispatcher)
|
|
||||||
{
|
|
||||||
$this->eventDispatcher = $eventDispatcher;
|
|
||||||
$this->repository = $repository;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles install toggle request from daemon.
|
|
||||||
*
|
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
* @return \Illuminate\Http\JsonResponse
|
|
||||||
*
|
|
||||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
|
||||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
|
||||||
*/
|
|
||||||
public function markInstall(Request $request): JsonResponse
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
/** @var \Pterodactyl\Models\Server $server */
|
|
||||||
$server = $this->repository->findFirstWhere([
|
|
||||||
'uuid' => $request->input('server'),
|
|
||||||
]);
|
|
||||||
} catch (RecordNotFoundException $exception) {
|
|
||||||
return JsonResponse::create([
|
|
||||||
'error' => 'No server by that ID was found on the system.',
|
|
||||||
], Response::HTTP_UNPROCESSABLE_ENTITY);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! $server->relationLoaded('node')) {
|
|
||||||
$server->load('node');
|
|
||||||
}
|
|
||||||
|
|
||||||
$hmac = $request->input('signed');
|
|
||||||
$status = $request->input('installed');
|
|
||||||
|
|
||||||
if (! hash_equals(base64_decode($hmac), hash_hmac('sha256', $server->uuid, $server->getRelation('node')->daemonSecret, true))) {
|
|
||||||
return JsonResponse::create([
|
|
||||||
'error' => 'Signed HMAC was invalid.',
|
|
||||||
], Response::HTTP_FORBIDDEN);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->repository->update($server->id, [
|
|
||||||
'installed' => ($status === 'installed') ? 1 : 2,
|
|
||||||
], true, true);
|
|
||||||
|
|
||||||
// Only fire event if server installed successfully.
|
|
||||||
if ($status === 'installed') {
|
|
||||||
$this->eventDispatcher->dispatch(new ServerInstalled($server));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't use a 204 here, the daemon is hard-checking for a 200 code.
|
|
||||||
return JsonResponse::create([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles configuration data request from daemon.
|
|
||||||
*
|
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
* @param string $token
|
|
||||||
* @return \Illuminate\Http\JsonResponse|\Illuminate\Http\Response
|
|
||||||
*/
|
|
||||||
public function configuration(Request $request, $token)
|
|
||||||
{
|
|
||||||
$nodeId = Cache::pull('Node:Configuration:' . $token);
|
|
||||||
if (is_null($nodeId)) {
|
|
||||||
return response()->json(['error' => 'token_invalid'], 403);
|
|
||||||
}
|
|
||||||
|
|
||||||
$node = Node::findOrFail($nodeId);
|
|
||||||
|
|
||||||
// Manually as getConfigurationAsJson() returns it in correct format already
|
|
||||||
return $node->getJsonConfiguration();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,73 +0,0 @@
|
||||||
<?php
|
|
||||||
/**
|
|
||||||
* Pterodactyl - Panel
|
|
||||||
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
|
|
||||||
*
|
|
||||||
* This software is licensed under the terms of the MIT license.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Pterodactyl\Http\Controllers\Daemon;
|
|
||||||
|
|
||||||
use Storage;
|
|
||||||
use Pterodactyl\Models;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Pterodactyl\Http\Controllers\Controller;
|
|
||||||
|
|
||||||
class PackController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Pulls an install pack archive from the system.
|
|
||||||
*
|
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
* @param string $uuid
|
|
||||||
* @return \Illuminate\Http\JsonResponse|\Symfony\Component\HttpFoundation\BinaryFileResponse
|
|
||||||
*/
|
|
||||||
public function pull(Request $request, $uuid)
|
|
||||||
{
|
|
||||||
$pack = Models\Pack::where('uuid', $uuid)->first();
|
|
||||||
|
|
||||||
if (! $pack) {
|
|
||||||
return response()->json(['error' => 'No such pack.'], 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! Storage::exists('packs/' . $pack->uuid . '/archive.tar.gz')) {
|
|
||||||
return response()->json(['error' => 'There is no archive available for this pack.'], 503);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response()->download(storage_path('app/packs/' . $pack->uuid . '/archive.tar.gz'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the hash information for a pack.
|
|
||||||
*
|
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
* @param string $uuid
|
|
||||||
* @return \Illuminate\Http\JsonResponse
|
|
||||||
*/
|
|
||||||
public function hash(Request $request, $uuid)
|
|
||||||
{
|
|
||||||
$pack = Models\Pack::where('uuid', $uuid)->first();
|
|
||||||
|
|
||||||
if (! $pack) {
|
|
||||||
return response()->json(['error' => 'No such pack.'], 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! Storage::exists('packs/' . $pack->uuid . '/archive.tar.gz')) {
|
|
||||||
return response()->json(['error' => 'There is no archive available for this pack.'], 503);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response()->json([
|
|
||||||
'archive.tar.gz' => sha1_file(storage_path('app/packs/' . $pack->uuid . '/archive.tar.gz')),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pulls an update pack archive from the system.
|
|
||||||
*
|
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
*/
|
|
||||||
public function pullUpdate(Request $request)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -38,7 +38,6 @@ use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode;
|
||||||
use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull;
|
use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull;
|
||||||
use Pterodactyl\Http\Middleware\Api\Client\SubstituteClientApiBindings;
|
use Pterodactyl\Http\Middleware\Api\Client\SubstituteClientApiBindings;
|
||||||
use Pterodactyl\Http\Middleware\Api\Application\AuthenticateApplicationUser;
|
use Pterodactyl\Http\Middleware\Api\Application\AuthenticateApplicationUser;
|
||||||
use Pterodactyl\Http\Middleware\DaemonAuthenticate as OldDaemonAuthenticate;
|
|
||||||
|
|
||||||
class Kernel extends HttpKernel
|
class Kernel extends HttpKernel
|
||||||
{
|
{
|
||||||
|
@ -107,7 +106,6 @@ class Kernel extends HttpKernel
|
||||||
'server' => AccessingValidServer::class,
|
'server' => AccessingValidServer::class,
|
||||||
'subuser.auth' => AuthenticateAsSubuser::class,
|
'subuser.auth' => AuthenticateAsSubuser::class,
|
||||||
'admin' => AdminAuthenticate::class,
|
'admin' => AdminAuthenticate::class,
|
||||||
'daemon-old' => OldDaemonAuthenticate::class,
|
|
||||||
'csrf' => VerifyCsrfToken::class,
|
'csrf' => VerifyCsrfToken::class,
|
||||||
'throttle' => ThrottleRequests::class,
|
'throttle' => ThrottleRequests::class,
|
||||||
'can' => Authorize::class,
|
'can' => Authorize::class,
|
||||||
|
|
|
@ -4,6 +4,7 @@ namespace Pterodactyl\Http\Middleware\Api\Daemon;
|
||||||
|
|
||||||
use Closure;
|
use Closure;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Contracts\Encryption\Encrypter;
|
||||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||||
use Pterodactyl\Contracts\Repository\NodeRepositoryInterface;
|
use Pterodactyl\Contracts\Repository\NodeRepositoryInterface;
|
||||||
use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
|
use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
|
||||||
|
@ -25,14 +26,21 @@ class DaemonAuthenticate
|
||||||
'daemon.configuration',
|
'daemon.configuration',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Illuminate\Contracts\Encryption\Encrypter
|
||||||
|
*/
|
||||||
|
private $encrypter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DaemonAuthenticate constructor.
|
* DaemonAuthenticate constructor.
|
||||||
*
|
*
|
||||||
|
* @param \Illuminate\Contracts\Encryption\Encrypter $encrypter
|
||||||
* @param \Pterodactyl\Contracts\Repository\NodeRepositoryInterface $repository
|
* @param \Pterodactyl\Contracts\Repository\NodeRepositoryInterface $repository
|
||||||
*/
|
*/
|
||||||
public function __construct(NodeRepositoryInterface $repository)
|
public function __construct(Encrypter $encrypter, NodeRepositoryInterface $repository)
|
||||||
{
|
{
|
||||||
$this->repository = $repository;
|
$this->repository = $repository;
|
||||||
|
$this->encrypter = $encrypter;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -50,20 +58,31 @@ class DaemonAuthenticate
|
||||||
return $next($request);
|
return $next($request);
|
||||||
}
|
}
|
||||||
|
|
||||||
$token = $request->bearerToken();
|
if (is_null($bearer = $request->bearerToken())) {
|
||||||
|
throw new HttpException(
|
||||||
if (is_null($token)) {
|
401, 'Access this this endpoint must include an Authorization header.', null, ['WWW-Authenticate' => 'Bearer']
|
||||||
throw new HttpException(401, null, null, ['WWW-Authenticate' => 'Bearer']);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[$identifier, $token] = explode('.', $bearer);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$node = $this->repository->findFirstWhere([['daemonSecret', '=', $token]]);
|
/** @var \Pterodactyl\Models\Node $node */
|
||||||
|
$node = $this->repository->findFirstWhere([
|
||||||
|
'daemon_token_id' => $identifier,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (hash_equals((string) $this->encrypter->decrypt($node->daemon_token), $token)) {
|
||||||
|
$request->attributes->set('node', $node);
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
} catch (RecordNotFoundException $exception) {
|
} catch (RecordNotFoundException $exception) {
|
||||||
throw new AccessDeniedHttpException;
|
// Do nothing, we don't want to expose a node not existing at all.
|
||||||
}
|
}
|
||||||
|
|
||||||
$request->attributes->set('node', $node);
|
throw new AccessDeniedHttpException(
|
||||||
|
'You are not authorized to access this resource.'
|
||||||
return $next($request);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,69 +0,0 @@
|
||||||
<?php
|
|
||||||
/**
|
|
||||||
* Pterodactyl - Panel
|
|
||||||
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
|
|
||||||
*
|
|
||||||
* This software is licensed under the terms of the MIT license.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Pterodactyl\Http\Middleware;
|
|
||||||
|
|
||||||
use Closure;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Pterodactyl\Contracts\Repository\NodeRepositoryInterface;
|
|
||||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
|
||||||
|
|
||||||
class DaemonAuthenticate
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* An array of route names to not apply this middleware to.
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
private $except = [
|
|
||||||
'daemon.configuration',
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface
|
|
||||||
*/
|
|
||||||
private $repository;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new filter instance.
|
|
||||||
*
|
|
||||||
* @param \Pterodactyl\Contracts\Repository\NodeRepositoryInterface $repository
|
|
||||||
* @deprecated
|
|
||||||
*/
|
|
||||||
public function __construct(NodeRepositoryInterface $repository)
|
|
||||||
{
|
|
||||||
$this->repository = $repository;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle an incoming request.
|
|
||||||
*
|
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
* @param \Closure $next
|
|
||||||
* @return mixed
|
|
||||||
*
|
|
||||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
|
||||||
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
|
|
||||||
*/
|
|
||||||
public function handle(Request $request, Closure $next)
|
|
||||||
{
|
|
||||||
if (in_array($request->route()->getName(), $this->except)) {
|
|
||||||
return $next($request);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! $request->header('X-Access-Node')) {
|
|
||||||
throw new AccessDeniedHttpException;
|
|
||||||
}
|
|
||||||
|
|
||||||
$node = $this->repository->findFirstWhere(['daemonSecret' => $request->header('X-Access-Node')]);
|
|
||||||
$request->attributes->set('node', $node);
|
|
||||||
|
|
||||||
return $next($request);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,11 +3,14 @@
|
||||||
namespace Pterodactyl\Models;
|
namespace Pterodactyl\Models;
|
||||||
|
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
use Illuminate\Container\Container;
|
||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
use Pterodactyl\Models\Traits\Searchable;
|
use Pterodactyl\Models\Traits\Searchable;
|
||||||
|
use Illuminate\Contracts\Encryption\Encrypter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property int $id
|
* @property int $id
|
||||||
|
* @property string $uuid
|
||||||
* @property bool $public
|
* @property bool $public
|
||||||
* @property string $name
|
* @property string $name
|
||||||
* @property string $description
|
* @property string $description
|
||||||
|
@ -21,7 +24,8 @@ use Pterodactyl\Models\Traits\Searchable;
|
||||||
* @property int $disk
|
* @property int $disk
|
||||||
* @property int $disk_overallocate
|
* @property int $disk_overallocate
|
||||||
* @property int $upload_size
|
* @property int $upload_size
|
||||||
* @property string $daemonSecret
|
* @property string $daemon_token_id
|
||||||
|
* @property string $daemon_token
|
||||||
* @property int $daemonListen
|
* @property int $daemonListen
|
||||||
* @property int $daemonSFTP
|
* @property int $daemonSFTP
|
||||||
* @property string $daemonBase
|
* @property string $daemonBase
|
||||||
|
@ -43,7 +47,8 @@ class Node extends Model
|
||||||
*/
|
*/
|
||||||
const RESOURCE_NAME = 'node';
|
const RESOURCE_NAME = 'node';
|
||||||
|
|
||||||
const DAEMON_SECRET_LENGTH = 36;
|
const DAEMON_TOKEN_ID_LENGTH = 16;
|
||||||
|
const DAEMON_TOKEN_LENGTH = 64;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The table associated with the model.
|
* The table associated with the model.
|
||||||
|
@ -57,7 +62,7 @@ class Node extends Model
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $hidden = ['daemonSecret'];
|
protected $hidden = ['daemon_token_id', 'daemon_token'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cast values to correct type.
|
* Cast values to correct type.
|
||||||
|
@ -84,8 +89,7 @@ class Node extends Model
|
||||||
'public', 'name', 'location_id',
|
'public', 'name', 'location_id',
|
||||||
'fqdn', 'scheme', 'behind_proxy',
|
'fqdn', 'scheme', 'behind_proxy',
|
||||||
'memory', 'memory_overallocate', 'disk',
|
'memory', 'memory_overallocate', 'disk',
|
||||||
'disk_overallocate', 'upload_size',
|
'disk_overallocate', 'upload_size', 'daemonBase',
|
||||||
'daemonSecret', 'daemonBase',
|
|
||||||
'daemonSFTP', 'daemonListen',
|
'daemonSFTP', 'daemonListen',
|
||||||
'description', 'maintenance_mode',
|
'description', 'maintenance_mode',
|
||||||
];
|
];
|
||||||
|
@ -153,12 +157,15 @@ class Node extends Model
|
||||||
/**
|
/**
|
||||||
* Returns the configuration as an array.
|
* Returns the configuration as an array.
|
||||||
*
|
*
|
||||||
* @return string
|
* @return array
|
||||||
*/
|
*/
|
||||||
private function getConfiguration()
|
public function getConfiguration()
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'debug' => false,
|
'debug' => false,
|
||||||
|
'uuid' => $this->uuid,
|
||||||
|
'token_id' => $this->daemon_token_id,
|
||||||
|
'token' => Container::getInstance()->make(Encrypter::class)->decrypt($this->daemon_token),
|
||||||
'api' => [
|
'api' => [
|
||||||
'host' => '0.0.0.0',
|
'host' => '0.0.0.0',
|
||||||
'port' => $this->daemonListen,
|
'port' => $this->daemonListen,
|
||||||
|
@ -202,7 +209,6 @@ class Node extends Model
|
||||||
'check_interval' => 100,
|
'check_interval' => 100,
|
||||||
],
|
],
|
||||||
'remote' => route('index'),
|
'remote' => route('index'),
|
||||||
'token' => $this->daemonSecret,
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -211,17 +217,32 @@ class Node extends Model
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getYamlConfiguration() {
|
public function getYamlConfiguration()
|
||||||
|
{
|
||||||
return Yaml::dump($this->getConfiguration(), 4, 2);
|
return Yaml::dump($this->getConfiguration(), 4, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the configuration in JSON format.
|
* Returns the configuration in JSON format.
|
||||||
*
|
*
|
||||||
|
* @param bool $pretty
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getJsonConfiguration(bool $pretty = false)
|
||||||
|
{
|
||||||
|
return json_encode($this->getConfiguration(), $pretty ? JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT : JSON_UNESCAPED_SLASHES);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to return the decrypted key for a node.
|
||||||
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getJsonConfiguration(bool $pretty = false) {
|
public function getDecryptedKey(): string
|
||||||
return json_encode($this->getConfiguration(), $pretty ? JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT : JSON_UNESCAPED_SLASHES);
|
{
|
||||||
|
return (string) Container::getInstance()->make(Encrypter::class)->decrypt(
|
||||||
|
$this->daemon_token
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -49,9 +49,5 @@ class RouteServiceProvider extends ServiceProvider
|
||||||
Route::middleware(['daemon'])->prefix('/api/remote')
|
Route::middleware(['daemon'])->prefix('/api/remote')
|
||||||
->namespace($this->namespace . '\Api\Remote')
|
->namespace($this->namespace . '\Api\Remote')
|
||||||
->group(base_path('routes/api-remote.php'));
|
->group(base_path('routes/api-remote.php'));
|
||||||
|
|
||||||
Route::middleware(['web', 'daemon-old'])->prefix('/daemon')
|
|
||||||
->namespace($this->namespace . '\Daemon')
|
|
||||||
->group(base_path('routes/daemon.php'));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,154 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Repositories\Daemon;
|
|
||||||
|
|
||||||
use RuntimeException;
|
|
||||||
use GuzzleHttp\Client;
|
|
||||||
use Pterodactyl\Models\Node;
|
|
||||||
use Pterodactyl\Models\Server;
|
|
||||||
use Illuminate\Foundation\Application;
|
|
||||||
use Pterodactyl\Contracts\Repository\NodeRepositoryInterface;
|
|
||||||
use Pterodactyl\Contracts\Repository\Daemon\BaseRepositoryInterface;
|
|
||||||
|
|
||||||
abstract class BaseRepository implements BaseRepositoryInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var \Illuminate\Foundation\Application
|
|
||||||
*/
|
|
||||||
private $app;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var \Pterodactyl\Models\Server
|
|
||||||
*/
|
|
||||||
private $server;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string|null
|
|
||||||
*/
|
|
||||||
private $token;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var \Pterodactyl\Models\Node|null
|
|
||||||
*/
|
|
||||||
private $node;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface
|
|
||||||
*/
|
|
||||||
private $nodeRepository;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* BaseRepository constructor.
|
|
||||||
*
|
|
||||||
* @param \Illuminate\Foundation\Application $app
|
|
||||||
* @param \Pterodactyl\Contracts\Repository\NodeRepositoryInterface $nodeRepository
|
|
||||||
*/
|
|
||||||
public function __construct(Application $app, NodeRepositoryInterface $nodeRepository)
|
|
||||||
{
|
|
||||||
$this->app = $app;
|
|
||||||
$this->nodeRepository = $nodeRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the node model to be used for this daemon connection.
|
|
||||||
*
|
|
||||||
* @param \Pterodactyl\Models\Node $node
|
|
||||||
* @return $this
|
|
||||||
*/
|
|
||||||
public function setNode(Node $node)
|
|
||||||
{
|
|
||||||
$this->node = $node;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the node model being used.
|
|
||||||
*
|
|
||||||
* @return \Pterodactyl\Models\Node|null
|
|
||||||
*/
|
|
||||||
public function getNode()
|
|
||||||
{
|
|
||||||
return $this->node;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the Server model to use when requesting information from the Daemon.
|
|
||||||
*
|
|
||||||
* @param \Pterodactyl\Models\Server $server
|
|
||||||
* @return $this
|
|
||||||
*/
|
|
||||||
public function setServer(Server $server)
|
|
||||||
{
|
|
||||||
$this->server = $server;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the Server model.
|
|
||||||
*
|
|
||||||
* @return \Pterodactyl\Models\Server|null
|
|
||||||
*/
|
|
||||||
public function getServer()
|
|
||||||
{
|
|
||||||
return $this->server;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the token to be used in the X-Access-Token header for requests to the daemon.
|
|
||||||
*
|
|
||||||
* @param string $token
|
|
||||||
* @return $this
|
|
||||||
*/
|
|
||||||
public function setToken(string $token)
|
|
||||||
{
|
|
||||||
$this->token = $token;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the access token being used for requests.
|
|
||||||
*
|
|
||||||
* @return string|null
|
|
||||||
*/
|
|
||||||
public function getToken()
|
|
||||||
{
|
|
||||||
return $this->token;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an instance of the Guzzle HTTP Client to be used for requests.
|
|
||||||
*
|
|
||||||
* @param array $headers
|
|
||||||
* @return \GuzzleHttp\Client
|
|
||||||
*/
|
|
||||||
public function getHttpClient(array $headers = []): Client
|
|
||||||
{
|
|
||||||
// If no node is set, load the relationship onto the Server model
|
|
||||||
// and pass that to the setNode function.
|
|
||||||
if (! $this->getNode() instanceof Node) {
|
|
||||||
if (! $this->getServer() instanceof Server) {
|
|
||||||
throw new RuntimeException('An instance of ' . Node::class . ' or ' . Server::class . ' must be set on this repository in order to return a client.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->getServer()->loadMissing('node');
|
|
||||||
$this->setNode($this->getServer()->getRelation('node'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->getServer() instanceof Server) {
|
|
||||||
$headers['X-Access-Server'] = $this->getServer()->uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
$headers['X-Access-Token'] = $this->getToken() ?? $this->getNode()->daemonSecret;
|
|
||||||
|
|
||||||
return new Client([
|
|
||||||
'verify' => config('app.env') === 'production',
|
|
||||||
'base_uri' => sprintf('%s://%s:%s/v1/', $this->getNode()->scheme, $this->getNode()->fqdn, $this->getNode()->daemonListen),
|
|
||||||
'timeout' => config('pterodactyl.guzzle.timeout'),
|
|
||||||
'connect_timeout' => config('pterodactyl.guzzle.connect_timeout'),
|
|
||||||
'headers' => $headers,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Repositories\Daemon;
|
|
||||||
|
|
||||||
use Psr\Http\Message\ResponseInterface;
|
|
||||||
use Pterodactyl\Contracts\Repository\Daemon\CommandRepositoryInterface;
|
|
||||||
|
|
||||||
class CommandRepository extends BaseRepository implements CommandRepositoryInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Send a command to a server.
|
|
||||||
*
|
|
||||||
* @param string $command
|
|
||||||
* @return \Psr\Http\Message\ResponseInterface
|
|
||||||
* @throws \GuzzleHttp\Exception\GuzzleException
|
|
||||||
*/
|
|
||||||
public function send(string $command): ResponseInterface
|
|
||||||
{
|
|
||||||
return $this->getHttpClient()->request('POST', 'server/command', [
|
|
||||||
'json' => [
|
|
||||||
'command' => $command,
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Repositories\Daemon;
|
|
||||||
|
|
||||||
use Psr\Http\Message\ResponseInterface;
|
|
||||||
use Pterodactyl\Contracts\Repository\Daemon\ConfigurationRepositoryInterface;
|
|
||||||
|
|
||||||
class ConfigurationRepository extends BaseRepository implements ConfigurationRepositoryInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Update the configuration details for the specified node using data from the database.
|
|
||||||
*
|
|
||||||
* @param array $overrides
|
|
||||||
* @return \Psr\Http\Message\ResponseInterface
|
|
||||||
* @throws \GuzzleHttp\Exception\GuzzleException
|
|
||||||
*/
|
|
||||||
public function update(array $overrides = []): ResponseInterface
|
|
||||||
{
|
|
||||||
$node = $this->getNode();
|
|
||||||
$structure = [
|
|
||||||
'web' => [
|
|
||||||
'listen' => $node->daemonListen,
|
|
||||||
'ssl' => [
|
|
||||||
'enabled' => (! $node->behind_proxy && $node->scheme === 'https'),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'sftp' => [
|
|
||||||
'path' => $node->daemonBase,
|
|
||||||
'port' => $node->daemonSFTP,
|
|
||||||
],
|
|
||||||
'remote' => [
|
|
||||||
'base' => config('app.url'),
|
|
||||||
],
|
|
||||||
'uploads' => [
|
|
||||||
'size_limit' => $node->upload_size,
|
|
||||||
],
|
|
||||||
'keys' => [
|
|
||||||
$node->daemonSecret,
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
return $this->getHttpClient()->request('PATCH', 'config', [
|
|
||||||
'json' => array_merge($structure, $overrides),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,104 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Repositories\Daemon;
|
|
||||||
|
|
||||||
use stdClass;
|
|
||||||
use RuntimeException;
|
|
||||||
use Psr\Http\Message\ResponseInterface;
|
|
||||||
use Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface;
|
|
||||||
|
|
||||||
class FileRepository extends BaseRepository implements FileRepositoryInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Return stat information for a given file.
|
|
||||||
*
|
|
||||||
* @param string $path
|
|
||||||
* @return \stdClass
|
|
||||||
*
|
|
||||||
* @throws \GuzzleHttp\Exception\GuzzleException
|
|
||||||
*/
|
|
||||||
public function getFileStat(string $path): stdClass
|
|
||||||
{
|
|
||||||
$file = str_replace('\\', '/', pathinfo($path));
|
|
||||||
$file['dirname'] = in_array($file['dirname'], ['.', './', '/']) ? null : trim($file['dirname'], '/') . '/';
|
|
||||||
|
|
||||||
$response = $this->getHttpClient()->request('GET', sprintf(
|
|
||||||
'server/file/stat/%s',
|
|
||||||
rawurlencode($file['dirname'] . $file['basename'])
|
|
||||||
));
|
|
||||||
|
|
||||||
return json_decode($response->getBody());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the contents of a given file if it can be edited in the Panel.
|
|
||||||
*
|
|
||||||
* @param string $path
|
|
||||||
* @return string
|
|
||||||
*
|
|
||||||
* @throws \GuzzleHttp\Exception\GuzzleException
|
|
||||||
*/
|
|
||||||
public function getContent(string $path): string
|
|
||||||
{
|
|
||||||
$file = str_replace('\\', '/', pathinfo($path));
|
|
||||||
$file['dirname'] = in_array($file['dirname'], ['.', './', '/']) ? null : trim($file['dirname'], '/') . '/';
|
|
||||||
|
|
||||||
$response = $this->getHttpClient()->request('GET', sprintf(
|
|
||||||
'server/file/f/%s',
|
|
||||||
rawurlencode($file['dirname'] . $file['basename'])
|
|
||||||
));
|
|
||||||
|
|
||||||
return object_get(json_decode($response->getBody()), 'content');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Save new contents to a given file.
|
|
||||||
*
|
|
||||||
* @param string $path
|
|
||||||
* @param string $content
|
|
||||||
* @return \Psr\Http\Message\ResponseInterface
|
|
||||||
*
|
|
||||||
* @throws \GuzzleHttp\Exception\GuzzleException
|
|
||||||
*/
|
|
||||||
public function putContent(string $path, string $content): ResponseInterface
|
|
||||||
{
|
|
||||||
$file = str_replace('\\', '/', pathinfo($path));
|
|
||||||
$file['dirname'] = in_array($file['dirname'], ['.', './', '/']) ? null : trim($file['dirname'], '/') . '/';
|
|
||||||
|
|
||||||
return $this->getHttpClient()->request('POST', 'server/file/save', [
|
|
||||||
'json' => [
|
|
||||||
'path' => rawurlencode($file['dirname'] . $file['basename']),
|
|
||||||
'content' => $content,
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a directory listing for a given path.
|
|
||||||
*
|
|
||||||
* @param string $path
|
|
||||||
* @return array
|
|
||||||
*
|
|
||||||
* @throws \GuzzleHttp\Exception\GuzzleException
|
|
||||||
*/
|
|
||||||
public function getDirectory(string $path): array
|
|
||||||
{
|
|
||||||
$response = $this->getHttpClient()->request('GET', sprintf('server/directory/%s', rawurlencode($path)));
|
|
||||||
|
|
||||||
return json_decode($response->getBody());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new directory for the server in the given $path.
|
|
||||||
*
|
|
||||||
* @param string $name
|
|
||||||
* @param string $path
|
|
||||||
* @return \Psr\Http\Message\ResponseInterface
|
|
||||||
*
|
|
||||||
* @throws \RuntimeException
|
|
||||||
*/
|
|
||||||
public function createDirectory(string $name, string $path): ResponseInterface
|
|
||||||
{
|
|
||||||
throw new RuntimeException('Not implemented.');
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Repositories\Daemon;
|
|
||||||
|
|
||||||
use Psr\Http\Message\ResponseInterface;
|
|
||||||
use Pterodactyl\Contracts\Repository\Daemon\PowerRepositoryInterface;
|
|
||||||
use Pterodactyl\Exceptions\Repository\Daemon\InvalidPowerSignalException;
|
|
||||||
|
|
||||||
class PowerRepository extends BaseRepository implements PowerRepositoryInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Send a power signal to a server.
|
|
||||||
*
|
|
||||||
* @param string $signal
|
|
||||||
* @return \Psr\Http\Message\ResponseInterface
|
|
||||||
*
|
|
||||||
* @throws InvalidPowerSignalException
|
|
||||||
* @throws \GuzzleHttp\Exception\GuzzleException
|
|
||||||
*/
|
|
||||||
public function sendSignal(string $signal): ResponseInterface
|
|
||||||
{
|
|
||||||
switch ($signal) {
|
|
||||||
case self::SIGNAL_START:
|
|
||||||
case self::SIGNAL_STOP:
|
|
||||||
case self::SIGNAL_RESTART:
|
|
||||||
case self::SIGNAL_KILL:
|
|
||||||
return $this->getHttpClient()->request('PUT', 'server/power', [
|
|
||||||
'json' => [
|
|
||||||
'action' => $signal,
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
default:
|
|
||||||
throw new InvalidPowerSignalException('The signal "' . $signal . '" is not defined and could not be processed.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,134 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Repositories\Daemon;
|
|
||||||
|
|
||||||
use Webmozart\Assert\Assert;
|
|
||||||
use Psr\Http\Message\ResponseInterface;
|
|
||||||
use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface;
|
|
||||||
|
|
||||||
class ServerRepository extends BaseRepository implements ServerRepositoryInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Create a new server on the daemon for the panel.
|
|
||||||
*
|
|
||||||
* @param array $structure
|
|
||||||
* @param array $overrides
|
|
||||||
* @return \Psr\Http\Message\ResponseInterface
|
|
||||||
*
|
|
||||||
* @throws \GuzzleHttp\Exception\GuzzleException
|
|
||||||
*/
|
|
||||||
public function create(array $structure, array $overrides = []): ResponseInterface
|
|
||||||
{
|
|
||||||
foreach ($overrides as $key => $value) {
|
|
||||||
$structure[$key] = value($value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->getHttpClient()->request('POST', 'servers', [
|
|
||||||
'json' => $structure,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update server details on the daemon.
|
|
||||||
*
|
|
||||||
* @param array $data
|
|
||||||
* @return \Psr\Http\Message\ResponseInterface
|
|
||||||
* @throws \GuzzleHttp\Exception\GuzzleException
|
|
||||||
*/
|
|
||||||
public function update(array $data): ResponseInterface
|
|
||||||
{
|
|
||||||
return $this->getHttpClient()->request('PATCH', 'server', [
|
|
||||||
'json' => $data,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mark a server to be reinstalled on the system.
|
|
||||||
*
|
|
||||||
* @param array|null $data
|
|
||||||
* @return \Psr\Http\Message\ResponseInterface
|
|
||||||
* @throws \GuzzleHttp\Exception\GuzzleException
|
|
||||||
*/
|
|
||||||
public function reinstall(array $data = null): ResponseInterface
|
|
||||||
{
|
|
||||||
return $this->getHttpClient()->request('POST', 'server/reinstall', [
|
|
||||||
'json' => $data ?? [],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mark a server as needing a container rebuild the next time the server is booted.
|
|
||||||
*
|
|
||||||
* @return \Psr\Http\Message\ResponseInterface
|
|
||||||
* @throws \GuzzleHttp\Exception\GuzzleException
|
|
||||||
*/
|
|
||||||
public function rebuild(): ResponseInterface
|
|
||||||
{
|
|
||||||
return $this->getHttpClient()->request('POST', 'server/rebuild');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Suspend a server on the daemon.
|
|
||||||
*
|
|
||||||
* @return \Psr\Http\Message\ResponseInterface
|
|
||||||
* @throws \GuzzleHttp\Exception\GuzzleException
|
|
||||||
*/
|
|
||||||
public function suspend(): ResponseInterface
|
|
||||||
{
|
|
||||||
return $this->getHttpClient()->request('POST', 'server/suspend');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Un-suspend a server on the daemon.
|
|
||||||
*
|
|
||||||
* @return \Psr\Http\Message\ResponseInterface
|
|
||||||
* @throws \GuzzleHttp\Exception\GuzzleException
|
|
||||||
*/
|
|
||||||
public function unsuspend(): ResponseInterface
|
|
||||||
{
|
|
||||||
return $this->getHttpClient()->request('POST', 'server/unsuspend');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a server on the daemon.
|
|
||||||
*
|
|
||||||
* @return \Psr\Http\Message\ResponseInterface
|
|
||||||
* @throws \GuzzleHttp\Exception\GuzzleException
|
|
||||||
*/
|
|
||||||
public function delete(): ResponseInterface
|
|
||||||
{
|
|
||||||
return $this->getHttpClient()->request('DELETE', 'servers');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return details on a specific server.
|
|
||||||
*
|
|
||||||
* @return \Psr\Http\Message\ResponseInterface
|
|
||||||
* @throws \GuzzleHttp\Exception\GuzzleException
|
|
||||||
*/
|
|
||||||
public function details(): ResponseInterface
|
|
||||||
{
|
|
||||||
return $this->getHttpClient()->request('GET', 'server');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Revoke an access key on the daemon before the time is expired.
|
|
||||||
*
|
|
||||||
* @param string|array $key
|
|
||||||
* @return \Psr\Http\Message\ResponseInterface
|
|
||||||
*
|
|
||||||
* @throws \GuzzleHttp\Exception\GuzzleException
|
|
||||||
*/
|
|
||||||
public function revokeAccessKey($key): ResponseInterface
|
|
||||||
{
|
|
||||||
if (is_array($key)) {
|
|
||||||
return $this->getHttpClient()->request('POST', 'keys/batch-delete', [
|
|
||||||
'json' => ['keys' => $key],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert::stringNotEmpty($key, 'First argument passed to revokeAccessKey must be a non-empty string or array, received %s.');
|
|
||||||
|
|
||||||
return $this->getHttpClient()->request('DELETE', 'keys/' . $key);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -183,7 +183,7 @@ class NodeRepository extends EloquentRepository implements NodeRepositoryInterfa
|
||||||
public function getNodeWithResourceUsage(int $node_id): Node
|
public function getNodeWithResourceUsage(int $node_id): Node
|
||||||
{
|
{
|
||||||
$instance = $this->getBuilder()
|
$instance = $this->getBuilder()
|
||||||
->select(['nodes.id', 'nodes.fqdn', 'nodes.scheme', 'nodes.daemonSecret', 'nodes.daemonListen', 'nodes.memory', 'nodes.disk', 'nodes.memory_overallocate', 'nodes.disk_overallocate'])
|
->select(['nodes.id', 'nodes.fqdn', 'nodes.scheme', 'nodes.daemon_token', 'nodes.daemonListen', 'nodes.memory', 'nodes.disk', 'nodes.memory_overallocate', 'nodes.disk_overallocate'])
|
||||||
->selectRaw('IFNULL(SUM(servers.memory), 0) as sum_memory, IFNULL(SUM(servers.disk), 0) as sum_disk')
|
->selectRaw('IFNULL(SUM(servers.memory), 0) as sum_memory, IFNULL(SUM(servers.disk), 0) as sum_disk')
|
||||||
->leftJoin('servers', 'servers.node_id', '=', 'nodes.id')
|
->leftJoin('servers', 'servers.node_id', '=', 'nodes.id')
|
||||||
->where('nodes.id', $node_id);
|
->where('nodes.id', $node_id);
|
||||||
|
|
|
@ -23,4 +23,22 @@ class DaemonConfigurationRepository extends DaemonRepository
|
||||||
|
|
||||||
return json_decode($response->getBody()->__toString(), true);
|
return json_decode($response->getBody()->__toString(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the configuration information for a daemon.
|
||||||
|
*
|
||||||
|
* @param array $attributes
|
||||||
|
* @return \Psr\Http\Message\ResponseInterface
|
||||||
|
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
|
||||||
|
*/
|
||||||
|
public function update(array $attributes = [])
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return $this->getHttpClient()->post(
|
||||||
|
'/api/update', array_merge($this->node->getConfiguration(), $attributes)
|
||||||
|
);
|
||||||
|
} catch (TransferException $exception) {
|
||||||
|
throw new DaemonConnectionException($exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,7 +79,7 @@ abstract class DaemonRepository
|
||||||
'timeout' => config('pterodactyl.guzzle.timeout'),
|
'timeout' => config('pterodactyl.guzzle.timeout'),
|
||||||
'connect_timeout' => config('pterodactyl.guzzle.connect_timeout'),
|
'connect_timeout' => config('pterodactyl.guzzle.connect_timeout'),
|
||||||
'headers' => array_merge($headers, [
|
'headers' => array_merge($headers, [
|
||||||
'Authorization' => 'Bearer ' . $this->node->daemonSecret,
|
'Authorization' => 'Bearer ' . $this->node->getDecryptedKey(),
|
||||||
'Accept' => 'application/json',
|
'Accept' => 'application/json',
|
||||||
'Content-Type' => 'application/json',
|
'Content-Type' => 'application/json',
|
||||||
]),
|
]),
|
||||||
|
|
|
@ -1,33 +1,34 @@
|
||||||
<?php
|
<?php
|
||||||
/**
|
|
||||||
* Pterodactyl - Panel
|
|
||||||
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
|
|
||||||
*
|
|
||||||
* This software is licensed under the terms of the MIT license.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Pterodactyl\Services\Nodes;
|
namespace Pterodactyl\Services\Nodes;
|
||||||
|
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Pterodactyl\Models\Node;
|
||||||
|
use Illuminate\Encryption\Encrypter;
|
||||||
use Pterodactyl\Contracts\Repository\NodeRepositoryInterface;
|
use Pterodactyl\Contracts\Repository\NodeRepositoryInterface;
|
||||||
|
|
||||||
class NodeCreationService
|
class NodeCreationService
|
||||||
{
|
{
|
||||||
const DAEMON_SECRET_LENGTH = 36;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface
|
* @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface
|
||||||
*/
|
*/
|
||||||
protected $repository;
|
protected $repository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Illuminate\Encryption\Encrypter
|
||||||
|
*/
|
||||||
|
private $encrypter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CreationService constructor.
|
* CreationService constructor.
|
||||||
*
|
*
|
||||||
|
* @param \Illuminate\Encryption\Encrypter $encrypter
|
||||||
* @param \Pterodactyl\Contracts\Repository\NodeRepositoryInterface $repository
|
* @param \Pterodactyl\Contracts\Repository\NodeRepositoryInterface $repository
|
||||||
*/
|
*/
|
||||||
public function __construct(NodeRepositoryInterface $repository)
|
public function __construct(Encrypter $encrypter, NodeRepositoryInterface $repository)
|
||||||
{
|
{
|
||||||
$this->repository = $repository;
|
$this->repository = $repository;
|
||||||
|
$this->encrypter = $encrypter;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -40,8 +41,9 @@ class NodeCreationService
|
||||||
*/
|
*/
|
||||||
public function handle(array $data)
|
public function handle(array $data)
|
||||||
{
|
{
|
||||||
$data['daemonSecret'] = str_random(self::DAEMON_SECRET_LENGTH);
|
$data['daemon_token'] = Str::random(Node::DAEMON_TOKEN_LENGTH);
|
||||||
|
$data['daemon_token_id'] = $this->encrypter->encrypt(Str::random(Node::DAEMON_TOKEN_ID_LENGTH));
|
||||||
|
|
||||||
return $this->repository->create($data);
|
return $this->repository->create($data, true, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,6 +69,6 @@ class NodeJWTService
|
||||||
|
|
||||||
return $builder
|
return $builder
|
||||||
->withClaim('unique_id', Str::random(16))
|
->withClaim('unique_id', Str::random(16))
|
||||||
->getToken($signer, new Key($node->daemonSecret));
|
->getToken($signer, new Key($node->getDecryptedKey()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,12 +2,15 @@
|
||||||
|
|
||||||
namespace Pterodactyl\Services\Nodes;
|
namespace Pterodactyl\Services\Nodes;
|
||||||
|
|
||||||
|
use Illuminate\Support\Str;
|
||||||
use Pterodactyl\Models\Node;
|
use Pterodactyl\Models\Node;
|
||||||
use GuzzleHttp\Exception\ConnectException;
|
use GuzzleHttp\Exception\ConnectException;
|
||||||
use GuzzleHttp\Exception\RequestException;
|
use GuzzleHttp\Exception\RequestException;
|
||||||
use Illuminate\Database\ConnectionInterface;
|
use Illuminate\Database\ConnectionInterface;
|
||||||
|
use Illuminate\Contracts\Encryption\Encrypter;
|
||||||
use Pterodactyl\Repositories\Daemon\ConfigurationRepository;
|
use Pterodactyl\Repositories\Daemon\ConfigurationRepository;
|
||||||
use Pterodactyl\Contracts\Repository\NodeRepositoryInterface;
|
use Pterodactyl\Contracts\Repository\NodeRepositoryInterface;
|
||||||
|
use Pterodactyl\Repositories\Wings\DaemonConfigurationRepository;
|
||||||
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
|
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
|
||||||
use Pterodactyl\Exceptions\Service\Node\ConfigurationNotPersistedException;
|
use Pterodactyl\Exceptions\Service\Node\ConfigurationNotPersistedException;
|
||||||
|
|
||||||
|
@ -18,31 +21,39 @@ class NodeUpdateService
|
||||||
*/
|
*/
|
||||||
private $connection;
|
private $connection;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var \Pterodactyl\Contracts\Repository\Daemon\ConfigurationRepositoryInterface
|
|
||||||
*/
|
|
||||||
private $configRepository;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface
|
* @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface
|
||||||
*/
|
*/
|
||||||
private $repository;
|
private $repository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Pterodactyl\Repositories\Wings\DaemonConfigurationRepository
|
||||||
|
*/
|
||||||
|
private $configurationRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Illuminate\Contracts\Encryption\Encrypter
|
||||||
|
*/
|
||||||
|
private $encrypter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UpdateService constructor.
|
* UpdateService constructor.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Database\ConnectionInterface $connection
|
* @param \Illuminate\Database\ConnectionInterface $connection
|
||||||
* @param \Pterodactyl\Repositories\Daemon\ConfigurationRepository $configurationRepository
|
* @param \Illuminate\Contracts\Encryption\Encrypter $encrypter
|
||||||
|
* @param \Pterodactyl\Repositories\Wings\DaemonConfigurationRepository $configurationRepository
|
||||||
* @param \Pterodactyl\Contracts\Repository\NodeRepositoryInterface $repository
|
* @param \Pterodactyl\Contracts\Repository\NodeRepositoryInterface $repository
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
ConnectionInterface $connection,
|
ConnectionInterface $connection,
|
||||||
ConfigurationRepository $configurationRepository,
|
Encrypter $encrypter,
|
||||||
|
DaemonConfigurationRepository $configurationRepository,
|
||||||
NodeRepositoryInterface $repository
|
NodeRepositoryInterface $repository
|
||||||
) {
|
) {
|
||||||
$this->connection = $connection;
|
$this->connection = $connection;
|
||||||
$this->configRepository = $configurationRepository;
|
|
||||||
$this->repository = $repository;
|
$this->repository = $repository;
|
||||||
|
$this->configurationRepository = $configurationRepository;
|
||||||
|
$this->encrypter = $encrypter;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -58,13 +69,14 @@ class NodeUpdateService
|
||||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||||
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
|
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
|
||||||
* @throws \Pterodactyl\Exceptions\Service\Node\ConfigurationNotPersistedException
|
* @throws \Pterodactyl\Exceptions\Service\Node\ConfigurationNotPersistedException
|
||||||
*
|
|
||||||
* @throws \GuzzleHttp\Exception\GuzzleException
|
|
||||||
*/
|
*/
|
||||||
public function handle(Node $node, array $data, bool $resetToken = false)
|
public function handle(Node $node, array $data, bool $resetToken = false)
|
||||||
{
|
{
|
||||||
if ($resetToken) {
|
if ($resetToken) {
|
||||||
$data['daemonSecret'] = str_random(Node::DAEMON_SECRET_LENGTH);
|
$data['daemon_token'] = Str::random(Node::DAEMON_TOKEN_LENGTH);
|
||||||
|
$data['daemon_token_id'] = $this->encrypter->encrypt(
|
||||||
|
Str::random(Node::DAEMON_TOKEN_ID_LENGTH)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->connection->beginTransaction();
|
$this->connection->beginTransaction();
|
||||||
|
@ -77,14 +89,15 @@ class NodeUpdateService
|
||||||
// We need to clone the new model and set it's authentication token to be the
|
// We need to clone the new model and set it's authentication token to be the
|
||||||
// old one so we can connect. Then we will pass the new token through as an
|
// old one so we can connect. Then we will pass the new token through as an
|
||||||
// override on the call.
|
// override on the call.
|
||||||
$cloned = $updatedModel->replicate(['daemonSecret']);
|
$cloned = $updatedModel->replicate(['daemon_token']);
|
||||||
$cloned->setAttribute('daemonSecret', $node->getAttribute('daemonSecret'));
|
$cloned->setAttribute('daemon_token', $node->getAttribute('daemon_token'));
|
||||||
|
|
||||||
$this->configRepository->setNode($cloned)->update([
|
$this->configurationRepository->setNode($cloned)->update([
|
||||||
'keys' => [$data['daemonSecret']],
|
'daemon_token_id' => $updatedModel->daemon_token_id,
|
||||||
|
'daemon_token' => $updatedModel->getDecryptedKey(),
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
$this->configRepository->setNode($updatedModel)->update();
|
$this->configurationRepository->setNode($updatedModel)->update();
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->connection->commit();
|
$this->connection->commit();
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use Ramsey\Uuid\Uuid;
|
||||||
use Cake\Chronos\Chronos;
|
use Cake\Chronos\Chronos;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
use Faker\Generator as Faker;
|
use Faker\Generator as Faker;
|
||||||
|
use Pterodactyl\Models\Node;
|
||||||
use Pterodactyl\Models\ApiKey;
|
use Pterodactyl\Models\ApiKey;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -80,6 +83,7 @@ $factory->define(Pterodactyl\Models\Location::class, function (Faker $faker) {
|
||||||
$factory->define(Pterodactyl\Models\Node::class, function (Faker $faker) {
|
$factory->define(Pterodactyl\Models\Node::class, function (Faker $faker) {
|
||||||
return [
|
return [
|
||||||
'id' => $faker->unique()->randomNumber(),
|
'id' => $faker->unique()->randomNumber(),
|
||||||
|
'uuid' => Uuid::uuid4()->toString(),
|
||||||
'public' => true,
|
'public' => true,
|
||||||
'name' => $faker->firstName,
|
'name' => $faker->firstName,
|
||||||
'fqdn' => $faker->ipv4,
|
'fqdn' => $faker->ipv4,
|
||||||
|
@ -90,10 +94,11 @@ $factory->define(Pterodactyl\Models\Node::class, function (Faker $faker) {
|
||||||
'disk' => 10240,
|
'disk' => 10240,
|
||||||
'disk_overallocate' => 0,
|
'disk_overallocate' => 0,
|
||||||
'upload_size' => 100,
|
'upload_size' => 100,
|
||||||
'daemonSecret' => $faker->uuid,
|
'daemon_token_id' => Str::random(Node::DAEMON_TOKEN_ID_LENGTH),
|
||||||
|
'daemon_token' => Str::random(Node::DAEMON_TOKEN_LENGTH),
|
||||||
'daemonListen' => 8080,
|
'daemonListen' => 8080,
|
||||||
'daemonSFTP' => 2022,
|
'daemonSFTP' => 2022,
|
||||||
'daemonBase' => '/srv/daemon',
|
'daemonBase' => '/srv/daemon-data',
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Ramsey\Uuid\Uuid;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Container\Container;
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use Illuminate\Contracts\Encryption\Encrypter;
|
||||||
|
|
||||||
|
class StoreNodeTokensAsEncryptedValue extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::table('nodes', function (Blueprint $table) {
|
||||||
|
$table->dropUnique(['daemonSecret']);
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('nodes', function (Blueprint $table) {
|
||||||
|
$table->char('uuid', 36)->after('id')->unique();
|
||||||
|
$table->char('daemon_token_id', 16)->after('upload_size')->unique();
|
||||||
|
$table->renameColumn('daemonSecret', 'daemon_token');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('nodes', function (Blueprint $table) {
|
||||||
|
$table->text('daemon_token')->change();
|
||||||
|
});
|
||||||
|
|
||||||
|
DB::transaction(function () {
|
||||||
|
/** @var \Illuminate\Contracts\Encryption\Encrypter $encrypter */
|
||||||
|
$encrypter = Container::getInstance()->make(Encrypter::class);
|
||||||
|
|
||||||
|
foreach (DB::select('SELECT id, daemon_token FROM nodes') as $datum) {
|
||||||
|
DB::update('UPDATE nodes SET uuid = ?, daemon_token_id = ?, daemon_token = ? WHERE id = ?', [
|
||||||
|
Uuid::uuid4()->toString(),
|
||||||
|
substr($datum->daemon_token, 0, 16),
|
||||||
|
$encrypter->encrypt(substr($datum->daemon_token, 16)),
|
||||||
|
$datum->id,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
DB::transaction(function () {
|
||||||
|
/** @var \Illuminate\Contracts\Encryption\Encrypter $encrypter */
|
||||||
|
$encrypter = Container::getInstance()->make(Encrypter::class);
|
||||||
|
|
||||||
|
foreach (DB::select('SELECT id, daemon_token_id, daemon_token FROM nodes') as $datum) {
|
||||||
|
DB::update('UPDATE nodes SET daemon_token = ? WHERE id = ?', [
|
||||||
|
$datum->daemon_token_id . $encrypter->decrypt($datum->daemon_token),
|
||||||
|
$datum->id,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('nodes', function (Blueprint $table) {
|
||||||
|
$table->dropUnique(['uuid']);
|
||||||
|
$table->dropUnique(['daemon_token_id']);
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('nodes', function (Blueprint $table) {
|
||||||
|
$table->dropColumn(['uuid', 'daemon_token_id']);
|
||||||
|
$table->renameColumn('daemon_token', 'daemonSecret');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('nodes', function (Blueprint $table) {
|
||||||
|
$table->string('daemonSecret', 36)->change();
|
||||||
|
$table->unique(['daemonSecret']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -55,7 +55,7 @@
|
||||||
</tr>
|
</tr>
|
||||||
@foreach ($nodes as $node)
|
@foreach ($nodes as $node)
|
||||||
<tr>
|
<tr>
|
||||||
<td class="text-center text-muted left-icon" data-action="ping" data-secret="{{ $node->daemonSecret }}" data-location="{{ $node->scheme }}://{{ $node->fqdn }}:{{ $node->daemonListen }}/api/system"><i class="fa fa-fw fa-refresh fa-spin"></i></td>
|
<td class="text-center text-muted left-icon" data-action="ping" data-secret="{{ $node->getDecryptedKey() }}" data-location="{{ $node->scheme }}://{{ $node->fqdn }}:{{ $node->daemonListen }}/api/system"><i class="fa fa-fw fa-refresh fa-spin"></i></td>
|
||||||
<td>{!! $node->maintenance_mode ? '<span class="label label-warning"><i class="fa fa-wrench"></i></span> ' : '' !!}<a href="{{ route('admin.nodes.view', $node->id) }}">{{ $node->name }}</a></td>
|
<td>{!! $node->maintenance_mode ? '<span class="label label-warning"><i class="fa fa-wrench"></i></span> ' : '' !!}<a href="{{ route('admin.nodes.view', $node->id) }}">{{ $node->name }}</a></td>
|
||||||
<td>{{ $node->location->short }}</td>
|
<td>{{ $node->location->short }}</td>
|
||||||
<td>{{ $node->memory }} MB</td>
|
<td>{{ $node->memory }} MB</td>
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
<?php
|
|
||||||
/**
|
|
||||||
* Pterodactyl - Panel
|
|
||||||
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
|
|
||||||
*
|
|
||||||
* This software is licensed under the terms of the MIT license.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
Route::get('/packs/pull/{uuid}', 'PackController@pull')->name('daemon.pack.pull');
|
|
||||||
Route::get('/packs/pull/{uuid}/hash', 'PackController@hash')->name('daemon.pack.hash');
|
|
||||||
Route::get('/configure/{token}', 'ActionController@configuration')->name('daemon.configuration');
|
|
||||||
|
|
||||||
Route::post('/install', 'ActionController@markInstall')->name('daemon.install');
|
|
Loading…
Reference in a new issue