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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ public/vendor/
!public/assets
.phpunit.result.cache
.phpunit.cache/
app/Http/Controllers/Auth/LoginController.php
280 changes: 280 additions & 0 deletions app/Http/Controllers/Api/KeaController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\Rede;
use App\Models\Equipamento;
use App\Models\Config;
use App\Utils\NetworkOps;
use App\Utils\Utils;
use IPTools\Network;

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

$date = Utils::ItensUpdatedAt();

$dhcp_global = Config::where('key', 'dhcp_global')->first();
$globalOptions = $this->parseGlobalOptions($dhcp_global->value ?? '');

$keaConfig = [
'Dhcp4' => [
'interfaces-config' => [
'interfaces' => ['*'], // ou defina uma interface específica via config
],
'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' => $globalOptions, // opções globais
'shared-networks' => [],
],
];

$sharedNetworkConfig = Config::where('key', 'shared_network')->first();
if (empty($sharedNetworkConfig)) {
// Todas as redes com active_dhcp=1 vão para a shared-network "default"
$redes = Rede::where('active_dhcp', 1)->get();
if ($redes->isNotEmpty()) {
$keaConfig['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()) {
$keaConfig['Dhcp4']['shared-networks'][] = $this->buildSharedNetwork($sn, $redes);
}
}
}

return response()->json($keaConfig, 200, ['Content-Type' => 'application/json']);
}

private function buildSharedNetwork(string $name, $redes)
{
$sharedNetwork = [
'name' => $name,
'subnet4' => [],
'option-data' => [],
];

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

return $sharedNetwork;
}

private function buildSubnet(Rede $rede)
{
$mask = (string) Network::parse("{$rede->iprede}/{$rede->cidr}")->netmask;
$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);

$options = [];

$options[] = [
'name' => 'routers',
'data' => $rede->gateway,
];
$options[] = [
'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,
];
}

if (!empty($rede->dhcpd_subnet_options)) {
// As opções customizadas vêm como texto bruto (ex: "option ntp-servers 10.0.0.1;")
// Para Kea, precisamos converter para JSON; aqui simplificamos: ignoramos ou logamos.
// Se necessário, implemente um parser básico.
}

// Reservas de host (equipamentos com IP fixo)
$reservations = [];
foreach ($rede->equipamentos as $equip) {
if (!empty($equip->macaddress) && !empty($equip->ip)) {
$reservations[] = [
'hw-address' => $equip->macaddress,
'ip-address' => $equip->ip,
];
}
}

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

return $subnet;
}

/**
* Converte as opções globais do dhcp_global para o formato Kea option-data
* Exemplo de entrada: "option domain-name-servers 8.8.8.8;\noption ntp-servers 0.0.0.0;"
*/
private function parseGlobalOptions($globalConfigText)
{
$options = [];
// Parse simplificado: procura linhas com "option <nome> <valor>;"
$lines = explode("\n", $globalConfigText);
foreach ($lines as $line) {
if (preg_match('/^\s*option\s+([a-z0-9\-]+)\s+(.+?);\s*$/i', $line, $matches)) {
$name = $matches[1];
$value = trim($matches[2]);
$options[] = [
'name' => $name,
'data' => $value,
];
}
}
return $options;
}

/**
* Geração de configuração Kea para rede única (não segmentada)
* Baseado em uniquedhcpd()
*/
public function uniquekeab(Request $request)
{
if ($request->consumer_deploy_key != config('copaco.consumer_deploy_key')) {
return response()->json(['error' => 'Unauthorized action.'], 403);
}

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

if (is_null($iprede) || is_null($gateway) || is_null($cidr)) {
return response()->json(['error' => 'Missing network data'], 403);
}

$iprede = $iprede->value;
$gateway = $gateway->value;
$cidr = $cidr->value;
$mask = NetworkOps::findNetmask($cidr);
$broadcast = NetworkOps::findBroadcast($iprede, $cidr);
$rangeBegin = NetworkOps::findFirstIP($iprede, $cidr);
$rangeEnd = NetworkOps::findLastIP($iprede, $cidr);

$ipsReservadosConfig = Config::where('key', 'ips_reservados')->first();
$reservedIps = [];
if ($ipsReservadosConfig) {
$reservedIps = array_map('trim', explode(',', $ipsReservadosConfig->value));
}

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

$dhcp_global = Config::where('key', 'dhcp_global')->first();
$globalOptions = $this->parseGlobalOptions($dhcp_global->value ?? '');

$keaConfig = [
'Dhcp4' => [
'interfaces-config' => ['interfaces' => ['*']],
'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' => $globalOptions,
'subnet4' => [
[
'subnet' => "{$iprede}/{$cidr}",
'pools' => [
['pool' => "{$rangeBegin} - {$rangeEnd}"],
],
'option-data' => [
['name' => 'routers', 'data' => $gateway],
['name' => 'broadcast-address', 'data' => $broadcast],
],
'reservations' => $reservations,
],
],
],
];

return response()->json($keaConfig, 200, ['Content-Type' => 'application/json']);
}
}
2 changes: 2 additions & 0 deletions app/Http/Controllers/Auth/LoginController.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ public function handleProviderCallback(Request $request)
}

}
// para desenvolvimento, permitir login de qualquer usuário, remover ao dar commit final
$login = true;

if (!$login) {
$request->session()->flash('alert-danger', 'Usuário sem acesso ao sistema.');
Expand Down
12 changes: 12 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,17 @@ services:
- copaco-network
shm_size: 2gb

senhaunica-faker:
image: uspdev/senhaunica-faker
container_name: copaco_senhaunica-faker
ports:
- "3141:3141"
environment:
- APP_URL=http://auth.local:3141
networks:
copaco-network:
aliases:
- auth.local

networks:
copaco-network:
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
3 changes: 3 additions & 0 deletions routes/api.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
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']);
Loading
Loading