Final touches to new key-rotation service

This commit is contained in:
Dane Everitt 2017-09-24 12:34:00 -05:00
parent 0f0c319ec0
commit 8e2b77dc1e
No known key found for this signature in database
GPG Key ID: EEA66103B3D71F53
6 changed files with 154 additions and 18 deletions

View File

@ -30,6 +30,7 @@ use Illuminate\Contracts\Foundation\Application;
use Illuminate\Foundation\Testing\HttpException; use Illuminate\Foundation\Testing\HttpException;
use League\Fractal\Serializer\JsonApiSerializer; use League\Fractal\Serializer\JsonApiSerializer;
use Pterodactyl\Transformers\Daemon\ApiKeyTransformer; use Pterodactyl\Transformers\Daemon\ApiKeyTransformer;
use Pterodactyl\Services\DaemonKeys\DaemonKeyUpdateService;
use Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface; use Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface;
class ValidateKeyController extends Controller class ValidateKeyController extends Controller
@ -77,7 +78,7 @@ class ValidateKeyController extends Controller
*/ */
public function index($token) public function index($token)
{ {
if (! starts_with($token, 'i_')) { if (! starts_with($token, DaemonKeyUpdateService::INTERNAL_TOKEN_IDENTIFIER)) {
throw new HttpException(501); throw new HttpException(501);
} }

View File

@ -26,7 +26,9 @@
namespace Pterodactyl\Http\Controllers\Base; namespace Pterodactyl\Http\Controllers\Base;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use GuzzleHttp\Exception\RequestException;
use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Http\Controllers\Controller;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Pterodactyl\Services\Servers\ServerAccessHelperService; use Pterodactyl\Services\Servers\ServerAccessHelperService;
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface;
@ -36,7 +38,7 @@ class IndexController extends Controller
/** /**
* @var \Pterodactyl\Services\Servers\ServerAccessHelperService * @var \Pterodactyl\Services\Servers\ServerAccessHelperService
*/ */
protected $access; protected $serverAccessHelper;
/** /**
* @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface
@ -52,15 +54,15 @@ class IndexController extends Controller
* IndexController constructor. * IndexController constructor.
* *
* @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonRepository * @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonRepository
* @param \Pterodactyl\Services\Servers\ServerAccessHelperService $access * @param \Pterodactyl\Services\Servers\ServerAccessHelperService $serverAccessHelper
* @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository
*/ */
public function __construct( public function __construct(
DaemonServerRepositoryInterface $daemonRepository, DaemonServerRepositoryInterface $daemonRepository,
ServerAccessHelperService $access, ServerAccessHelperService $serverAccessHelper,
ServerRepositoryInterface $repository ServerRepositoryInterface $repository
) { ) {
$this->access = $access; $this->serverAccessHelper = $serverAccessHelper;
$this->daemonRepository = $daemonRepository; $this->daemonRepository = $daemonRepository;
$this->repository = $repository; $this->repository = $repository;
} }
@ -90,7 +92,8 @@ class IndexController extends Controller
*/ */
public function status(Request $request, $uuid) public function status(Request $request, $uuid)
{ {
$server = $this->access->handle($uuid, $request->user()); $server = $this->repository->findFirstWhere([['uuidShort', '=', $uuid]]);
$token = $this->serverAccessHelper->handle($server, $request->user());
if (! $server->installed) { if (! $server->installed) {
return response()->json(['status' => 20]); return response()->json(['status' => 20]);
@ -98,10 +101,14 @@ class IndexController extends Controller
return response()->json(['status' => 30]); return response()->json(['status' => 30]);
} }
$response = $this->daemonRepository->setNode($server->node_id) try {
->setAccessServer($server->uuid) $response = $this->daemonRepository->setNode($server->node_id)
->setAccessToken($server->daemonSecret) ->setAccessServer($server->uuid)
->details(); ->setAccessToken($token)
->details();
} catch (RequestException $exception) {
throw new HttpException(500, $exception->getMessage());
}
return response()->json(json_decode($response->getBody())); return response()->json(json_decode($response->getBody()));
} }

View File

@ -0,0 +1,93 @@
<?php
/*
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
namespace Pterodactyl\Services\DaemonKeys;
use Carbon\Carbon;
use Pterodactyl\Models\DaemonKey;
use Illuminate\Contracts\Config\Repository as ConfigRepository;
use Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface;
class DaemonKeyUpdateService
{
const INTERNAL_TOKEN_IDENTIFIER = 'i_';
/**
* @var \Carbon\Carbon
*/
protected $carbon;
/**
* @var
*/
protected $config;
/**
* @var \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface
*/
protected $repository;
/**
* DaemonKeyUpdateService constructor.
*
* @param \Carbon\Carbon $carbon
* @param \Illuminate\Contracts\Config\Repository $config
* @param \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface $repository
*/
public function __construct(
Carbon $carbon,
ConfigRepository $config,
DaemonKeyRepositoryInterface $repository
) {
$this->carbon = $carbon;
$this->config = $config;
$this->repository = $repository;
}
/**
* Update a daemon key to expire the previous one.
*
* @param \Pterodactyl\Models\DaemonKey|int $key
* @return string
*
* @throws \RuntimeException
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function handle($key)
{
if ($key instanceof DaemonKey) {
$key = $key->id;
}
$secret = self::INTERNAL_TOKEN_IDENTIFIER . str_random(40);
$this->repository->withoutFresh()->update($key, [
'secret' => $secret,
'expires_at' => $this->carbon->now()->addMinutes($this->config->get('pterodactyl.api.key_expire_time')),
]);
return $secret;
}
}

View File

@ -24,9 +24,12 @@
namespace Pterodactyl\Services\Servers; namespace Pterodactyl\Services\Servers;
use Carbon\Carbon;
use Pterodactyl\Models\User; use Pterodactyl\Models\User;
use Pterodactyl\Models\Server; use Pterodactyl\Models\Server;
use Pterodactyl\Models\DaemonKey;
use Illuminate\Cache\Repository as CacheRepository; use Illuminate\Cache\Repository as CacheRepository;
use Pterodactyl\Services\DaemonKeys\DaemonKeyUpdateService;
use Pterodactyl\Contracts\Repository\UserRepositoryInterface; use Pterodactyl\Contracts\Repository\UserRepositoryInterface;
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
use Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface; use Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface;
@ -39,11 +42,21 @@ class ServerAccessHelperService
*/ */
protected $cache; protected $cache;
/**
* @var \Carbon\Carbon
*/
protected $carbon;
/** /**
* @var \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface * @var \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface
*/ */
protected $daemonKeyRepository; protected $daemonKeyRepository;
/**
* @var \Pterodactyl\Services\DaemonKeys\DaemonKeyUpdateService
*/
protected $daemonKeyUpdateService;
/** /**
* @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface
*/ */
@ -58,18 +71,24 @@ class ServerAccessHelperService
* ServerAccessHelperService constructor. * ServerAccessHelperService constructor.
* *
* @param \Illuminate\Cache\Repository $cache * @param \Illuminate\Cache\Repository $cache
* @param \Carbon\Carbon $carbon
* @param \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface $daemonKeyRepository * @param \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface $daemonKeyRepository
* @param \Pterodactyl\Services\DaemonKeys\DaemonKeyUpdateService $daemonKeyUpdateService
* @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository
* @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $userRepository * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $userRepository
*/ */
public function __construct( public function __construct(
CacheRepository $cache, CacheRepository $cache,
Carbon $carbon,
DaemonKeyRepositoryInterface $daemonKeyRepository, DaemonKeyRepositoryInterface $daemonKeyRepository,
DaemonKeyUpdateService $daemonKeyUpdateService,
ServerRepositoryInterface $repository, ServerRepositoryInterface $repository,
UserRepositoryInterface $userRepository UserRepositoryInterface $userRepository
) { ) {
$this->cache = $cache; $this->cache = $cache;
$this->carbon = $carbon;
$this->daemonKeyRepository = $daemonKeyRepository; $this->daemonKeyRepository = $daemonKeyRepository;
$this->daemonKeyUpdateService = $daemonKeyUpdateService;
$this->repository = $repository; $this->repository = $repository;
$this->userRepository = $userRepository; $this->userRepository = $userRepository;
} }
@ -81,8 +100,10 @@ class ServerAccessHelperService
* @param int|\Pterodactyl\Models\User $user * @param int|\Pterodactyl\Models\User $user
* @return string * @return string
* *
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
* @throws \Pterodactyl\Exceptions\Service\Server\UserNotLinkedToServerException * @throws \Pterodactyl\Exceptions\Service\Server\UserNotLinkedToServerException
* @throws \RuntimeException
*/ */
public function handle($server, $user) public function handle($server, $user)
{ {
@ -95,16 +116,16 @@ class ServerAccessHelperService
} }
$keys = $server->relationLoaded('keys') ? $server->keys : $this->daemonKeyRepository->getServerKeys($server->id); $keys = $server->relationLoaded('keys') ? $server->keys : $this->daemonKeyRepository->getServerKeys($server->id);
$key = $keys->where('user_id', $user->root_admin ? $server->owner_id : $user->id)->first();
$key = array_get($keys->where('user_id', $user->id)->first(null, []), 'secret');
if ($user->root_admin) {
$key = array_get($keys->where('user_id', $server->owner_id)->first(null, []), 'secret');
}
if (is_null($key)) { if (is_null($key)) {
throw new UserNotLinkedToServerException; throw new UserNotLinkedToServerException;
} }
return $key; if (max($this->carbon->now()->diffInSeconds($key->expires_at, false), 0) === 0) {
$key = $this->daemonKeyUpdateService->handle($key);
}
return ($key instanceof DaemonKey) ? $key->secret : $key;
} }
} }

View File

@ -24,6 +24,7 @@
namespace Pterodactyl\Transformers\Daemon; namespace Pterodactyl\Transformers\Daemon;
use Carbon\Carbon;
use Pterodactyl\Models\DaemonKey; use Pterodactyl\Models\DaemonKey;
use Pterodactyl\Models\Permission; use Pterodactyl\Models\Permission;
use League\Fractal\TransformerAbstract; use League\Fractal\TransformerAbstract;
@ -31,6 +32,11 @@ use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface;
class ApiKeyTransformer extends TransformerAbstract class ApiKeyTransformer extends TransformerAbstract
{ {
/**
* @var \Carbon\Carbon
*/
protected $carbon;
/** /**
* @var \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface * @var \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface
*/ */
@ -39,10 +45,12 @@ class ApiKeyTransformer extends TransformerAbstract
/** /**
* ApiKeyTransformer constructor. * ApiKeyTransformer constructor.
* *
* @param \Carbon\Carbon $carbon
* @param \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface $repository * @param \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface $repository
*/ */
public function __construct(SubuserRepositoryInterface $repository) public function __construct(Carbon $carbon, SubuserRepositoryInterface $repository)
{ {
$this->carbon = $carbon;
$this->repository = $repository; $this->repository = $repository;
} }
@ -59,6 +67,8 @@ class ApiKeyTransformer extends TransformerAbstract
if ($key->user_id === $key->server->owner_id) { if ($key->user_id === $key->server->owner_id) {
return [ return [
'id' => $key->server->uuid, 'id' => $key->server->uuid,
'is_temporary' => true,
'expires_in' => max($this->carbon->now()->diffInSeconds($key->expires_at, false), 0),
'permissions' => ['s:*'], 'permissions' => ['s:*'],
]; ];
} }
@ -76,7 +86,10 @@ class ApiKeyTransformer extends TransformerAbstract
} }
return [ return [
$key->server->uuid => $daemonPermissions, 'id' => $key->server->uuid,
'is_temporary' => true,
'expires_in' => max($this->carbon->now()->diffInSeconds($key->expires_at, false), 0),
'permissions' => $daemonPermissions,
]; ];
} }
} }

View File

@ -59,6 +59,7 @@ return [
*/ */
'api' => [ 'api' => [
'include_on_list' => env('API_INCLUDE_ON_LIST', false), 'include_on_list' => env('API_INCLUDE_ON_LIST', false),
'key_expire_time' => env('API_KEY_EXPIRE_TIME', 60 * 12),
], ],
/* /*