diff --git a/exampleconfig.yaml b/exampleconfig.yaml index fa8704c..50c9e2d 100644 --- a/exampleconfig.yaml +++ b/exampleconfig.yaml @@ -11,7 +11,7 @@ envvars: # Container Image you want to use image: git.jmbit.de/jmb/webtop-plus:latest # Maximum age of Session -maxage: 10800 +maxage: 0 # Port Podterminal should listen to port: 80 # Files that will be copied into container on startup diff --git a/pods/container.go b/pods/container.go index 16a55d6..e83039a 100644 --- a/pods/container.go +++ b/pods/container.go @@ -2,6 +2,7 @@ package pods import ( "log" + "time" "github.com/containers/podman/v4/pkg/bindings/containers" "github.com/containers/podman/v4/pkg/specgen" @@ -50,15 +51,30 @@ func DestroyContainer(id string) error { return nil } +// GetContainerIP returns a func GetContainerIP(id string) (string, error) { conn := Socket + var ip string + var err error + i := 0 - container, err := containers.Inspect(conn, id, nil) - if err != nil { - log.Println("Could not get IP of container", err) - return "", err + for i < 50 { + time.Sleep(100 * time.Millisecond) + container, err := containers.Inspect(conn, id, nil) + if err != nil { + log.Println("Could not get IP of container", err) + return "", err + } + ip = container.NetworkSettings.IPAddress + log.Println(ip) + if len(ip) > 5 { + break + } + i++ + } + if i > 50 { + log.Println("timed out waiting for IP") } - ip := container.NetworkSettings.IPAddress return ip, err } diff --git a/pods/manager.go b/pods/manager.go index bb7b5c2..6b939aa 100644 --- a/pods/manager.go +++ b/pods/manager.go @@ -16,6 +16,8 @@ import ( var Socket context.Context var rawSocket net.Conn +var OldContainers []string + func socketConnection() context.Context { uri := "unix:///run/podman/podman.sock" conn, err := bindings.NewConnection(context.Background(), uri) diff --git a/readConfig.go b/readConfig.go index 9b91e50..89b13f8 100644 --- a/readConfig.go +++ b/readConfig.go @@ -5,9 +5,12 @@ import ( "os" "github.com/spf13/viper" + + "git.jmbit.de/jmb/podterminal/utils" ) func readConfigFile() { + sessionKey, _ := utils.RandomString(64) log.Println("Reading Config") viper.SetConfigFile("/etc/podterminal/config.yaml") viper.SetDefault("port", 80) @@ -21,6 +24,8 @@ func readConfigFile() { viper.SetDefault("skel_target", "/") viper.SetDefault("skel_user", "") viper.SetDefault("block_filebrowser", false) + viper.SetDefault("session_key", sessionKey) + viper.SetDefault("container_port", 3000) viper.SetDefault("envvars", map[string]string{ "CUSTOM_USER": "user", diff --git a/web/reverseProxy.go b/web/reverseProxy.go index 2e39907..13e8d75 100644 --- a/web/reverseProxy.go +++ b/web/reverseProxy.go @@ -35,25 +35,32 @@ func createReverseProxy(backendService string) (*httputil.ReverseProxy, error) { func containerProxy(c *gin.Context) { session := sessions.Default(c) session.Save() - sessionID := session.ID() + sessionID := sessions.Session.ID(session) if session.Get("ct") == nil { log.Println("Creating Container for Session ", sessionID) ct, err := pods.CreateContainer() + session.Set("ct", ct) + session.Save() if err != nil { c.HTML(500, "Error", fmt.Sprintf("[%s] Could not create Container: %v", sessionID, err)) + session.Delete("ct") + session.Save() c.Abort() } err = pods.StartContainer(ct) if err != nil { c.HTML(500, "Error", fmt.Sprintf("[%s] Could not start Container: %v", sessionID, err)) + session.Delete("ct") + session.Save() 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)) + session.Delete("ct") + session.Save() c.Abort() } @@ -68,9 +75,11 @@ func containerProxy(c *gin.Context) { "Error", fmt.Sprintf("[%s] Could not create Container Proxy: %v", sessionID, err), ) + session.Delete("ct") + session.Save() c.Abort() } - session.Set("ct", ct) + session.Set("ready", true) session.Save() c.Redirect(301, "/") } else { @@ -80,8 +89,14 @@ func containerProxy(c *gin.Context) { default: c.HTML(500, "Error", "Session Container ID is not a string") + session.Delete("ct") + session.Save() c.Abort() } + if session.Get("ready") == nil { + time.Sleep(100 * time.Millisecond) + c.Redirect(301, "/") + } 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 6385b66..20b2c2e 100644 --- a/web/router.go +++ b/web/router.go @@ -8,7 +8,6 @@ import ( "github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions/cookie" "github.com/gin-gonic/gin" - "github.com/google/uuid" "github.com/spf13/viper" ) @@ -42,7 +41,7 @@ func setupRouter() *gin.Engine { gin.ForceConsoleColor() gin.SetMode("release") router := gin.New() - store := cookie.NewStore([]byte(uuid.NewString())) + store := cookie.NewStore([]byte(viper.GetString("session_key"))) store.Options(sessions.Options{ MaxAge: viper.GetInt("maxAge"), }) diff --git a/web/sessionAging.go b/web/sessionAging.go new file mode 100644 index 0000000..e33e260 --- /dev/null +++ b/web/sessionAging.go @@ -0,0 +1,56 @@ +package web + +import ( + "time" + + "github.com/spf13/viper" + + "git.jmbit.de/jmb/podterminal/pods" +) + +type sessionData struct { + lastAccess *time.Time + sessionID string +} + +var invalidSessions []string +var sessionLastAccess map[string]*sessionData + +func initSessionAging() error { + sessionLastAccess = make(map[string]*sessionData) + + return nil +} + +func updateSession(id string, sessionID string) { + nowTime := time.Now() + sessionLastAccess[id].lastAccess = &nowTime + sessionLastAccess[id].sessionID = sessionID +} + +func deleteIdleSessions() { + idleTimout := viper.GetInt("session_timeout") + tenMinutesAgo := -time.Duration(idleTimout) * time.Second + oldAge := time.Now().Add(tenMinutesAgo) + for session, sessionData := range sessionLastAccess { + if oldAge.After(*sessionData.lastAccess) { + pods.DestroyContainer(session) + invalidSessions = append(invalidSessions, sessionData.sessionID) + + } + + } + +} + +func IdleSessionCleanup() error { + err := initSessionAging() + if err != nil { + println("Could not initialize Session aging") + return err + } + for { + deleteIdleSessions() + time.Sleep(time.Duration(viper.GetInt("session_timeout")) * time.Second) + } +}