Skip to content

Commit

Permalink
Add back auto deploy (#627)
Browse files Browse the repository at this point in the history
* Add Docker, Refactor, Fix Notification

Co-authored-by: notCharles <charles@pelican.dev>

* Pint

* Required adjustments

* Remove deprecated

* Third time's the charm

---------

Co-authored-by: notCharles <charles@pelican.dev>
  • Loading branch information
RMartinOscar and notAreYouScared authored Oct 27, 2024
1 parent 291b514 commit f3de185
Show file tree
Hide file tree
Showing 9 changed files with 245 additions and 98 deletions.
2 changes: 1 addition & 1 deletion app/Filament/Resources/ApiKeyResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class ApiKeyResource extends Resource

public static function getNavigationBadge(): ?string
{
return static::getModel()::where('key_type', '2')->count() ?: null;
return static::getModel()::where('key_type', ApiKey::TYPE_APPLICATION)->count() ?: null;
}

public static function canEdit(Model $record): bool
Expand Down
211 changes: 163 additions & 48 deletions app/Filament/Resources/NodeResource/Pages/EditNode.php

Large diffs are not rendered by default.

44 changes: 7 additions & 37 deletions app/Http/Controllers/Admin/NodeAutoDeployController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,61 +2,31 @@

namespace App\Http\Controllers\Admin;

use Illuminate\Http\Request;
use App\Models\Node;
use App\Models\ApiKey;
use Illuminate\Http\JsonResponse;
use App\Http\Controllers\Controller;
use App\Services\Api\KeyCreationService;
use App\Services\Nodes\NodeAutoDeployService;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;

class NodeAutoDeployController extends Controller
{
/**
* NodeAutoDeployController constructor.
*/
public function __construct(
private KeyCreationService $keyCreationService
private readonly NodeAutoDeployService $nodeAutoDeployService
) {
}

/**
* Generates a new API key for the logged-in user with only permission to read
* nodes, and returns that as the deployment key for a node.
* Handles the API request and returns the deployment command.
*
* @throws \App\Exceptions\Model\DataValidationException
*/
public function __invoke(Request $request, Node $node): JsonResponse
{
$keys = $request->user()->apiKeys()
->where('key_type', ApiKey::TYPE_APPLICATION)
->get();

/** @var ApiKey|null $key */
$key = $keys
->filter(function (ApiKey $key) {
foreach ($key->getAttributes() as $permission => $value) {
if ($permission === 'r_nodes' && $value === 1) {
return true;
}
}

return false;
})
->first();

// We couldn't find a key that exists for this user with only permission for
// reading nodes. Go ahead and create it now.
if (!$key) {
$key = $this->keyCreationService->setKeyType(ApiKey::TYPE_APPLICATION)->handle([
'user_id' => $request->user()->id,
'memo' => 'Automatically generated node deployment key.',
'allowed_ips' => [],
], ['r_nodes' => 1]);
}
$command = $this->nodeAutoDeployService->handle($request, $node);

return new JsonResponse([
'node' => $node->id,
'token' => $key->identifier . $key->token,
]);
return new JsonResponse(['command' => $command]);
}
}
2 changes: 1 addition & 1 deletion app/Http/Controllers/Api/Client/ApiKeyController.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public function index(ClientApiRequest $request): array
*/
public function store(StoreApiKeyRequest $request): array
{
if ($request->user()->apiKeys->count() >= 25) {
if ($request->user()->apiKeys->count() >= config('panel.api.key_limit')) {
throw new DisplayException('You have reached the account limit for number of API keys.');
}

Expand Down
9 changes: 1 addition & 8 deletions app/Models/ApiKey.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,15 +71,8 @@ class ApiKey extends Model

public const TYPE_ACCOUNT = 1;

/* @deprecated */
public const TYPE_APPLICATION = 2;

/* @deprecated */
public const TYPE_DAEMON_USER = 3;

/* @deprecated */
public const TYPE_DAEMON_APPLICATION = 4;

/**
* The length of API key identifiers.
*/
Expand Down Expand Up @@ -138,7 +131,7 @@ class ApiKey extends Model
*/
public static array $validationRules = [
'user_id' => 'required|exists:users,id',
'key_type' => 'present|integer|min:0|max:4',
'key_type' => 'present|integer|min:0|max:2',
'identifier' => 'required|string|size:16|unique:api_keys,identifier',
'token' => 'required|string',
'memo' => 'required|nullable|string|max:500',
Expand Down
58 changes: 58 additions & 0 deletions app/Services/Nodes/NodeAutoDeployService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

namespace App\Services\Nodes;

use App\Models\ApiKey;
use App\Models\Node;
use App\Services\Api\KeyCreationService;
use Illuminate\Http\Request;

class NodeAutoDeployService
{
/**
* NodeAutoDeployService constructor.
*/
public function __construct(
private readonly KeyCreationService $keyCreationService
) {
}

/**
* Generates a new API key for the logged-in user with only permission to read
* nodes, and returns that as the deployment key for a node.
*
* @throws \App\Exceptions\Model\DataValidationException
*/
public function handle(Request $request, Node $node, ?bool $docker = false): ?string
{
/** @var ApiKey|null $key */
$key = ApiKey::query()
->where('key_type', ApiKey::TYPE_APPLICATION)
->where('r_nodes', true)
->first();

// We couldn't find a key that exists for this user with only permission for
// reading nodes. Go ahead and create it now.
if (!$key) {
$key = $this->keyCreationService->setKeyType(ApiKey::TYPE_APPLICATION)->handle([
'memo' => 'Automatically generated node deployment key.',
'user_id' => $request->user()->id,
], ['r_nodes' => true]);
}

$token = $key->identifier . $key->token;

if (!$token) {
return null;
}

return sprintf(
'%s wings configure --panel-url %s --token %s --node %d%s',
$docker ? 'docker compose exec -it' : 'sudo',
config('app.url'),
$token,
$node->id,
$request->isSecure() ? '' : ' --allow-insecure'
);
}
}
5 changes: 5 additions & 0 deletions config/panel.php
Original file line number Diff line number Diff line change
Expand Up @@ -167,4 +167,9 @@
'use_binary_prefix' => env('PANEL_USE_BINARY_PREFIX', true),

'editable_server_descriptions' => env('PANEL_EDITABLE_SERVER_DESCRIPTIONS', true),

'api' => [
'key_limit' => env('API_KEYS_LIMIT', 25),
'key_expire_time' => env('API_KEYS_EXPIRE_TIME', 720),
],
];
8 changes: 7 additions & 1 deletion phpunit.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd" bootstrap="bootstrap/tests.php" colors="true">
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
bootstrap="bootstrap/tests.php"
colors="true"
displayDetailsOnSkippedTests="true"
>
<testsuites>
<testsuite name="Integration">
<directory>./tests/Integration</directory>
Expand Down
4 changes: 2 additions & 2 deletions tests/Integration/Api/Client/ApiKeyControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,14 +96,14 @@ public function testApiKeyCannotSpecifyMoreThanFiftyIps(): void
}

/**
* Test that no more than 25 API keys can exist at any one time for an account. This prevents
* Test that no more than the Max number of API keys can exist at one time for an account. This prevents
* a DoS attack vector against the panel.
*/
public function testApiKeyLimitIsApplied(): void
{
/** @var \App\Models\User $user */
$user = User::factory()->create();
ApiKey::factory()->times(25)->for($user)->create([
ApiKey::factory()->times(config('panel.api.key_limit', 25))->for($user)->create([
'key_type' => ApiKey::TYPE_ACCOUNT,
]);

Expand Down

0 comments on commit f3de185

Please sign in to comment.