Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ FREERADIUS_MACADDR_CASE=lower
CODIGO_ACESSO='ABCD-1234'
SOMENTE_SENHAUNICA=false

USP_THEME_SKIN=uspdev
USP_THEME_SKIN=fflch

# Dusk
APP_URL=http://copaco
Expand Down
46 changes: 46 additions & 0 deletions app/Http/Controllers/Api/KeaController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Http\Services\KeaService;

class KeaController extends Controller
{
protected $keaService;

public function __construct(KeaService $keaService)
{
$this->keaService = $keaService;
}

public function kea(Request $request)
{
if ($request->consumer_deploy_key != config('copaco.consumer_deploy_key')) {
return response()->json(['error' => 'Unauthorized action.'], 403);
}

try {
$config = $this->keaService->generateKeaConfig();
return response()->json($config, 200, ['Content-Type' => 'application/json']);
} catch (\Exception $e) {
return response()->json(['error' => $e->getMessage()], 500);
}
}


public function uniquekeab(Request $request)
{
if ($request->consumer_deploy_key != config('copaco.consumer_deploy_key')) {
return response()->json(['error' => 'Unauthorized action.'], 403);
}

try {
$config = $this->keaService->generateUniqueKeaConfig();
return response()->json($config, 200, ['Content-Type' => 'application/json']);
} catch (\Exception $e) {
return response()->json(['error' => $e->getMessage()], 500);
}
}
}
223 changes: 223 additions & 0 deletions app/Http/Services/KeaService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
<?php

namespace App\Http\Services;

use App\Models\Rede;
use App\Models\Equipamento;
use App\Models\Config;
use App\Utils\NetworkOps;
use App\Utils\Utils;
use IPTools\Network;

class KeaService
{

/* Estrutura base do Kea Dhcp4*/

private function baseConfig(): array
{
return [
'Dhcp4' => [
'interfaces-config' => [
'interfaces' => ['*'], // pode vir de config futuramente
],
'lease-database' => [
'type' => 'memfile',
'name' => '/var/lib/kea/kea-leases4.csv',
],
'control-socket' => [
'socket-type' => 'unix',
'socket-name' => '/tmp/kea4-ctrl-socket',
],
'loggers' => [
[
'name' => 'kea-dhcp4',
'severity' => 'INFO',
'output_options' => [
['output' => '/var/log/kea/kea-dhcp4.log'],
],
],
],
'option-data' => [], // será preenchido depois
],
];
}

public function generateKeaConfig(): array
{
$config = $this->baseConfig();
$config['Dhcp4']['option-data'] = $this->parseGlobalOptions();

$sharedNetworkConfig = Config::where('key', 'shared_network')->first();

if (empty($sharedNetworkConfig)) {
$redes = Rede::where('active_dhcp', 1)->get();
if ($redes->isNotEmpty()) {
$config['Dhcp4']['shared-networks'][] = $this->buildSharedNetwork('default', $redes);
}
} else {
$sharedNetworksList = array_map('trim', explode(',', $sharedNetworkConfig->value));
if (!in_array('default', $sharedNetworksList)) {
$sharedNetworksList[] = 'default';
}
foreach ($sharedNetworksList as $sn) {
$redes = Rede::where('shared_network', $sn)->where('active_dhcp', 1)->get();
if ($redes->isNotEmpty()) {
$config['Dhcp4']['shared-networks'][] = $this->buildSharedNetwork($sn, $redes);
}
}
}

return $config;
}


public function generateUniqueKeaConfig(): array
{
$config = $this->baseConfig();
$config['Dhcp4']['option-data'] = $this->parseGlobalOptions();

$iprede = Config::where('key', 'unique_iprede')->value('value');
$gateway = Config::where('key', 'unique_gateway')->value('value');
$cidr = Config::where('key', 'unique_cidr')->value('value');

if (!$iprede || !$gateway || !$cidr) {
throw new \Exception('Missing network data for unique configuration');
}

$broadcast = NetworkOps::findBroadcast($iprede, $cidr);
$rangeBegin = NetworkOps::findFirstIP($iprede, $cidr);
$rangeEnd = NetworkOps::findLastIP($iprede, $cidr);

// Opções específicas da subnet
$subnetOptions = [
['name' => 'routers', 'data' => $gateway],
['name' => 'broadcast-address', 'data' => $broadcast],
];

// Reservas para equipamentos (alocação automática)
$reservations = $this->buildReservationsForUnique($iprede, $cidr, $gateway);

$config['Dhcp4']['subnet4'] = [
[
'subnet' => "{$iprede}/{$cidr}",
'pools' => [
['pool' => "{$rangeBegin} - {$rangeEnd}"],
],
'option-data' => $subnetOptions,
'reservations' => $reservations,
],
];

return $config;
}


private function buildSharedNetwork(string $name, $redes): array
{
$sharedNetwork = [
'name' => $name,
'subnet4' => [],
'option-data' => [], // opções comuns a todas subnets (se houver)
];

foreach ($redes as $rede) {
$sharedNetwork['subnet4'][] = $this->buildSubnet($rede);
}

return $sharedNetwork;
}

private function buildSubnet(Rede $rede): array
{
$subnetCidr = "{$rede->iprede}/{$rede->cidr}";
$rangeBegin = NetworkOps::findFirstIP($rede->iprede, $rede->cidr);
$rangeEnd = NetworkOps::findLastIP($rede->iprede, $rede->cidr);
$broadcast = NetworkOps::findBroadcast($rede->iprede, $rede->cidr);

// Opções DHCP para esta subnet
$options = [
['name' => 'routers', 'data' => $rede->gateway],
['name' => 'broadcast-address', 'data' => $broadcast],
];

if (!empty($rede->netbios)) {
$options[] = ['name' => 'netbios-name-servers', 'data' => $rede->netbios];
}
if (!empty($rede->ntp)) {
$options[] = ['name' => 'ntp-servers', 'data' => $rede->ntp];
}
if (!empty($rede->dns)) {
$options[] = ['name' => 'domain-name-servers', 'data' => $rede->dns];
}
if (!empty($rede->ad_domain)) {
$options[] = ['name' => 'domain-name', 'data' => $rede->ad_domain];
}


$reservations = [];
foreach ($rede->equipamentos as $equip) {
if (!empty($equip->macaddress) && !empty($equip->ip)) {
$reservations[] = [
'hw-address' => $equip->macaddress,
'ip-address' => $equip->ip,
];
}
}

return [
'subnet' => $subnetCidr,
'pools' => [
['pool' => "{$rangeBegin} - {$rangeEnd}"],
],
'option-data' => $options,
'reservations' => $reservations,
];
}

private function buildReservationsForUnique(string $iprede, string $cidr, string $gateway): array
{
$reservedIpsConfig = Config::where('key', 'ips_reservados')->first();
$reservedIps = $reservedIpsConfig ? array_map('trim', explode(',', $reservedIpsConfig->value)) : [];

$equipamentos = Equipamento::all();
$reservations = [];
$ipsAlocados = $reservedIps;

foreach ($equipamentos as $equip) {
if (empty($equip->macaddress)) {
continue;
}
$ip = NetworkOps::nextIpAvailable($ipsAlocados, $iprede, $cidr, $gateway);
if ($ip) {
$reservations[] = [
'hw-address' => $equip->macaddress,
'ip-address' => $ip,
];
$ipsAlocados[] = $ip;
}
}

return $reservations;
}

private function parseGlobalOptions(): array
{
$dhcpGlobal = Config::where('key', 'dhcp_global')->first();
if (!$dhcpGlobal || empty($dhcpGlobal->value)) {
return [];
}

$options = [];
$lines = explode("\n", $dhcpGlobal->value);
foreach ($lines as $line) {
if (preg_match('/^\s*option\s+([a-z0-9\-]+)\s+(.+?);\s*$/i', $line, $matches)) {
$options[] = [
'name' => $matches[1],
'data' => trim($matches[2]),
];
}
}
return $options;
}
}
20 changes: 20 additions & 0 deletions resources/views/config/index.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,26 @@
@stop

@section('content')
<div>
<form action="/api/kea" method="post">
{{csrf_field()}}
<input type="hidden" name="consumer_deploy_key" value="{{ $consumer_deploy_key }}">
<button class="btn btn-info" type="submit">Gerar configuração Kea (JSON) - Redes segmentadas</button>
</form>
</div>

<br />

<div>
<form action="/api/kea/unique" method="post">
{{csrf_field()}}
<input type="hidden" name="consumer_deploy_key" value="{{ $consumer_deploy_key }}">
<button class="btn btn-info" type="submit">Gerar configuração Kea única (JSON) - Sem segmentação</button>
</form>
</div>

<br />

<div>
<form action="/api/dhcpd.conf" method="post">
{{csrf_field()}}
Expand Down
4 changes: 4 additions & 0 deletions routes/api.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

use App\Http\Controllers\Api\DhcpController;
use App\Http\Controllers\Api\FreeradiusController;
use App\Http\Controllers\Api\EquipamentoController;
use App\Http\Controllers\Api\KeaController;

Route::post('/dhcpd.conf', [DhcpController::class, 'dhcpd']);
Route::post('/uniquedhcpd.conf', [DhcpController::class, 'uniquedhcpd']);
Route::post('/freeradius/authorize_file', [FreeradiusController::class, 'authorize_file']);
Route::post('/kea', [KeaController::class, 'kea']);
Route::post('/kea/unique', [KeaController::class, 'uniquekeab']);
6 changes: 6 additions & 0 deletions tests/Browser/RedeCrudTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ public function test_rede_crud()
->pause(1000)
->assertSee('Rede Teste Editada');

// Gerar Keadhcp
$browser->visit("/config")
->press('Gerar configuração Kea (JSON) - Redes segmentadas')
->pause(2000)
->assertSee('141.232.67.1');

// Delete
$browser->visit('/redes')
->click("form[action$='/redes/{$rede->id}'] button.delete-item")
Expand Down
Loading