mirror of
https://github.com/khairul169/vaulterm.git
synced 2026-09-15 17:03:30 +07:00
feat: add incus terminal
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
package lib
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func LoadClientCertificate(clientCert string, clientKey string) (*tls.Certificate, error) {
|
||||
// Client certificate
|
||||
ccb, _ := pem.Decode([]byte(clientCert))
|
||||
if ccb == nil {
|
||||
return nil, fmt.Errorf("failed to parse client certificate")
|
||||
}
|
||||
|
||||
cert, err := x509.ParseCertificate(ccb.Bytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse client certificate: %v", err)
|
||||
}
|
||||
|
||||
// Client key
|
||||
ckb, _ := pem.Decode([]byte(clientKey))
|
||||
if ckb == nil {
|
||||
return nil, fmt.Errorf("failed to parse client key")
|
||||
}
|
||||
|
||||
key, err := x509.ParsePKCS8PrivateKey(ckb.Bytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse client key: %v", err)
|
||||
}
|
||||
|
||||
return &tls.Certificate{
|
||||
Certificate: [][]byte{cert.Raw},
|
||||
PrivateKey: key,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package lib
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type IncusServer struct {
|
||||
HostName string
|
||||
Port int
|
||||
ClientCert string
|
||||
ClientKey string
|
||||
}
|
||||
|
||||
type IncusFetchConfig struct {
|
||||
Body map[string]interface{}
|
||||
}
|
||||
|
||||
func (i *IncusServer) GetCertificate() (*tls.Certificate, error) {
|
||||
return LoadClientCertificate(i.ClientCert, i.ClientKey)
|
||||
}
|
||||
|
||||
func (i *IncusServer) Fetch(method string, url string, cfg *IncusFetchConfig) ([]byte, error) {
|
||||
var body io.Reader
|
||||
if cfg != nil && cfg.Body != nil {
|
||||
json, _ := json.Marshal(cfg.Body)
|
||||
body = bytes.NewBuffer(json)
|
||||
}
|
||||
|
||||
reqUrl := fmt.Sprintf("https://%s:%d%s", i.HostName, i.Port, url)
|
||||
req, _ := http.NewRequest(method, reqUrl, body)
|
||||
|
||||
if body != nil {
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
}
|
||||
|
||||
clientCert, err := i.GetCertificate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
Certificates: []tls.Certificate{*clientCert},
|
||||
},
|
||||
}
|
||||
client := &http.Client{
|
||||
Transport: tr,
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= http.StatusBadRequest {
|
||||
return nil, fmt.Errorf("request failed with status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return io.ReadAll(resp.Body)
|
||||
}
|
||||
|
||||
type IncusInstanceExecRes struct {
|
||||
ID string
|
||||
Operation string
|
||||
Control string
|
||||
Secret string
|
||||
}
|
||||
|
||||
func (i *IncusServer) InstanceExec(instance string, command []string, interactive bool) (*IncusInstanceExecRes, error) {
|
||||
url := fmt.Sprintf("/1.0/instances/%s/exec?project=default", instance)
|
||||
|
||||
body, err := i.Fetch("POST", url, &IncusFetchConfig{
|
||||
Body: map[string]interface{}{
|
||||
"command": command,
|
||||
"interactive": interactive,
|
||||
"wait-for-websocket": true,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var res struct {
|
||||
Operation string `json:"operation"`
|
||||
Metadata struct {
|
||||
ID string `json:"id"`
|
||||
Metadata struct {
|
||||
Fds map[string]string `json:"fds"`
|
||||
} `json:"metadata"`
|
||||
} `json:"metadata"`
|
||||
}
|
||||
if err := json.Unmarshal(body, &res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
control := res.Metadata.Metadata.Fds["control"]
|
||||
secret := res.Metadata.Metadata.Fds["0"]
|
||||
|
||||
return &IncusInstanceExecRes{
|
||||
ID: res.Metadata.ID,
|
||||
Operation: res.Operation,
|
||||
Control: control,
|
||||
Secret: secret,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package lib
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
fastWs "github.com/fasthttp/websocket"
|
||||
"github.com/gofiber/contrib/websocket"
|
||||
)
|
||||
|
||||
func NewIncusWebsocketSession(c *websocket.Conn, incus *IncusServer) error {
|
||||
exec, err := incus.InstanceExec("test", []string{"/bin/sh"}, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
clientCert, err := incus.GetCertificate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dialer := fastWs.Dialer{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
Certificates: []tls.Certificate{*clientCert},
|
||||
},
|
||||
}
|
||||
|
||||
controlUrl := fmt.Sprintf("wss://%s:%d%s/websocket?secret=%s", incus.HostName, incus.Port, exec.Operation, exec.Control)
|
||||
controlWs, _, err := dialer.Dial(controlUrl, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer controlWs.Close()
|
||||
|
||||
ttyUrl := fmt.Sprintf("wss://%s:%d%s/websocket?secret=%s", incus.HostName, incus.Port, exec.Operation, exec.Secret)
|
||||
ttyWs, _, err := dialer.Dial(ttyUrl, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer ttyWs.Close()
|
||||
|
||||
go func() {
|
||||
for {
|
||||
_, 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 {
|
||||
resizeCmd, _ := json.Marshal(map[string]interface{}{
|
||||
"command": "window-resize",
|
||||
"args": map[string]string{
|
||||
"width": parts[0],
|
||||
"height": parts[1],
|
||||
},
|
||||
})
|
||||
controlWs.WriteMessage(websocket.BinaryMessage, resizeCmd)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if err = ttyWs.WriteMessage(websocket.BinaryMessage, msg); err != nil {
|
||||
log.Println("Error writing to Incus:", err)
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
t, msg, err := ttyWs.ReadMessage()
|
||||
if err != nil {
|
||||
log.Println("Error reading from Incus:", err)
|
||||
break
|
||||
}
|
||||
|
||||
if err = c.WriteMessage(t, msg); err != nil {
|
||||
log.Println("Error writing to client:", err)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
+15
-1
@@ -14,7 +14,7 @@ func main() {
|
||||
app := fiber.New()
|
||||
|
||||
var pve = &lib.PVEServer{
|
||||
HostName: "pve",
|
||||
HostName: "10.0.0.1",
|
||||
Port: 8006,
|
||||
Username: os.Getenv("PVE_USERNAME"),
|
||||
Password: os.Getenv("PVE_PASSWORD"),
|
||||
@@ -75,5 +75,19 @@ func main() {
|
||||
c.WriteMessage(websocket.TextMessage, []byte(err.Error()))
|
||||
}
|
||||
}))
|
||||
|
||||
app.Get("/ws/incus", websocket.New(func(c *websocket.Conn) {
|
||||
incus := &lib.IncusServer{
|
||||
HostName: "100.64.0.3",
|
||||
Port: 8443,
|
||||
ClientCert: "",
|
||||
ClientKey: "",
|
||||
}
|
||||
|
||||
if err := lib.NewIncusWebsocketSession(c, incus); err != nil {
|
||||
c.WriteMessage(websocket.TextMessage, []byte(err.Error()))
|
||||
}
|
||||
}))
|
||||
|
||||
app.Listen(":3000")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user