diff --git a/src/OgBootBundle/Controller/OgBootController.php b/src/OgBootBundle/Controller/OgBootController.php index 93766f1..3e20ef5 100644 --- a/src/OgBootBundle/Controller/OgBootController.php +++ b/src/OgBootBundle/Controller/OgBootController.php @@ -871,176 +871,139 @@ public function getBootFiles(): JsonResponse */ -public function getBootFile(string $mac): Response -{ - // Ruta donde están alojados los archivos de arranque - $directory = '/opt/ogboot/tftpboot/ipxe_scripts'; - - // Generar el nombre del archivo basado en la dirección MAC - $mac = $this->validateAndFormatMac($mac ?? null); - $fileName = "01-" . $mac; - $filePath = "$directory/$fileName"; - $this->logger->info('FilePath: ' . $filePath); - // Verificar si el archivo existe - if (!file_exists($filePath)) { - return new JsonResponse( - ['error' => 'NOT_FOUND', 'message' => 'No boot file found for the specified MAC address.'], - Response::HTTP_NOT_FOUND - ); - } - - $content = file_get_contents($filePath); - $templateName = 'unknown'; // Valor por defecto si no se encuentra la plantilla - $contentLines = explode("\n", $content); - - // Buscar la plantilla utilizada - foreach ($contentLines as $line) { - if (strpos($line, '#Template:') !== false) { - $templateName = trim(str_replace('#Template:', '', $line)); - break; - } - } - - // Extraer los parámetros del contenido del archivo iPXE - preg_match('/set kernelargs (.*)/', $content, $matches); - - // Si no se encuentran 'kernelargs', podría ser un archivo de arranque por disco - if (!isset($matches[1])) { - return new JsonResponse( - [ - 'success' => 'Boot file retrieved successfully', - 'message' => [ - 'template_name' => $templateName, - 'mac' => $mac, - 'message' => 'Boot file without parameters, possibly a disk boot template.' - ] - ], - Response::HTTP_OK - ); - } - - // Parsear los argumentos del kernel - $kernelargs = $matches[1]; - parse_str(str_replace(' ', '&', $kernelargs), $params); - - // Crear la estructura del resultado - $result = [ - 'template_name' => $templateName, - 'mac' => $mac, - 'lang' => $params['LANG'] ?? '', - 'ip' => explode(':', $params['ip'])[0] ?? '', - 'server_ip' => explode(':', $params['ip'])[1] ?? '', - 'router' => explode(':', $params['ip'])[2] ?? '', - 'netmask' => explode(':', $params['ip'])[3] ?? '', - 'computer_name' => explode(':', $params['ip'])[4] ?? '', - 'netiface' => explode(':', $params['ip'])[5] ?? '', - 'group' => $params['group'] ?? '', - 'ogrepo' => $params['ogrepo'] ?? '', - 'oglive' => $params['oglive'] ?? '', - 'oglog' => $params['oglog'] ?? '', - 'ogshare' => $params['ogshare'] ?? '', - 'oglivedir' => $params['oglivedir'] ?? '', - 'ogprof' => $params['ogprof'] ?? '', - 'hardprofile' => $params['hardprofile'] ?? '', - 'ogntp' => $params['ogntp'] ?? '', - 'ogdns' => $params['ogdns'] ?? '', - 'ogproxy' => $params['ogproxy'] ?? '', - 'ogunit' => $params['ogunit'] ?? '', - 'resolution' => $params['vga'] ?? '', - ]; - - // Formatear la respuesta de éxito - return new JsonResponse( - ['success' => 'Boot file retrieved successfully', 'message' => $result], - Response::HTTP_OK - ); -} + public function getBootFile(string $mac): Response + { + // Ruta donde están alojados los archivos de arranque + $directory = '/opt/ogboot/tftpboot/ipxe_scripts'; + + // Generar el nombre del archivo basado en la dirección MAC + $mac = $this->validateAndFormatMac($mac ?? null); + $fileName = "01-" . $mac; + $filePath = "$directory/$fileName"; + + // Normalizar la ruta del archivo para evitar accesos fuera del directorio permitido + $realFilePath = realpath($directory) . '/' . $fileName; + + // Verificar que el archivo está dentro del directorio permitido + if (strpos($realFilePath, realpath($directory)) !== 0) { + return new JsonResponse( + [ + 'error' => 'UNAUTHORIZED_ACCESS', + 'message' => 'Intento de acceso no autorizado fuera del directorio de arranque.' + ], + Response::HTTP_FORBIDDEN + ); + } + + // Verificar si el archivo existe + if (!file_exists($realFilePath)) { + return new JsonResponse( + ['error' => 'NOT_FOUND', 'message' => 'No boot file found for the specified MAC address.'], + Response::HTTP_NOT_FOUND + ); + } + + $content = file_get_contents($realFilePath); + $templateName = 'unknown'; // Valor por defecto si no se encuentra la plantilla + $contentLines = explode("\n", $content); + + // Buscar la plantilla utilizada + foreach ($contentLines as $line) { + if (strpos($line, '#Template:') !== false) { + $templateName = trim(str_replace('#Template:', '', $line)); + break; + } + } + + // Extraer los parámetros del contenido del archivo iPXE + preg_match('/set kernelargs (.*)/', $content, $matches); + + // Si no se encuentran 'kernelargs', podría ser un archivo de arranque por disco + if (!isset($matches[1])) { + return new JsonResponse( + [ + 'success' => 'Boot file retrieved successfully', + 'message' => [ + 'template_name' => $templateName, + 'mac' => $mac, + 'message' => 'Boot file without parameters, possibly a disk boot template.' + ] + ], + Response::HTTP_OK + ); + } + + // Parsear los argumentos del kernel + $kernelargs = $matches[1]; + parse_str(str_replace(' ', '&', $kernelargs), $params); + + // Crear la estructura del resultado + $result = [ + 'template_name' => $templateName, + 'mac' => $mac, + 'lang' => $params['LANG'] ?? '', + 'ip' => explode(':', $params['ip'])[0] ?? '', + 'server_ip' => explode(':', $params['ip'])[1] ?? '', + 'router' => explode(':', $params['ip'])[2] ?? '', + 'netmask' => explode(':', $params['ip'])[3] ?? '', + 'computer_name' => explode(':', $params['ip'])[4] ?? '', + 'netiface' => explode(':', $params['ip'])[5] ?? '', + 'group' => $params['group'] ?? '', + 'ogrepo' => $params['ogrepo'] ?? '', + 'oglive' => $params['oglive'] ?? '', + 'oglog' => $params['oglog'] ?? '', + 'ogshare' => $params['ogshare'] ?? '', + 'oglivedir' => $params['oglivedir'] ?? '', + 'ogprof' => $params['ogprof'] ?? '', + 'hardprofile' => $params['hardprofile'] ?? '', + 'ogntp' => $params['ogntp'] ?? '', + 'ogdns' => $params['ogdns'] ?? '', + 'ogproxy' => $params['ogproxy'] ?? '', + 'ogunit' => $params['ogunit'] ?? '', + 'resolution' => $params['vga'] ?? '', + ]; + + // Formatear la respuesta de éxito + return new JsonResponse( + ['success' => 'Boot file retrieved successfully', 'message' => $result], + Response::HTTP_OK + ); + } + /** - * @Route("/ogboot/v1/pxes", name="create_boot_file", methods={"POST"}) + * @Route("/ogboot/v1/pxes", methods={"POST"}) * @OA\Post( * path="/ogboot/v1/pxes", - * summary="Create a new PXE boot file", - * description="Creates a PXE boot file based on the provided template and parameters. All parameters except 'template_name' and 'mac' are optional.", + * summary="Crear archivo PXE", + * description="Crea un archivo de arranque PXE con la MAC y plantilla proporcionadas.", * @OA\RequestBody( * required=true, * @OA\JsonContent( * type="object", - * @OA\Property(property="template_name", type="string", description="Template name (required)", example="pxe"), - * @OA\Property(property="mac", type="string", description="MAC address (required)", example="00:50:56:22:11:12"), - * @OA\Property(property="lang", type="string", description="Language (optional)", example="es_ES.UTF-8"), - * @OA\Property(property="ip", type="string", description="IP address (optional)", example="192.168.2.11"), - * @OA\Property(property="server_ip", type="string", description="Server IP address (optional)", example="192.168.2.1"), - * @OA\Property(property="router", type="string", description="Router (optional)", example="192.168.2.1"), - * @OA\Property(property="netmask", type="string", description="Netmask (optional)", example="255.255.255.0"), - * @OA\Property(property="computer_name", type="string", description="Computer name (optional)", example="pc11"), - * @OA\Property(property="netiface", type="string", description="Network interface (optional)", example="eth0"), - * @OA\Property(property="group", type="string", description="Group (optional)", example="Aula_virtual"), - * @OA\Property(property="ogrepo", type="string", description="Repository IP (optional)", example="192.168.2.1"), - * @OA\Property(property="oglive", type="string", description="Live server IP (optional)", example="192.168.2.1"), - * @OA\Property(property="oglog", type="string", description="Log server IP (optional)", example="192.168.2.1"), - * @OA\Property(property="ogshare", type="string", description="Share server IP (optional)", example="192.168.2.1"), - * @OA\Property(property="oglivedir", type="string", description="Live directory (optional)", example="ogLive"), - * @OA\Property(property="ogprof", type="string", description="Is professor (optional)", example="false"), - * @OA\Property(property="hardprofile", type="string", description="Hardware profile (optional)", example=""), - * @OA\Property(property="ogntp", type="string", description="NTP server (optional)", example=""), - * @OA\Property(property="ogdns", type="string", description="DNS server (optional)", example=""), - * @OA\Property(property="ogproxy", type="string", description="Proxy server (optional)", example=""), - * @OA\Property(property="ogunit", type="string", description="Unit directory (optional)", example=""), - * @OA\Property(property="resolution", type="string", description="Screen resolution (optional)", example="788") + * @OA\Property(property="mac", type="string", example="00:50:56:22:11:12"), + * @OA\Property(property="template_name", type="string", example="mi_plantilla.ipxe"), + * @OA\Property(property="server_ip", type="string", example="192.168.2.1"), + * @OA\Property(property="oglivedir", type="string", example="ogLive") * ) * ), * @OA\Response( * response=200, - * description="PXE boot file created successfully", + * description="Plantilla creada exitosamente", * @OA\JsonContent( * type="object", + * @OA\Property(property="success", type="string", example="Plantilla creada con éxito"), * @OA\Property(property="message", type="string") * ) * ), * @OA\Response( * response=400, - * description="Invalid input", - * @OA\JsonContent( - * oneOf={ - * @OA\Schema( - * @OA\Property(property="error", type="string", example="Missing required fields"), - * @OA\Property(property="details", type="string", example="MAC and template_name are required") - * ), - * @OA\Schema( - * @OA\Property(property="error", type="string", example="Invalid MAC address"), - * @OA\Property(property="details", type="string", example="MAC address contains invalid characters") - * ), - * @OA\Schema( - * @OA\Property(property="error", type="string", example="Template not found"), - * @OA\Property(property="details", type="string", example="The specified template does not exist") - * ) - * } - * ) - * ), - * @OA\Response( - * response=404, - * description="Template not found", - * @OA\JsonContent( - * @OA\Property(property="error", type="string", example="Template not found") - * ) + * description="Entrada inválida o datos requeridos faltantes" * ), * @OA\Response( * response=500, - * description="Internal server error", - * @OA\JsonContent( - * oneOf={ - * @OA\Schema( - * @OA\Property(property="error", type="string", example="Failed to create PXE boot file"), - * @OA\Property(property="details", type="string", example="Error writing to file system") - * ), - * @OA\Schema( - * @OA\Property(property="error", type="string", example="Failed to read template"), - * @OA\Property(property="details", type="string", example="Could not access the template file") - * ) - * } - * ) + * description="Error interno del servidor al crear el archivo PXE" * ) * ) */ @@ -1058,6 +1021,14 @@ public function createBootFile(Request $request): JsonResponse return new JsonResponse(['error' => 'Missing required fields: mac and template_name'], Response::HTTP_BAD_REQUEST); } + // Validación adicional para asegurarnos de que no hay caracteres inseguros + if (!preg_match('/^[a-zA-Z0-9._-]+$/', $templateName) || strpos($templateName, '..') !== false) { + return new JsonResponse( + ['error' => 'INVALID_TEMPLATE_NAME', 'message' => 'Nombre de la plantilla no válido o intento de acceso no autorizado.'], + Response::HTTP_BAD_REQUEST + ); + } + // Parámetros opcionales $parameters = [ 'LANG' => $data['lang'] ?? 'es_ES.UTF-8', @@ -1081,18 +1052,17 @@ public function createBootFile(Request $request): JsonResponse 'resolution' => $data['resolution'] ?? '788' ]; -# $templateDir = '/opt/ogboot/tftpboot/ipxe_scripts/templates'; $templateDir = $this->tftpbootDir . '/ipxe_scripts/templates'; $templatePath = $templateDir . '/' . $templateName; // Verificar si la plantilla existe if (!file_exists($templatePath)) { - return new JsonResponse(['error' => 'Template not found'], Response::HTTP_NOT_FOUND); + return new JsonResponse(['error' => 'TEMPLATE_NOT_FOUND', 'message' => 'No se encontró la plantilla especificada'], Response::HTTP_NOT_FOUND); } $templateContent = file_get_contents($templatePath); if ($templateContent === false) { - return new JsonResponse(['error' => 'Failed to read template'], Response::HTTP_INTERNAL_SERVER_ERROR); + return new JsonResponse(['error' => 'FAILED_TO_READ_TEMPLATE', 'message' => 'Error al leer la plantilla'], Response::HTTP_INTERNAL_SERVER_ERROR); } // Construcción de los argumentos del kernel @@ -1123,10 +1093,8 @@ public function createBootFile(Request $request): JsonResponse // Insertar el comentario con el nombre de la plantilla después de #!ipxe if (strpos($pxeContent, '#!ipxe') === 0) { - // Añadir el comentario después de #!ipxe $pxeContent = "#!ipxe\n#Template: $templateName\n" . substr($pxeContent, 6); } else { - // Si no encuentra #!ipxe, agregarlo al principio seguido del comentario $pxeContent = "#!ipxe\n#Template: $templateName\n" . $pxeContent; } @@ -1134,19 +1102,20 @@ public function createBootFile(Request $request): JsonResponse $pxeFileName = '01-' . $mac; // Ruta para guardar el archivo PXE - #$pxeFilePath = '/opt/ogboot/tftpboot/ipxe_scripts/' . $pxeFileName; - $pxeFilePath = $this->tftpbootDir . '/ipxe_scripts' . $pxeFileName; + $pxeFilePath = $this->tftpbootDir . '/ipxe_scripts/' . $pxeFileName; + // Crear el archivo PXE if (file_put_contents($pxeFilePath, $pxeContent) === false) { - return new JsonResponse(['error' => 'Failed to create PXE boot file'], Response::HTTP_INTERNAL_SERVER_ERROR); + return new JsonResponse(['error' => 'FAILED_TO_CREATE_PXE_FILE', 'message' => 'Error al crear el archivo PXE'], Response::HTTP_INTERNAL_SERVER_ERROR); } // Retornar la plantilla creada en el formato solicitado return new JsonResponse([ - 'success' => 'Plantilla creada con éxito', + 'success' => 'PXE file created successfully', 'message' => $pxeContent ], Response::HTTP_OK); } + function validateAndFormatMac($mac) { @@ -1391,65 +1360,114 @@ public function getAllTemplates(): JsonResponse * description="La plantilla de arranque se creó exitosamente.", * @OA\JsonContent( * type="object", - * @OA\Property(property="message", type="string", example="Plantilla creada exitosamente"), - * @OA\Property(property="template", type="string", example="mi_plantilla.ipxe") + * @OA\Property(property="success", type="string", example="Plantilla creada con éxito"), + * @OA\Property( + * property="message", + * type="object", + * @OA\Property(property="template_name", type="string", example="mi_plantilla.ipxe"), + * @OA\Property(property="template_content", type="string", example="#!ipxe\nset timeout 0\nset timeout-style hidden\nset ISODIR __OGLIVE__\nset default 0\nset kernelargs __INFOHOST__\n:try_iso\nkernel http://__SERVERIP__/tftpboot/${ISODIR}/ogvmlinuz ${kernelargs} || goto fallback\ninitrd http://__SERVERIP__/tftpboot/${ISODIR}/oginitrd.img\nboot\n\n:fallback\nset ISODIR ogLive\nkernel http://__SERVERIP__/tftpboot/${ISODIR}/ogvmlinuz ${kernelargs}\ninitrd http://__SERVERIP__/tftpboot/${ISODIR}/oginitrd.img\nboot\n") + * ) * ) * ), * @OA\Response( * response=400, - * description="La solicitud no pudo ser procesada debido a un error en los datos proporcionados en el cuerpo de la solicitud." + * description="Datos no válidos. Faltan los parámetros 'name_template' o 'content_template'.", + * @OA\JsonContent( + * type="object", + * @OA\Property(property="error", type="string", example="INVALID_INPUT"), + * @OA\Property(property="message", type="string", example="Faltan datos requeridos: name_template y content_template son necesarios.") + * ) + * ), + * @OA\Response( + * response=403, + * description="Intento de acceso no autorizado fuera del directorio de plantillas.", + * @OA\JsonContent( + * type="object", + * @OA\Property(property="error", type="string", example="UNAUTHORIZED_ACCESS"), + * @OA\Property(property="message", type="string", example="Intento de acceso no autorizado fuera del directorio de plantillas.") + * ) * ), * @OA\Response( * response=500, - * description="Ocurrió un error al crear la plantilla de arranque." + * description="Ocurrió un error al crear la plantilla de arranque.", + * @OA\JsonContent( + * type="object", + * @OA\Property(property="error", type="string", example="FILE_CREATION_ERROR"), + * @OA\Property(property="message", type="string", example="Ocurrió un error al crear la plantilla de arranque.") + * ) * ) * ) */ -public function createTemplate(Request $request) + +public function createTemplate(Request $request): JsonResponse { $data = json_decode($request->getContent(), true); if (!isset($data['name_template']) || !isset($data['content_template'])) { - return new Response('La solicitud no pudo ser procesada debido a un error en los datos proporcionados en el cuerpo de la solicitud.', 400); + return new JsonResponse([ + 'error' => 'INVALID_INPUT', + 'message' => 'Faltan datos requeridos: name_template y content_template son necesarios.' + ], JsonResponse::HTTP_BAD_REQUEST); } $nameTemplate = $data['name_template']; $contentTemplate = $data['content_template']; - #$templateDir = '/opt/ogboot/tftpboot/ipxe_scripts/templates'; $templateDir = $this->tftpbootDir . '/ipxe_scripts/templates'; + // Validar el nombre de la plantilla if (!preg_match('/^[a-zA-Z0-9._-]+$/', $nameTemplate)) { - return new Response('Nombre de la plantilla no válido.', 400); + return new JsonResponse([ + 'error' => 'INVALID_TEMPLATE_NAME', + 'message' => 'El nombre de la plantilla contiene caracteres no válidos.' + ], JsonResponse::HTTP_BAD_REQUEST); } + // Construir la ruta completa del archivo $filePath = $templateDir . '/' . $nameTemplate; + + // Normalizar la ruta del archivo para evitar accesos fuera del directorio permitido + $realFilePath = realpath($templateDir) . '/' . $nameTemplate; + + // Verificar que el archivo está dentro del directorio permitido + if (strpos($realFilePath, realpath($templateDir)) !== 0) { + return new JsonResponse([ + 'error' => 'UNAUTHORIZED_ACCESS', + 'message' => 'Intento de acceso no autorizado fuera del directorio de plantillas.' + ], JsonResponse::HTTP_FORBIDDEN); + } + $contentTemplate = str_replace("\\n", "\n", $contentTemplate); try { // Intentar crear la plantilla - file_put_contents($filePath, $contentTemplate); + file_put_contents($realFilePath, $contentTemplate); // Comprobar si el archivo se ha creado correctamente - if (!file_exists($filePath)) { + if (!file_exists($realFilePath)) { throw new \Exception('El archivo no se pudo crear.'); } // Verificar si el contenido escrito coincide con el contenido esperado - $writtenContent = file_get_contents($filePath); + $writtenContent = file_get_contents($realFilePath); if ($writtenContent !== $contentTemplate) { throw new \Exception('El contenido del archivo no coincide con el contenido esperado.'); } } catch (\Exception $e) { - return new Response('Ocurrió un error al crear la plantilla de arranque. ' . $e->getMessage(), 500); + return new JsonResponse([ + 'error' => 'FILE_CREATION_ERROR', + 'message' => 'Ocurrió un error al crear la plantilla de arranque: ' . $e->getMessage() + ], JsonResponse::HTTP_INTERNAL_SERVER_ERROR); } // Retornar el nombre de la plantilla y su contenido en el formato solicitado - return new Response(json_encode([ + return new JsonResponse([ 'success' => 'Plantilla creada con éxito', - 'template_name' => $nameTemplate, - 'template_content' => $contentTemplate - ]), 200, ['Content-Type' => 'application/json']); + 'message' => [ + 'template_name' => $nameTemplate, + 'template_content' => $contentTemplate + ] + ], JsonResponse::HTTP_OK); } @@ -1532,3 +1550,4 @@ public function deleteTemplate($name): Response } +