mirror of
https://github.com/yusing/godoxy.git
synced 2026-04-24 01:38:50 +02:00
feat(proxmox): add tail endpoint and enhance journalctl with multi-service support
Add new `/proxmox/tail` API endpoint for streaming file contents from Proxmox nodes and LXC containers via WebSocket. Extend journalctl endpoint to support filtering by multiple services simultaneously. Changes: - Add `GET /proxmox/tail` endpoint supporting node-level and LXC container file tailing - Change `service` parameter from string to array in journalctl endpoints - Add input validation (`checkValidInput`) to prevent command injection - Refactor command formatting with proper shell quoting Security: All command inputs are validated for dangerous characters before
This commit is contained in:
59
internal/proxmox/command_common.go
Normal file
59
internal/proxmox/command_common.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package proxmox
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// checkValidInput checks if the input contains invalid characters.
|
||||
//
|
||||
// The characters are: & | $ ; ' " ` $( ${ < >
|
||||
// These characters are used in the command line to escape the input or to expand variables.
|
||||
// We need to check if the input contains these characters and return an error if it does.
|
||||
// This is to prevent command injection.
|
||||
func checkValidInput(input string) error {
|
||||
if strings.ContainsAny(input, "&|$;'\"`<>") {
|
||||
return fmt.Errorf("input contains invalid characters: %q", input)
|
||||
}
|
||||
if strings.Contains(input, "$(") {
|
||||
return fmt.Errorf("input contains $(: %q", input)
|
||||
}
|
||||
if strings.Contains(input, "${") {
|
||||
return fmt.Errorf("input contains ${: %q", input)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func formatTail(files []string, limit int) (string, error) {
|
||||
for _, file := range files {
|
||||
if err := checkValidInput(file); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
var command strings.Builder
|
||||
command.WriteString("tail -f -q --retry ")
|
||||
for _, file := range files {
|
||||
fmt.Fprintf(&command, " %q ", file)
|
||||
}
|
||||
if limit > 0 {
|
||||
fmt.Fprintf(&command, " -n %d", limit)
|
||||
}
|
||||
return command.String(), nil
|
||||
}
|
||||
|
||||
func formatJournalctl(services []string, limit int) (string, error) {
|
||||
for _, service := range services {
|
||||
if err := checkValidInput(service); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
var command strings.Builder
|
||||
command.WriteString("journalctl -f")
|
||||
for _, service := range services {
|
||||
fmt.Fprintf(&command, " -u %q ", service)
|
||||
}
|
||||
if limit > 0 {
|
||||
fmt.Fprintf(&command, " -n %d", limit)
|
||||
}
|
||||
return command.String(), nil
|
||||
}
|
||||
@@ -39,15 +39,23 @@ func (n *Node) LXCCommand(ctx context.Context, vmid int, command string) (io.Rea
|
||||
|
||||
// LXCJournalctl streams journalctl output for the given service.
|
||||
//
|
||||
// If service is not empty, it will be used to filter the output by service.
|
||||
// If services are not empty, it will be used to filter the output by service.
|
||||
// If limit is greater than 0, it will be used to limit the number of lines of output.
|
||||
func (n *Node) LXCJournalctl(ctx context.Context, vmid int, service string, limit int) (io.ReadCloser, error) {
|
||||
command := "journalctl -f"
|
||||
if service != "" {
|
||||
command = fmt.Sprintf("journalctl -u %q -f", service)
|
||||
}
|
||||
if limit > 0 {
|
||||
command = fmt.Sprintf("%s -n %d", command, limit)
|
||||
func (n *Node) LXCJournalctl(ctx context.Context, vmid int, services []string, limit int) (io.ReadCloser, error) {
|
||||
command, err := formatJournalctl(services, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return n.LXCCommand(ctx, vmid, command)
|
||||
}
|
||||
|
||||
// LXCTail streams tail output for the given file.
|
||||
//
|
||||
// If limit is greater than 0, it will be used to limit the number of lines of output.
|
||||
func (n *Node) LXCTail(ctx context.Context, vmid int, files []string, limit int) (io.ReadCloser, error) {
|
||||
command, err := formatTail(files, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return n.LXCCommand(ctx, vmid, command)
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ type NodeConfig struct {
|
||||
VMID *int `json:"vmid"` // unset: auto discover; explicit 0: node-level route; >0: lxc/qemu resource route
|
||||
VMName string `json:"vmname,omitempty"`
|
||||
Services []string `json:"services,omitempty" aliases:"service"`
|
||||
Files []string `json:"files,omitempty" aliases:"file"`
|
||||
} // @name ProxmoxNodeConfig
|
||||
|
||||
type Node struct {
|
||||
|
||||
@@ -120,13 +120,25 @@ func (n *Node) NodeCommand(ctx context.Context, command string) (io.ReadCloser,
|
||||
return pr, nil
|
||||
}
|
||||
|
||||
func (n *Node) NodeJournalctl(ctx context.Context, service string, limit int) (io.ReadCloser, error) {
|
||||
command := "journalctl -f"
|
||||
if service != "" {
|
||||
command = fmt.Sprintf("journalctl -u %q -f", service)
|
||||
}
|
||||
if limit > 0 {
|
||||
command = fmt.Sprintf("%s -n %d", command, limit)
|
||||
// NodeJournalctl streams journalctl output for the given service.
|
||||
//
|
||||
// If services are not empty, it will be used to filter the output by services.
|
||||
// If limit is greater than 0, it will be used to limit the number of lines of output.
|
||||
func (n *Node) NodeJournalctl(ctx context.Context, services []string, limit int) (io.ReadCloser, error) {
|
||||
command, err := formatJournalctl(services, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return n.NodeCommand(ctx, command)
|
||||
}
|
||||
|
||||
// NodeTail streams tail output for the given file.
|
||||
//
|
||||
// If limit is greater than 0, it will be used to limit the number of lines of output.
|
||||
func (n *Node) NodeTail(ctx context.Context, files []string, limit int) (io.ReadCloser, error) {
|
||||
command, err := formatTail(files, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return n.NodeCommand(ctx, command)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user