coolify/app/Services/Flux/AgentTokenIssuer.php
Andras Bacsai e3e91b7741 feat(v5): sync server status and ingress access
Add Flux agent token issuing, open and revoke Caddy ingress firewall
rules, broadcast server-scoped application status updates, and update
the dashboard to show unreachable servers as unknown.
2026-06-22 11:56:26 +02:00

75 lines
2.2 KiB
PHP

<?php
namespace App\Services\Flux;
use App\Models\V5\Server as V5Server;
use Firebase\JWT\JWT;
use Illuminate\Support\Facades\File;
use RuntimeException;
class AgentTokenIssuer
{
public const DEFAULT_PROFILE = 'host-agent:default';
/**
* @param array<int, string> $capabilities
* @param array<string, mixed> $extraClaims
*/
public function issue(string $hostId, array $capabilities = [self::DEFAULT_PROFILE], int $ttl = 86400, array $extraClaims = []): string
{
if ($hostId === '') {
throw new RuntimeException('Flux host id is required.');
}
$privateKeyPath = config('flux.jwt_private_key_path');
if (! is_string($privateKeyPath) || $privateKeyPath === '' || ! File::isReadable($privateKeyPath)) {
throw new RuntimeException("Flux JWT private key not found at {$privateKeyPath}.");
}
$now = time();
return JWT::encode(array_merge($extraClaims, [
'sub' => $hostId,
'aud' => 'coold',
'caps' => $this->normalizeCapabilities($capabilities),
'iat' => $now,
'exp' => $now + max(60, $ttl),
]), File::get($privateKeyPath), 'ES256');
}
public function issueForServer(V5Server $server, int $ttl = 86400): string
{
$hostId = $server->wireguard_management_ip ?: $server->node_address;
if (! is_string($hostId) || $hostId === '') {
throw new RuntimeException('Server is missing its Flux host id.');
}
return $this->issue($hostId, [self::DEFAULT_PROFILE], $ttl, [
'team_id' => $server->team_id,
'cluster_id' => $server->cluster_id,
'server_id' => $server->id,
]);
}
/**
* @param array<int, string> $capabilities
* @return array<int, string>
*/
private function normalizeCapabilities(array $capabilities): array
{
$normalized = collect($capabilities)
->map(fn (string $capability) => trim($capability))
->filter()
->unique()
->values()
->all();
if ($normalized === []) {
return [self::DEFAULT_PROFILE];
}
return $normalized;
}
}