mirror of
https://github.com/khairul169/vaulterm.git
synced 2025-04-28 08:39:37 +07:00
feat: add pve lxc xtermjs console
This commit is contained in:
parent
b9d879330a
commit
bde42ca729
1
server/.gitignore
vendored
1
server/.gitignore
vendored
@ -1 +1,2 @@
|
||||
tmp/
|
||||
.env
|
||||
|
@ -5,23 +5,24 @@ go 1.21.1
|
||||
require (
|
||||
github.com/gofiber/contrib/websocket v1.3.2
|
||||
github.com/gofiber/fiber/v2 v2.52.5
|
||||
github.com/joho/godotenv v1.5.1
|
||||
golang.org/x/crypto v0.28.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/andybalholm/brotli v1.1.0 // indirect
|
||||
github.com/fasthttp/websocket v1.5.8 // indirect
|
||||
github.com/fasthttp/websocket v1.5.10
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/klauspost/compress v1.17.7 // indirect
|
||||
github.com/klauspost/compress v1.17.9 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511 // indirect
|
||||
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.52.0 // indirect
|
||||
github.com/valyala/fasthttp v1.55.0 // indirect
|
||||
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||
golang.org/x/net v0.23.0 // indirect
|
||||
golang.org/x/net v0.26.0 // indirect
|
||||
)
|
||||
|
||||
require golang.org/x/sys v0.26.0 // indirect
|
||||
|
@ -2,16 +2,18 @@ github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1
|
||||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fasthttp/websocket v1.5.8 h1:k5DpirKkftIF/w1R8ZzjSgARJrs54Je9YJK37DL/Ah8=
|
||||
github.com/fasthttp/websocket v1.5.8/go.mod h1:d08g8WaT6nnyvg9uMm8K9zMYyDjfKyj3170AtPRuVU0=
|
||||
github.com/fasthttp/websocket v1.5.10 h1:bc7NIGyrg1L6sd5pRzCIbXpro54SZLEluZCu0rOpcN4=
|
||||
github.com/fasthttp/websocket v1.5.10/go.mod h1:BwHeuXGWzCW1/BIKUKD3+qfCl+cTdsHu/f243NcAI/Q=
|
||||
github.com/gofiber/contrib/websocket v1.3.2 h1:AUq5PYeKwK50s0nQrnluuINYeep1c4nRCJ0NWsV3cvg=
|
||||
github.com/gofiber/contrib/websocket v1.3.2/go.mod h1:07u6QGMsvX+sx7iGNCl5xhzuUVArWwLQ3tBIH24i+S8=
|
||||
github.com/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo=
|
||||
github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg=
|
||||
github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
@ -23,20 +25,20 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511 h1:KanIMPX0QdEdB4R3CiimCAbxFrhB3j7h0/OvpYGVQa8=
|
||||
github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg=
|
||||
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 h1:D0vL7YNisV2yqE55+q0lFuGse6U8lxlg7fYTctlT5Gc=
|
||||
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.52.0 h1:wqBQpxH71XW0e2g+Og4dzQM8pk34aFYlA1Ga8db7gU0=
|
||||
github.com/valyala/fasthttp v1.52.0/go.mod h1:hf5C4QnVMkNXMspnsUlfM3WitlgYflyhHYoKol/szxQ=
|
||||
github.com/valyala/fasthttp v1.55.0 h1:Zkefzgt6a7+bVKHnu/YaYSOPfNYNisSVBo/unVCf8k8=
|
||||
github.com/valyala/fasthttp v1.55.0/go.mod h1:NkY9JtkrpPKmgwV3HTaS2HWaJss9RSIsRVfcxxoHiOM=
|
||||
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
|
||||
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
||||
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
||||
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
|
||||
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||
|
127
server/lib/pve.go
Normal file
127
server/lib/pve.go
Normal file
@ -0,0 +1,127 @@
|
||||
package lib
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type PVEServer struct {
|
||||
HostName string
|
||||
Port int
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
type PVERequestInit struct {
|
||||
Body map[string]string
|
||||
Ticket string
|
||||
CSRF string
|
||||
}
|
||||
|
||||
func fetch(method string, url string, cfg *PVERequestInit) ([]byte, error) {
|
||||
var body io.Reader
|
||||
if cfg.Body != nil {
|
||||
json, _ := json.Marshal(cfg.Body)
|
||||
body = bytes.NewBuffer(json)
|
||||
}
|
||||
|
||||
req, _ := http.NewRequest(method, url, body)
|
||||
|
||||
if cfg.Ticket != "" {
|
||||
req.Header.Add("Cookie", "PVEAuthCookie="+cfg.Ticket)
|
||||
}
|
||||
if cfg.CSRF != "" {
|
||||
req.Header.Add("CSRFPreventionToken", cfg.CSRF)
|
||||
}
|
||||
if body != nil {
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
}
|
||||
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
client := &http.Client{
|
||||
Transport: tr,
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("request failed with status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return io.ReadAll(resp.Body)
|
||||
}
|
||||
|
||||
type PVEAccessTicket struct {
|
||||
CSRFPreventionToken string `json:"CSRFPreventionToken"`
|
||||
Ticket string `json:"ticket"`
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
func (pve *PVEServer) GetAccessTicket() (*PVEAccessTicket, error) {
|
||||
url := fmt.Sprintf("https://%s:%d/api2/json/access/ticket", pve.HostName, pve.Port)
|
||||
|
||||
body, err := fetch("POST", url, &PVERequestInit{Body: map[string]string{
|
||||
"username": pve.Username,
|
||||
"password": pve.Password,
|
||||
}})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var res struct {
|
||||
Data PVEAccessTicket `json:"data"`
|
||||
}
|
||||
if err := json.Unmarshal(body, &res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &res.Data, nil
|
||||
}
|
||||
|
||||
type PVEInstance struct {
|
||||
Type string
|
||||
Node string
|
||||
VMID string
|
||||
}
|
||||
|
||||
type PVEVNCTicketData struct {
|
||||
Port string `json:"port"`
|
||||
User string `json:"user"`
|
||||
Ticket string `json:"ticket"`
|
||||
CERT string `json:"cert"`
|
||||
Upid string `json:"upid"`
|
||||
}
|
||||
|
||||
func (pve *PVEServer) GetVNCTicket(access *PVEAccessTicket, instance *PVEInstance, isVNC bool) (*PVEVNCTicketData, error) {
|
||||
proxyType := "termproxy"
|
||||
if isVNC {
|
||||
proxyType = "vncproxy"
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("https://%s:%d/api2/json/nodes/%s/%s/%s/%s",
|
||||
pve.HostName, pve.Port, instance.Node, instance.Type, instance.VMID, proxyType)
|
||||
|
||||
body, err := fetch("POST", url, &PVERequestInit{Ticket: access.Ticket, CSRF: access.CSRFPreventionToken})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var res struct {
|
||||
Data PVEVNCTicketData `json:"data"`
|
||||
}
|
||||
if err := json.Unmarshal(body, &res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &res.Data, nil
|
||||
}
|
96
server/lib/pve_session.go
Normal file
96
server/lib/pve_session.go
Normal file
@ -0,0 +1,96 @@
|
||||
package lib
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
fastWs "github.com/fasthttp/websocket"
|
||||
"github.com/gofiber/contrib/websocket"
|
||||
)
|
||||
|
||||
type PVEConfig struct {
|
||||
HostName string
|
||||
User string
|
||||
Password string
|
||||
Port int
|
||||
PrivateKey string
|
||||
PrivateKeyPassphrase string
|
||||
}
|
||||
|
||||
func (pve *PVEServer) NewTerminalSession(c *websocket.Conn, access *PVEAccessTicket, instance *PVEInstance, ticket *PVEVNCTicketData) error {
|
||||
url := fmt.Sprintf("wss://%s:%d/api2/json/nodes/%s/%s/%s/vncwebsocket?port=%s&vncticket=%s",
|
||||
pve.HostName, pve.Port, instance.Node, instance.Type, instance.VMID, ticket.Port, url.QueryEscape(ticket.Ticket))
|
||||
|
||||
headers := http.Header{}
|
||||
headers.Add("Authorization", "PVEAPIToken="+access.Username)
|
||||
headers.Add("Cookie", "PVEAuthCookie="+access.Ticket)
|
||||
|
||||
dialer := fastWs.Dialer{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
|
||||
ws, _, err := dialer.Dial(url, headers)
|
||||
if err != nil {
|
||||
log.Println("Error connecting to Proxmox WebSocket:", err)
|
||||
return err
|
||||
}
|
||||
defer ws.Close()
|
||||
|
||||
// Send first ticket line
|
||||
ws.WriteMessage(fastWs.TextMessage, []byte(fmt.Sprintf("%s:%s\n", access.Username, access.Ticket)))
|
||||
|
||||
// https://github.com/proxmox/pve-xtermjs/blob/master/README
|
||||
|
||||
go func() {
|
||||
for {
|
||||
t, msg, err := c.ReadMessage()
|
||||
if err != nil {
|
||||
log.Println("Error reading from client:", err)
|
||||
break
|
||||
}
|
||||
|
||||
if strings.HasPrefix(string(msg), "\x01") {
|
||||
parts := strings.Split(string(msg[1:]), ",")
|
||||
if len(parts) == 2 {
|
||||
width, _ := strconv.Atoi(parts[0])
|
||||
height, _ := strconv.Atoi(parts[1])
|
||||
ws.WriteMessage(fastWs.TextMessage, []byte(fmt.Sprintf("1:%d:%d:", width, height)))
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
msg = []byte(fmt.Sprintf("0:%d:%s\n", len(msg), string(msg)))
|
||||
err = ws.WriteMessage(t, msg)
|
||||
|
||||
if err != nil {
|
||||
log.Println("Error writing to Proxmox:", err)
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
t, msg, err := ws.ReadMessage()
|
||||
if err != nil {
|
||||
log.Println("Error reading from Proxmox:", err)
|
||||
break
|
||||
}
|
||||
|
||||
if string(msg) == "OK" {
|
||||
continue
|
||||
}
|
||||
|
||||
err = c.WriteMessage(t, msg)
|
||||
if err != nil {
|
||||
log.Println("Error writing to client:", err)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -145,9 +145,10 @@ func NewSSHWebsocketSession(c *websocket.Conn, cfg *SSHConfig) error {
|
||||
height, _ := strconv.Atoi(parts[1])
|
||||
session.WindowChange(height, width)
|
||||
}
|
||||
} else {
|
||||
stdinPipe.Write(msg)
|
||||
continue
|
||||
}
|
||||
|
||||
stdinPipe.Write(msg)
|
||||
}
|
||||
}()
|
||||
|
@ -1,16 +1,25 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/gofiber/contrib/websocket"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/joho/godotenv"
|
||||
"rul.sh/vaulterm/lib"
|
||||
)
|
||||
|
||||
func main() {
|
||||
godotenv.Load()
|
||||
app := fiber.New()
|
||||
|
||||
var pve = &lib.PVEServer{
|
||||
HostName: "pve",
|
||||
Port: 8006,
|
||||
Username: os.Getenv("PVE_USERNAME"),
|
||||
Password: os.Getenv("PVE_PASSWORD"),
|
||||
}
|
||||
|
||||
app.Get("/", func(c *fiber.Ctx) error {
|
||||
return c.SendString("Hello, World!")
|
||||
})
|
||||
@ -22,18 +31,42 @@ func main() {
|
||||
return fiber.ErrUpgradeRequired
|
||||
})
|
||||
|
||||
// app.Get("/ws/ssh", websocket.New(func(c *websocket.Conn) {
|
||||
// err := lib.NewSSHWebsocketSession(c, &lib.SSHConfig{
|
||||
// HostName: "10.0.0.102",
|
||||
// User: "root",
|
||||
// Password: "ausya2",
|
||||
// })
|
||||
|
||||
// if err != nil {
|
||||
// msg := fmt.Sprintf("\r\n%s\r\n", err.Error())
|
||||
// c.WriteMessage(websocket.TextMessage, []byte(msg))
|
||||
// }
|
||||
// }))
|
||||
|
||||
app.Get("/ws/ssh", websocket.New(func(c *websocket.Conn) {
|
||||
err := lib.NewSSHWebsocketSession(c, &lib.SSHConfig{
|
||||
HostName: "10.0.0.102",
|
||||
User: "root",
|
||||
Password: "ausya2",
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("\r\n%s\r\n", err.Error())
|
||||
c.WriteMessage(websocket.TextMessage, []byte(msg))
|
||||
node := &lib.PVEInstance{
|
||||
Type: "lxc",
|
||||
Node: "pve",
|
||||
VMID: "102",
|
||||
}
|
||||
}))
|
||||
|
||||
access, err := pve.GetAccessTicket()
|
||||
if err != nil {
|
||||
c.WriteMessage(websocket.TextMessage, []byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
ticket, err := pve.GetVNCTicket(access, node, false)
|
||||
if err != nil {
|
||||
c.WriteMessage(websocket.TextMessage, []byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
if err := pve.NewTerminalSession(c, access, node, ticket); err != nil {
|
||||
c.WriteMessage(websocket.TextMessage, []byte(err.Error()))
|
||||
}
|
||||
|
||||
}))
|
||||
app.Listen(":3000")
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user