From f48a4fc10872ac63eb91c59be08a8d38d903d0e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20B=C3=BClow?= Date: Tue, 30 Jan 2024 16:45:03 +0100 Subject: [PATCH] infinitely writes skel for some reason --- Makefile | 10 +++-- exampleconfig.yaml | 4 +- go.mod | 2 +- main.go | 34 ---------------- pods/skel.go | 79 ++++++++++++++++++++++++++++++++++++ readConfig.go | 41 +++++++++++++++++++ web/reverseProxy.go | 98 +++++++++++++++++++++++++++++++++++++++++++++ web/router.go | 87 +++++++--------------------------------- 8 files changed, 243 insertions(+), 112 deletions(-) create mode 100644 pods/skel.go create mode 100644 readConfig.go create mode 100644 web/reverseProxy.go diff --git a/Makefile b/Makefile index 3d6344b..137d6e0 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,6 @@ +.PHONY: release dev deps install + release: deps CGO_ENABLED=1 go build -buildvcs=true . @@ -12,9 +14,11 @@ deps: install: cp podterminal /usr/local/bin/podterminal - mkdir -p /etc/podterminal + ifeq ($(shell id podterminal 2>/dev/null)) useradd -r -s /bin/false podterminal - cp ./exampleconfig.yaml /etc/podterminal/config.yaml - cp podterminal.service /etc/systemd/system/ + endif + mkdir -p /etc/podterminal/skel + cp -n ./exampleconfig.yaml /etc/podterminal/config.yaml + cp -n podterminal.service /etc/systemd/system/ systemctl daemon-reload systemctl enable --now podterminal diff --git a/exampleconfig.yaml b/exampleconfig.yaml index 16dbe3c..ffba1ab 100644 --- a/exampleconfig.yaml +++ b/exampleconfig.yaml @@ -14,8 +14,10 @@ image: lscr.io/linuxserver/webtop maxage: 10800 # Port Podterminal should listen to port: 80 -# Files that will be copied into container on startup +# Files that will be copied into container userhome on startup skel: /etc/podterminal/skel/ ssl: false ssl_cert: /etc/ssl/certs/ssl-cert-snakeoil.pem ssl_cert_key: /etc/ssl/private/ssl-cert-snakeoil.key +# Blocks accessing /files and /files/socket.io to avoid File transfer +block_filebrowser: false diff --git a/go.mod b/go.mod index b5570ac..74be7ed 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/google/uuid v1.4.0 github.com/opencontainers/runtime-spec v1.1.1-0.20230922153023-c0e90434df2a github.com/spf13/viper v1.18.2 + golang.org/x/sync v0.5.0 ) require ( @@ -147,7 +148,6 @@ require ( golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect golang.org/x/mod v0.13.0 // indirect golang.org/x/net v0.19.0 // indirect - golang.org/x/sync v0.5.0 // indirect golang.org/x/sys v0.15.0 // indirect golang.org/x/term v0.15.0 // indirect golang.org/x/text v0.14.0 // indirect diff --git a/main.go b/main.go index ea35afa..567c124 100644 --- a/main.go +++ b/main.go @@ -2,9 +2,7 @@ package main import ( "log" - "os" - "github.com/spf13/viper" "golang.org/x/sync/errgroup" "git.jmbit.de/jmb/podterminal/pods" @@ -33,35 +31,3 @@ func main() { log.Fatal(err) } } - -func readConfigFile() { - log.Println("Reading Config") - viper.SetConfigFile("/etc/podterminal/config.yaml") - viper.SetDefault("port", 80) - viper.SetDefault("ip_addr", "0.0.0.0") - viper.SetDefault("image", "lscr.io/linuxserver/webtop") - viper.SetDefault("maxAge", 10800) - viper.SetDefault("dri", false) - viper.SetDefault("dir_node", "/dev/dri/renderD128") - viper.SetDefault("skel", "/etc/podterminal/skel") - viper.SetDefault("envvars", - map[string]string{ - "CUSTOM_USER": "user", - "PASSWORD": "", - "TITLE": "Podterminal", - }, - ) - if _, err := os.Stat("/etc/podterminal/config.yaml"); os.IsNotExist(err) { - log.Println("Config file does not exist, creating") - os.MkdirAll("/etc/podterminal", 755) - err := viper.WriteConfig() - if err != nil { - log.Fatalln("could not write default config", err) - } - } - err := viper.ReadInConfig() - if err != nil { - log.Fatalln("could not read config file", err) - } - log.Println("Finished reading Config") -} diff --git a/pods/skel.go b/pods/skel.go new file mode 100644 index 0000000..acc34da --- /dev/null +++ b/pods/skel.go @@ -0,0 +1,79 @@ +package pods + +import ( + "bytes" + "io" + "log" + + buildahCopyah "github.com/containers/buildah/copier" + "github.com/containers/podman/v4/pkg/bindings/containers" + "github.com/containers/podman/v4/pkg/copy" + "github.com/spf13/viper" + "golang.org/x/sync/errgroup" +) + +// CopySkelToContainer copies an Archive into the containers home directory +// (Assumes the use of a Webtop container) +// This Function is heavily inspired by https://github.com/containers/podman/blob/main/cmd/podman/containers/cp.go#L337 +func CopySkelToContainer(id string) error { + log.Println("Copying skel directory to container") + var errorGroup errgroup.Group + var buf bytes.Buffer + var err error + _, err = copy.ResolveHostPath(viper.GetString("skel")) + if err != nil { + log.Println("Could not find skel Directory", err) + return err + } + reader, writer := io.Pipe() + + // Create Function for copying from host to pipe + hostCopy := func() error { + getOptions := buildahCopyah.GetOptions{ + KeepDirectoryNames: true, + } + err := buildahCopyah.Get("/", "", getOptions, []string{viper.GetString("skel")}, &buf) + if err != nil { + log.Print("Error copying from Host: ", err) + } + + //defer closeAndLogWriter(writer) + return nil + } + + // create function to copy from pipe to container + containerCopy := func() error { + _, err := containers.CopyFromArchive(Socket, id, "/config/", &buf) + if err != nil { + log.Print("Error copying to Container: ", err) + } + //defer closeAndLogReader(reader) + return nil + } + + errorGroup.Go(hostCopy) + errorGroup.Go(containerCopy) + + if err := errorGroup.Wait(); err != nil { + log.Println("Could not Copy Skel", err) + } + closeAndLogReader(reader) + closeAndLogWriter(writer) + return err +} + +// closeAndLogReader takes a reader and closes it, logging any error +func closeAndLogReader(reader *io.PipeReader) { + err := reader.Close() + if err != nil { + log.Println("Could not close reader: ", err) + } +} + +// closeAndLogWriter takes a writer and closes it, logging any error +func closeAndLogWriter(writer *io.PipeWriter) { + err := writer.Close() + if err != nil { + log.Println("Could not close writer: ", err) + } +} diff --git a/readConfig.go b/readConfig.go new file mode 100644 index 0000000..f5dc6f4 --- /dev/null +++ b/readConfig.go @@ -0,0 +1,41 @@ +package main + +import ( + "log" + "os" + + "github.com/spf13/viper" +) + +func readConfigFile() { + log.Println("Reading Config") + viper.SetConfigFile("/etc/podterminal/config.yaml") + viper.SetDefault("port", 80) + viper.SetDefault("ip_addr", "0.0.0.0") + viper.SetDefault("image", "lscr.io/linuxserver/webtop") + viper.SetDefault("maxAge", 10800) + viper.SetDefault("dri", false) + viper.SetDefault("dir_node", "/dev/dri/renderD128") + viper.SetDefault("skel", "/etc/podterminal/skel/") + viper.SetDefault("block_filebrowser", false) + viper.SetDefault("envvars", + map[string]string{ + "CUSTOM_USER": "user", + "PASSWORD": "", + "TITLE": "Podterminal", + }, + ) + if _, err := os.Stat("/etc/podterminal/config.yaml"); os.IsNotExist(err) { + log.Println("Config file does not exist, creating") + os.MkdirAll("/etc/podterminal", 755) + err := viper.WriteConfig() + if err != nil { + log.Fatalln("could not write default config", err) + } + } + err := viper.ReadInConfig() + if err != nil { + log.Fatalln("could not read config file", err) + } + log.Println("Finished reading Config") +} diff --git a/web/reverseProxy.go b/web/reverseProxy.go new file mode 100644 index 0000000..26fa95d --- /dev/null +++ b/web/reverseProxy.go @@ -0,0 +1,98 @@ +package web + +import ( + "fmt" + "log" + "net/http/httputil" + "net/url" + "time" + + "github.com/gin-contrib/sessions" + "github.com/gin-gonic/gin" + + "git.jmbit.de/jmb/podterminal/pods" +) + +func createReverseProxy(backendService string) (*httputil.ReverseProxy, error) { + var err error + log.Println("Creating reverse Proxy for ", backendService) + + backendURL, err := url.Parse(backendService) + if err != nil { + log.Printf("Could not parse backend URL: %v", err) + } + + proxy := &httputil.ReverseProxy{ + Rewrite: func(request *httputil.ProxyRequest) { + request.SetURL(backendURL) + request.Out.Host = request.In.Host + }, + } + + return proxy, err +} + +func containerProxy(c *gin.Context) { + session := sessions.Default(c) + session.Save() + sessionID := session.ID() + if session.Get("ct") == nil { + log.Println("Creating Container for Session ", sessionID) + ct, err := pods.CreateContainer() + if err != nil { + c.HTML(500, "Error", fmt.Sprintf("[%s] Could not create Container: %v", sessionID, err)) + c.Abort() + } + err = pods.StartContainer(ct) + if err != nil { + c.HTML(500, "Error", fmt.Sprintf("[%s] Could not start Container: %v", sessionID, err)) + c.Abort() + } + // Hack to wait for Container to start up and get assigned an IP + time.Sleep(3 * time.Second) + ctip, err := pods.GetContainerIP(ct) + + if err != nil { + c.HTML(500, "Error", fmt.Sprintf("[%s] Could not get Container ip: %v", sessionID, err)) + c.Abort() + } + + err = pods.CopySkelToContainer(ct) + + if err != nil { + c.HTML( + 500, + "Error", + fmt.Sprintf("[%s] Could not copy skel archive to container: %v", sessionID, err), + ) + c.Abort() + } + + proxies[ct], err = createReverseProxy(fmt.Sprintf("http://%s:3000", ctip)) + + if err != nil { + c.HTML( + 500, + "Error", + fmt.Sprintf("[%s] Could not create Container Proxy: %v", sessionID, err), + ) + c.Abort() + } + session.Set("ct", ct) + session.Save() + c.Redirect(301, "/") + } else { + sessionCT := session.Get("ct") + switch sessionCT.(type) { + case string: + + default: + c.HTML(500, "Error", "Session Container ID is not a string") + c.Abort() + } + id := session.Get("ct").(string) + proxy := proxies[id] + proxy.ServeHTTP(c.Writer, c.Request) + } + +} diff --git a/web/router.go b/web/router.go index 83e1b02..791b59f 100644 --- a/web/router.go +++ b/web/router.go @@ -4,16 +4,12 @@ import ( "fmt" "log" "net/http/httputil" - "net/url" - "time" "github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions/cookie" "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/spf13/viper" - - "git.jmbit.de/jmb/podterminal/pods" ) var proxies = make(map[string]*httputil.ReverseProxy) @@ -52,6 +48,9 @@ func setupRouter() *gin.Engine { router.Use(gin.Recovery()) router.Use(sessions.Sessions("session", store)) //router.Use(urlLog()) + if viper.GetBool("block_filebrowser") == true { + router.Use(blockFilebrowser) + } router.Use(containerProxy) // router.Any("/", containerProxy) return router @@ -69,75 +68,17 @@ func urlLog() gin.HandlerFunc { } } -func createReverseProxy(backendService string) (*httputil.ReverseProxy, error) { - var err error - log.Println("Creating reverse Proxy for ", backendService) +// blockFilebrowser blocks the URLs used by the Kclient File browser +func blockFilebrowser(c *gin.Context) { + switch c.Request.URL.RawPath { + case "/files": + c.Abort() + case "/filebrowser.js": + c.Abort() + case "/files/socket.io": + c.Abort() - backendURL, err := url.Parse(backendService) - if err != nil { - log.Printf("Could not parse backend URL: %v", err) + default: + c.Next() } - - proxy := &httputil.ReverseProxy{ - Rewrite: func(request *httputil.ProxyRequest) { - request.SetURL(backendURL) - request.Out.Host = request.In.Host - }, - } - - return proxy, err -} - -func containerProxy(c *gin.Context) { - session := sessions.Default(c) - session.Save() - sessionID := session.ID() - if session.Get("ct") == nil { - log.Println("Creating Container for Session ", sessionID) - ct, err := pods.CreateContainer() - if err != nil { - c.HTML(500, "Error", fmt.Sprintf("[%s] Could not create Container: %v", sessionID, err)) - c.Abort() - } - err = pods.StartContainer(ct) - if err != nil { - c.HTML(500, "Error", fmt.Sprintf("[%s] Could not start Container: %v", sessionID, err)) - c.Abort() - } - // Hack to wait for Container to start up and get assigned an IP - time.Sleep(3 * time.Second) - ctip, err := pods.GetContainerIP(ct) - - if err != nil { - c.HTML(500, "Error", fmt.Sprintf("[%s] Could not get Container ip: %v", sessionID, err)) - c.Abort() - } - - proxies[ct], err = createReverseProxy(fmt.Sprintf("http://%s:3000", ctip)) - - if err != nil { - c.HTML( - 500, - "Error", - fmt.Sprintf("[%s] Could not create Container Proxy: %v", sessionID, err), - ) - c.Abort() - } - session.Set("ct", ct) - session.Save() - c.Redirect(301, "/") - } else { - sessionCT := session.Get("ct") - switch sessionCT.(type) { - case string: - - default: - c.HTML(500, "Error", "Session Container ID is not a string") - c.Abort() - } - id := session.Get("ct").(string) - proxy := proxies[id] - proxy.ServeHTTP(c.Writer, c.Request) - } - }