feat(api): add endpoint to execute a command

This commit is contained in:
Andras Bacsai
2024-09-23 11:09:14 +02:00
parent 4d36bc4742
commit 0222aa137d
2 changed files with 33 additions and 10 deletions

View File

@@ -2596,34 +2596,57 @@ class ApplicationsController extends Controller
)] )]
public function execute_command_by_uuid(Request $request) public function execute_command_by_uuid(Request $request)
{ {
$data = $request->validate([ // TODO: Need to review this from security perspective, to not allow arbitrary command execution
'command' => 'required|string|max:255', $allowedFields = ['command'];
]);
$teamId = getTeamIdFromToken(); $teamId = getTeamIdFromToken();
if (is_null($teamId)) { if (is_null($teamId)) {
return invalidTokenResponse(); return invalidTokenResponse();
} }
$uuid = $request->route('uuid'); $uuid = $request->route('uuid');
if (!$uuid) { if (! $uuid) {
return response()->json(['message' => 'UUID is required.'], 400); return response()->json(['message' => 'UUID is required.'], 400);
} }
$application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first(); $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
if (!$application) { if (! $application) {
return response()->json(['message' => 'Application not found.'], 404); return response()->json(['message' => 'Application not found.'], 404);
} }
$return = validateIncomingRequest($request);
if ($return instanceof \Illuminate\Http\JsonResponse) {
return $return;
}
$validator = customApiValidator($request->all(), [
'command' => 'string|required',
]);
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
if ($validator->fails() || ! empty($extraFields)) {
$errors = $validator->errors();
if (! empty($extraFields)) {
foreach ($extraFields as $field) {
$errors->add($field, 'This field is not allowed.');
}
}
return response()->json([
'message' => 'Validation failed.',
'errors' => $errors,
], 422);
}
$container = getCurrentApplicationContainerStatus($application->destination->server, $application->id)->firstOrFail(); $container = getCurrentApplicationContainerStatus($application->destination->server, $application->id)->firstOrFail();
$status = getContainerStatus($application->destination->server, $container['Names']); $status = getContainerStatus($application->destination->server, $container['Names']);
if ('running' !== $status) { if ($status !== 'running') {
return; return response()->json([
'message' => 'Application is not running.',
], 400);
} }
$commands = collect([ $commands = collect([
executeInDocker($container['Names'], $data['command']), executeInDocker($container['Names'], $request->command),
]); ]);
$res = instant_remote_process($commands, $application->destination->server); $res = instant_remote_process(command: $commands, server: $application->destination->server);
return response()->json([ return response()->json([
'message' => 'Command executed.', 'message' => 'Command executed.',

View File

@@ -86,7 +86,7 @@ Route::group([
Route::patch('/applications/{uuid}/envs/bulk', [ApplicationsController::class, 'create_bulk_envs'])->middleware([IgnoreReadOnlyApiToken::class]); Route::patch('/applications/{uuid}/envs/bulk', [ApplicationsController::class, 'create_bulk_envs'])->middleware([IgnoreReadOnlyApiToken::class]);
Route::patch('/applications/{uuid}/envs', [ApplicationsController::class, 'update_env_by_uuid'])->middleware([IgnoreReadOnlyApiToken::class]); Route::patch('/applications/{uuid}/envs', [ApplicationsController::class, 'update_env_by_uuid'])->middleware([IgnoreReadOnlyApiToken::class]);
Route::delete('/applications/{uuid}/envs/{env_uuid}', [ApplicationsController::class, 'delete_env_by_uuid'])->middleware([IgnoreReadOnlyApiToken::class]); Route::delete('/applications/{uuid}/envs/{env_uuid}', [ApplicationsController::class, 'delete_env_by_uuid'])->middleware([IgnoreReadOnlyApiToken::class]);
Route::post('/applications/{uuid}/execute', [ApplicationsController::class, 'execute_command_by_uuid'])->middleware([IgnoreReadOnlyApiToken::class]); // Route::post('/applications/{uuid}/execute', [ApplicationsController::class, 'execute_command_by_uuid'])->middleware([OnlyRootApiToken::class]);
Route::match(['get', 'post'], '/applications/{uuid}/start', [ApplicationsController::class, 'action_deploy'])->middleware([IgnoreReadOnlyApiToken::class]); Route::match(['get', 'post'], '/applications/{uuid}/start', [ApplicationsController::class, 'action_deploy'])->middleware([IgnoreReadOnlyApiToken::class]);
Route::match(['get', 'post'], '/applications/{uuid}/restart', [ApplicationsController::class, 'action_restart'])->middleware([IgnoreReadOnlyApiToken::class]); Route::match(['get', 'post'], '/applications/{uuid}/restart', [ApplicationsController::class, 'action_restart'])->middleware([IgnoreReadOnlyApiToken::class]);