Skip to content
Open
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ FREERADIUS_MACADDR_CASE=lower

CODIGO_ACESSO='ABCD-1234'

USP_THEME_SKIN=fflch
USP_THEME_SKIN=uspdev

# 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']);
10 changes: 6 additions & 4 deletions tests/Browser/EquipamentoCrudTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,19 @@ class EquipamentoCrudTest extends DuskTestCase
public function test_equipamento_crud()
{
$this->browse(function (Browser $browser) {
// Login obrigatório
$browser->visit('/login')
->clickLink('Faça login usando senha única USP!');
$browser->visit('/')
->clickLink('login USP');
$browser->waitFor('#loginUsuario')
->type('#loginUsuario', '1111')
->type('#callback', 'http://copaco/callback')
->type('#loginUsuario', '111111')
->press('Login');
// Início do teste crud
//Criação da rede para o teste do equipamento
$browser->visit('/redes')
->Pause(3000)
->assertSee('Adicionar Rede')
->visit('/redes/create')
->Pause(3000)
->assertSee('Cadastrar Rede')
->type('nome', 'Rede Teste')
->type('iprede', '141.232.67.0')
Expand Down
18 changes: 10 additions & 8 deletions tests/Browser/NavegacaoTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,31 @@ class NavegacaoTest extends DuskTestCase
public function test_navegacao()
{
$this->browse(function (Browser $browser) {
$browser->visit('/login')
->clickLink('Faça login usando senha única USP!');
$browser->visit('/')
->clickLink('login USP');
$browser->waitFor('#loginUsuario')
->type('#loginUsuario', '1111')
->type('#callback', 'http://copaco/callback')
->type('#loginUsuario', '111111')
->press('Login');

$browser->visit('/')
->assertSee('você é super administrador');

$browser->visit('/equipamentos')
->Pause(3000)
->assertSee('Equipamentos')
->visit('/redes/create')
->Pause(3000)
->assertSee('Cadastrar Rede')
->visit('/redes')
->Pause(3000)
->assertSee('Adicionar Rede')
->visit('/redes/migrate')
->Pause(3000)
->assertSee('Migração de equipamentos entre redes')
->visit('/config')
->assertSee('Configurações')
->visit('/roles')
->assertSee('Adicionar Grupo')
->visit('/users')
->assertSee('Nome de Usuário');
->Pause(3000)
->assertSee('Configurações');
});
}
}
Loading
Loading