appproxy.go 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. package main
  2. /*
  3. appproxy.go
  4. Top-level wiring for container apps: web servers (usually Docker
  5. containers) that an administrator publishes through the built-in reverse
  6. proxy, so they are reachable on the same port as ArozOS (and therefore
  7. through Cloudflare / a single forwarded port) and can be pinned to the
  8. desktop by every user.
  9. - mrouter calls appProxyManager.HandleRequest first (see main.router.go)
  10. - admins publish / edit apps in the Container Apps web app, or from the
  11. "Publish" action of Docker Manager
  12. - users get a simplified list of the apps they may open and pin them as
  13. "app" desktop shortcuts, which always resolve through /app/<slug>/
  14. */
  15. import (
  16. "encoding/json"
  17. "net/http"
  18. "net/url"
  19. "strconv"
  20. "imuslab.com/arozos/mod/appproxy"
  21. "imuslab.com/arozos/mod/docker"
  22. module "imuslab.com/arozos/mod/modules"
  23. prout "imuslab.com/arozos/mod/prouter"
  24. "imuslab.com/arozos/mod/utils"
  25. )
  26. var appProxyManager *appproxy.Manager
  27. // appProxyIdentity converts an ArozOS user into the proxy's identity
  28. func appProxyIdentity(username string, groups []string, isAdmin bool) *appproxy.Identity {
  29. return &appproxy.Identity{Username: username, Groups: groups, IsAdmin: isAdmin}
  30. }
  31. func AppProxyInit() {
  32. manager, err := appproxy.NewManager(appproxy.Options{
  33. Store: sysdb,
  34. SelfPort: *listen_port,
  35. ResolveUser: func(w http.ResponseWriter, r *http.Request) *appproxy.Identity {
  36. if !authAgent.CheckAuth(r) {
  37. return nil
  38. }
  39. userinfo, err := userHandler.GetUserInfoFromRequest(w, r)
  40. if err != nil {
  41. return nil
  42. }
  43. //Using an app counts as activity on the desktop session
  44. authAgent.UpdateSessionExpireTime(w, r)
  45. return appProxyIdentity(userinfo.Username, userinfo.GetUserPermissionGroupNames(), userinfo.IsAdmin())
  46. },
  47. LookupUser: func(username string) *appproxy.Identity {
  48. if !authAgent.UserExists(username) {
  49. return nil
  50. }
  51. userinfo, err := userHandler.GetUserInfoFromUsername(username)
  52. if err != nil {
  53. return nil
  54. }
  55. return appProxyIdentity(userinfo.Username, userinfo.GetUserPermissionGroupNames(), userinfo.IsAdmin())
  56. },
  57. LoginURL: func(returnTo string) string {
  58. return "/login.html?redirect=" + url.QueryEscape(returnTo)
  59. },
  60. StripCookies: []string{"ao_auth", "ao_acc"},
  61. Log: func(message string, err error) {
  62. systemWideLogger.PrintAndLog("AppProxy", message, err)
  63. },
  64. })
  65. if err != nil {
  66. systemWideLogger.PrintAndLog("AppProxy", "Unable to start the container app proxy", err)
  67. return
  68. }
  69. appProxyManager = manager
  70. //Any logged in user: the simplified app list and shortcut launch info
  71. userRouter := prout.NewModuleRouter(prout.RouterOption{
  72. ModuleName: "",
  73. UserHandler: userHandler,
  74. DeniedHandler: func(w http.ResponseWriter, r *http.Request) {
  75. utils.SendErrorResponse(w, "Permission Denied")
  76. },
  77. })
  78. userRouter.HandleFunc("/system/appproxy/apps", manager.HandleUserApps)
  79. userRouter.HandleFunc("/system/appproxy/launch", manager.HandleLaunch)
  80. //Administrators: publish, edit, remove and analyse apps
  81. adminRouter := prout.NewModuleRouter(prout.RouterOption{
  82. ModuleName: "",
  83. AdminOnly: true,
  84. UserHandler: userHandler,
  85. DeniedHandler: func(w http.ResponseWriter, r *http.Request) {
  86. utils.SendErrorResponse(w, "Permission Denied")
  87. },
  88. })
  89. adminRouter.HandleFunc("/system/appproxy/admin/list", manager.HandleAdminList)
  90. adminRouter.HandleFunc("/system/appproxy/admin/save", manager.HandleAdminSave)
  91. adminRouter.HandleFunc("/system/appproxy/admin/delete", manager.HandleAdminDelete)
  92. adminRouter.HandleFunc("/system/appproxy/admin/probe", manager.HandleAdminProbe)
  93. adminRouter.HandleFunc("/system/appproxy/admin/containers", handleAppProxyContainers)
  94. //Every user opens and pins apps here; administrators also publish from it,
  95. //including web servers that are not Docker containers
  96. moduleHandler.RegisterModule(module.ModuleInfo{
  97. Name: "Container Apps",
  98. Desc: "Open web apps running in containers and pin them to the desktop",
  99. Group: "Utilities",
  100. IconPath: "ContainerApps/img/icon.svg",
  101. Version: "1.0",
  102. StartDir: "ContainerApps/index.html",
  103. SupportFW: true,
  104. LaunchFWDir: "ContainerApps/index.html",
  105. InitFWSize: []int{900, 600},
  106. })
  107. systemWideLogger.PrintAndLog("AppProxy", "Container app proxy ready with "+strconv.Itoa(manager.Count())+" published app(s)", nil)
  108. }
  109. // appProxyContainer is a Docker container offered for publishing
  110. type appProxyContainer struct {
  111. ID string
  112. Name string
  113. Image string
  114. State string
  115. Ports []docker.PublishedPort
  116. Targets []string
  117. Published []string //Slugs of apps already pointing at this container
  118. }
  119. // handleAppProxyContainers lists Docker containers with their published web ports
  120. func handleAppProxyContainers(w http.ResponseWriter, r *http.Request) {
  121. if dockerManager == nil {
  122. utils.SendJSONResponse(w, "[]")
  123. return
  124. }
  125. containers, err := dockerManager.ListContainers()
  126. if err != nil {
  127. utils.SendErrorResponse(w, err.Error())
  128. return
  129. }
  130. published := map[string][]string{}
  131. for _, ep := range appProxyManager.List() {
  132. if ep.Container != "" {
  133. published[ep.Container] = append(published[ep.Container], ep.Slug)
  134. }
  135. }
  136. results := []appProxyContainer{}
  137. for _, c := range containers {
  138. ports := docker.ParsePublishedPorts(c.Ports)
  139. targets := []string{}
  140. for _, p := range ports {
  141. targets = append(targets, p.Target())
  142. }
  143. results = append(results, appProxyContainer{
  144. ID: c.ID,
  145. Name: c.Names,
  146. Image: c.Image,
  147. State: c.State,
  148. Ports: ports,
  149. Targets: targets,
  150. Published: published[c.Names],
  151. })
  152. }
  153. js, _ := json.Marshal(results)
  154. utils.SendJSONResponse(w, string(js))
  155. }