feat: add incus terminal

This commit is contained in:
2024-11-07 04:56:19 +07:00
parent 2adde048b0
commit ab9b3368d1
7 changed files with 302 additions and 21 deletions
+37
View File
@@ -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
}
+114
View File
@@ -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
}
+90
View File
@@ -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
}