diff --git a/auth/auth.go b/auth/auth.go index 8ea2ab0..2714931 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -2,6 +2,7 @@ package auth import ( "encoding/json" + "errors" "fmt" "log" "net/http" @@ -158,27 +159,33 @@ func (con AuthApi) Verify(ctx *gin.Context) { claimsPointer, exists := ctx.Get("claims") if !exists { ctx.Status(403) + ctx.Error(errors.New("Failed to get claims, not logged in")) return } - claims := claimsPointer.(*AuthClaims) + claims, ok := claimsPointer.(*AuthClaims) + if !ok { + ctx.Error(errors.New("Failed to convert claims")) + ctx.Status(500) + return + } - forwarded_host := ctx.Request.Header.Get("x-forwarded-host") - log.Printf("Checking auth of %s", forwarded_host) + forwardedUri := ctx.Request.Header.Get("x-forwarded-uri") - domainSegments := strings.Split(forwarded_host, ".") + pathSegments := strings.Split(forwardedUri, "/") - serverId, service := domainSegments[0], domainSegments[1] + serverId, service := pathSegments[2], pathSegments[1] switch service { case "browsers": + fmt.Printf("%#v %s", claims, serverId) serverPermissions, err := con.serverAuthDbHandler.GetPermissions(ctx, claims.Username, serverId) if err != nil { ctx.AbortWithError(500, err) return } if (claims.Permissions|serverPermissions)&models.Admin == models.Admin { - ctx.Header("X-Username", claims.Username) + ctx.Header("X-Auth-Username", claims.Username) log.Printf("Set header X-Username %s", claims.Username) ctx.Status(200) return @@ -186,7 +193,7 @@ func (con AuthApi) Verify(ctx *gin.Context) { case "cloud": if claims.Permissions&models.Cloud == models.Cloud || claims.Permissions&models.Admin == models.Admin { log.Printf("Set header X-Username %s", claims.Username) - ctx.Header("X-Username", claims.Username) + ctx.Header("X-Auth-Username", claims.Username) ctx.Status(200) return } @@ -201,20 +208,26 @@ func LoadGroup(group *gin.RouterGroup, config models.GlobalConfig) gin.HandlerFu panic(err) } + serverAuthDbHandler, err := factories.GetServersAuthorizationDbHandler(config.ServersAuthorizationDatabase) + if err != nil { + panic(err) + } + inviteHandler, err := factories.GetInviteTokenDbHandler(config.Authentication.UserPass.InviteTokenDatabase) if err != nil { panic(err) } connection := AuthApi{ - userAuthDbHandler: userAuthHandler, - tokenHandler: inviteHandler, - config: config, + userAuthDbHandler: userAuthHandler, + serverAuthDbHandler: serverAuthDbHandler, + tokenHandler: inviteHandler, + config: config, } group.POST("/signin", connection.signIn) group.POST("/signup", connection.signUp) - group.Any("/verify", connection.Verify) + group.Any("/verify", connection.LoggedIn, connection.Verify) return connection.LoggedIn } diff --git a/config.yaml b/config.yaml index c484356..810f394 100644 --- a/config.yaml +++ b/config.yaml @@ -40,9 +40,7 @@ authentication: instancemanager: type: "docker" docker: - games_domain: "games.acooldomain.co" - browsers_domain: "browsers.acooldomain.co" - certificate_resolver: "letsencrypt" + browsers_sub_domain: browsers file_browser: image: registry: "filebrowser/filebrowser" diff --git a/dbhandler/mongo/servers_authorization.go b/dbhandler/mongo/servers_authorization.go index febb13e..71087c0 100644 --- a/dbhandler/mongo/servers_authorization.go +++ b/dbhandler/mongo/servers_authorization.go @@ -126,6 +126,7 @@ func (self *ServersAuthorizationDbHandler) SetPermissions(ctx context.Context, u func (self *ServersAuthorizationDbHandler) GetPermissions(ctx context.Context, username string, serverId string) (models.Permission, error) { var serverPermissions ServerPermissions + err := self.collection.FindOne( ctx, bson.M{ diff --git a/dbhandler/mongo/user_pass_authentication.go b/dbhandler/mongo/user_pass_authentication.go index c8edade..43753c0 100644 --- a/dbhandler/mongo/user_pass_authentication.go +++ b/dbhandler/mongo/user_pass_authentication.go @@ -26,7 +26,7 @@ type UserPassAuthenticationDbHandler struct { } func (self *UserPassAuthenticationDbHandler) ListUsers(ctx context.Context) ([]models.User, error) { - cursor, err := self.collection.Find(ctx, nil) + cursor, err := self.collection.Find(ctx, bson.M{}) if err != nil { return nil, err diff --git a/factories/instancemanagers.go b/factories/instancemanagers.go index 18d56cd..bb246d2 100644 --- a/factories/instancemanagers.go +++ b/factories/instancemanagers.go @@ -14,11 +14,11 @@ var ( instanceManagerMutex sync.Mutex ) -func getDockerCacheKey(config *models.DockerInstanceManagerConfig) string { - return "Docker" +func getDockerCacheKey(config *models.DockerInstanceManagerConfig, siteDomain string) string { + return "Docker/" + siteDomain } -func GetInstanceManager(config models.InstanceManagerConfig) (instancemanager.InstanceManager, error) { +func GetInstanceManager(config models.InstanceManagerConfig, siteDomain string) (instancemanager.InstanceManager, error) { var key string var handler instancemanager.InstanceManager var err error @@ -29,7 +29,7 @@ func GetInstanceManager(config models.InstanceManagerConfig) (instancemanager.In if config.Docker == nil { return nil, errors.New("missing Docker configuration") } - key = getDockerCacheKey(config.Docker) + key = getDockerCacheKey(config.Docker, siteDomain) default: return nil, errors.New("unsupported database type") } @@ -43,7 +43,7 @@ func GetInstanceManager(config models.InstanceManagerConfig) (instancemanager.In switch config.Type { case models.DOCKER: - handler, err = docker.NewInstanceManager(*config.Docker) + handler, err = docker.NewInstanceManager(*config.Docker, siteDomain) if err != nil { return nil, err } diff --git a/instancemanager/docker/instance_manager.go b/instancemanager/docker/instance_manager.go index e709d37..907c1c4 100644 --- a/instancemanager/docker/instance_manager.go +++ b/instancemanager/docker/instance_manager.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "log" "maps" "net" "strings" @@ -23,8 +24,10 @@ import ( type InstanceManager struct { instancemanager.InstanceManager - client *client.Client - config models.DockerInstanceManagerConfig + client *client.Client + config models.DockerInstanceManagerConfig + browsersSubDomain string + siteDomain string } func (self *InstanceManager) containerList(ctx context.Context, labels ContainerLabels, all bool) ([]container.Summary, error) { @@ -129,7 +132,7 @@ func (self *InstanceManager) GetServer(ctx context.Context, serverId string) (*i RunningCommand: "", RunningImage: nil, Ports: nil, - Domain: self.config.GamesDomain, + Domain: self.siteDomain, }, nil } @@ -146,7 +149,7 @@ func (self *InstanceManager) GetServer(ctx context.Context, serverId string) (*i RunningCommand: runningCommand, Ports: convertContainerPortsToPorts(serverContainer.Ports), RunningImage: &image, - Domain: self.config.GamesDomain, + Domain: self.siteDomain, }, nil } @@ -170,7 +173,7 @@ func (self *InstanceManager) ListServers(ctx context.Context) ([]instancemanager RunningCommand: "", Ports: nil, RunningImage: nil, - Domain: self.config.GamesDomain, + Domain: self.siteDomain, } } @@ -328,18 +331,22 @@ func (self *InstanceManager) StopServer(ctx context.Context, serverId string) er return nil } -func (self *InstanceManager) CreateServer(ctx context.Context) (*instancemanager.Server, error) { - labels, err := convertLabelsToMap(VolumeLabels{ - Type: Game, - }) +func (self *InstanceManager) createVolume(ctx context.Context, volumeLabels VolumeLabels) (volume.Volume, error) { + labels, err := convertLabelsToMap(volumeLabels) if err != nil { - return nil, err + return volume.Volume{}, err } volume, err := self.client.VolumeCreate(ctx, volume.CreateOptions{ Labels: *labels, }) + return volume, err +} + +func (self *InstanceManager) CreateServer(ctx context.Context) (*instancemanager.Server, error) { + volume, err := self.createVolume(ctx, VolumeLabels{Type: Game}) + if err != nil { return nil, err } @@ -349,7 +356,7 @@ func (self *InstanceManager) CreateServer(ctx context.Context) (*instancemanager Running: false, RunningImage: nil, RunningCommand: "", - Domain: self.config.GamesDomain, + Domain: self.siteDomain, }, nil } @@ -455,8 +462,9 @@ func (self *InstanceManager) GetFileBrowser(ctx context.Context, serverId string } return &models.FileBrowser{ - Url: containerLabels.VolumeId[:12] + "." + self.config.BrowsersDomain, - Id: rawContainer.ID, + Url: fmt.Sprintf("%s/browsers/%s/", self.siteDomain, containerLabels.VolumeId), + ServerId: containerLabels.VolumeId, + Id: rawContainer.ID, }, nil } @@ -475,8 +483,9 @@ func (self *InstanceManager) ListFileBrowsers(ctx context.Context) ([]models.Fil } fileBrowsers[i] = models.FileBrowser{ - Url: containerLabels.VolumeId[:12] + "." + self.config.BrowsersDomain, - Id: rawContainer.ID, + Url: fmt.Sprintf("%s/browsers/%s/", self.siteDomain, containerLabels.VolumeId), + ServerId: containerLabels.VolumeId, + Id: rawContainer.ID, } } @@ -492,20 +501,71 @@ func (self *InstanceManager) StartFileBrowser(ctx context.Context, serverId stri return nil, err } - labelId := serverId[:12] + labelId := server.Id + volumeLabels := VolumeLabels{ + Type: FileBrowser, + VolumeId: server.Id, + } + + containers, err := self.containerList(ctx, ContainerLabels{VolumeId: serverId, Type: FileBrowser}, false) + + if len(containers) > 0 { + + return &models.FileBrowser{ + Url: fmt.Sprintf("%s/browsers/%s/", self.siteDomain, server.Id), + ServerId: server.Id, + Id: containers[0].ID, + }, nil + } + + volume, err := self.createVolume(ctx, volumeLabels) + + containerConfig, err := convertLabelsToMap(ContainerLabels{VolumeId: serverId, Type: FileBrowser}) + + if err != nil { + return nil, err + } + + PrepareDatabaseContainer, err := self.client.ContainerCreate( + ctx, + &container.Config{ + Entrypoint: []string{"/bin/sh"}, + Cmd: []string{"-c", "/filebrowser config init -d /tmp/database/database.db && /filebrowser config set -d /tmp/database/database.db --auth.method=proxy --auth.header=X-Auth-Username"}, + Image: fmt.Sprintf("%s:%s", self.config.FileBrowser.Image.Registry, self.config.FileBrowser.Image.Tag), + Labels: *containerConfig, + }, + &container.HostConfig{ + Mounts: []mount.Mount{{Source: volume.Name, Target: "/tmp/database", Type: mount.TypeVolume}}, + AutoRemove: true, + }, + &network.NetworkingConfig{}, + &v1.Platform{}, + "", + ) + + if err != nil { + return nil, err + } + + err = self.client.ContainerStart(ctx, PrepareDatabaseContainer.ID, container.StartOptions{}) + + if err != nil { + log.Printf("Failed to wait %s", err) + } + + responseChan, _ := self.client.ContainerWait(ctx, PrepareDatabaseContainer.ID, container.WaitConditionNotRunning) + response := <-responseChan + log.Printf("%#v\n", response) browserLabels := make(map[string]string) browserLabels["traefik.enable"] = "true" - browserLabels[fmt.Sprintf("traefik.http.routers.%s.rule", labelId)] = fmt.Sprintf("Host(`%s.browsers.%s`)", labelId, self.config.BrowsersDomain) + browserLabels[fmt.Sprintf("traefik.http.routers.%s.rule", labelId)] = fmt.Sprintf("Host(`%s`) && PathPrefix(`/browsers/%s/`)", self.siteDomain, labelId) browserLabels[fmt.Sprintf("traefik.http.routers.%s.entrypoints", labelId)] = self.config.FileBrowser.ReverseProxy.Entrypoint browserLabels[fmt.Sprintf("traefik.http.routers.%s.middlewares", labelId)] = strings.Join(self.config.FileBrowser.ReverseProxy.Middlewares, ",") if self.config.FileBrowser.ReverseProxy.Tls { - browserLabels[fmt.Sprintf("traefik.http.routers.%s.tls.domains[0].main", labelId)] = self.config.BrowsersDomain - browserLabels[fmt.Sprintf("traefik.http.routers.%s.tls.domains[0].sans", labelId)] = fmt.Sprintf("*.%s", self.config.BrowsersDomain) browserLabels[fmt.Sprintf("traefik.http.routers.%s.tls.certresolver", labelId)] = self.config.FileBrowser.ReverseProxy.TlsResolver } - containerConfig, err := convertLabelsToMap(ContainerLabels{VolumeId: serverId, Type: FileBrowser}) if err != nil { return nil, err } @@ -514,9 +574,7 @@ func (self *InstanceManager) StartFileBrowser(ctx context.Context, serverId stri command := self.config.FileBrowser.Command - if command == "" { - command = "--noauth -r /tmp/data" - } + command += fmt.Sprintf("-d /tmp/database/database.db -r /tmp/data -b /browsers/%s/", labelId) splitCommand, err := shellwords.Split(command) if err != nil { @@ -524,7 +582,7 @@ func (self *InstanceManager) StartFileBrowser(ctx context.Context, serverId stri } ContainerResponse, err := self.client.ContainerCreate( - context.TODO(), + ctx, &container.Config{ Cmd: splitCommand, Image: fmt.Sprintf("%s:%s", self.config.FileBrowser.Image.Registry, self.config.FileBrowser.Image.Tag), @@ -532,7 +590,7 @@ func (self *InstanceManager) StartFileBrowser(ctx context.Context, serverId stri Tty: true, }, &container.HostConfig{ - Mounts: []mount.Mount{{Source: server.Id, Target: "/tmp/data", Type: "volume"}}, + Mounts: []mount.Mount{{Source: server.Id, Target: "/tmp/data", Type: "volume"}, {Source: volume.Name, Target: "/tmp/database/", Type: mount.TypeVolume}}, AutoRemove: true, ConsoleSize: [2]uint{1000, 1000}, }, @@ -548,14 +606,15 @@ func (self *InstanceManager) StartFileBrowser(ctx context.Context, serverId stri return nil, err } - err = self.client.ContainerStart(context.TODO(), ContainerResponse.ID, container.StartOptions{}) + err = self.client.ContainerStart(ctx, ContainerResponse.ID, container.StartOptions{}) if err != nil { return nil, err } return &models.FileBrowser{ - Url: serverId[:12] + "." + self.config.BrowsersDomain, - Id: ContainerResponse.ID, + Url: fmt.Sprintf("%s/browsers/%s/", self.siteDomain, server.Id), + ServerId: server.Id, + Id: volume.Name, }, nil } @@ -566,16 +625,26 @@ func (self *InstanceManager) StopFileBrowser(ctx context.Context, serverId strin } for _, rawContainer := range containers { + stopChan, _ := self.client.ContainerWait(ctx, rawContainer.ID, container.WaitConditionRemoved) err := self.client.ContainerStop(ctx, rawContainer.ID, container.StopOptions{}) if err != nil { return err } + <-stopChan + for _, mount := range rawContainer.Mounts { + if mount.Destination == "/tmp/database" { + err := self.client.VolumeRemove(ctx, mount.Name, true) + if err != nil { + return err + } + } + } } return nil } -func NewInstanceManager(config models.DockerInstanceManagerConfig) (*InstanceManager, error) { +func NewInstanceManager(config models.DockerInstanceManagerConfig, siteDomain string) (*InstanceManager, error) { apiClient, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) if err != nil { return nil, err @@ -584,7 +653,8 @@ func NewInstanceManager(config models.DockerInstanceManagerConfig) (*InstanceMan defer apiClient.Close() return &InstanceManager{ - config: config, - client: apiClient, + config: config, + client: apiClient, + siteDomain: siteDomain, }, nil } diff --git a/instancemanager/docker/labels.go b/instancemanager/docker/labels.go index ffb84b1..382fcf8 100644 --- a/instancemanager/docker/labels.go +++ b/instancemanager/docker/labels.go @@ -19,7 +19,8 @@ type BrowserLabels struct { } type VolumeLabels struct { - Type ContainerType `json:"type,omitempty"` + Type ContainerType `json:"type,omitempty"` + VolumeId string `json:"volume_id,omitempty"` } type ImageLabels struct { diff --git a/models/config.go b/models/config.go index 7e0889e..9268270 100644 --- a/models/config.go +++ b/models/config.go @@ -65,10 +65,7 @@ type FileBrowserConfig struct { } type DockerInstanceManagerConfig struct { - GamesDomain string `yaml:"games_domain"` - BrowsersDomain string `yaml:"browsers_domain"` - CertificateResolver string `yaml:"certificate_resolver"` - FileBrowser FileBrowserConfig `yaml:"file_browser"` + FileBrowser FileBrowserConfig `yaml:"file_browser"` } type InstanceManagerConfig struct { diff --git a/models/server.go b/models/server.go index bca9e1d..807bcda 100644 --- a/models/server.go +++ b/models/server.go @@ -19,6 +19,7 @@ type Image struct { } type FileBrowser struct { - Id string - Url string + Id string + Url string + ServerId string } diff --git a/servers/browsers.go b/servers/browsers.go index e6786c3..ed3a370 100644 --- a/servers/browsers.go +++ b/servers/browsers.go @@ -7,6 +7,12 @@ import ( "github.com/gin-gonic/gin" ) +type FileBrowser struct { + Id string `json:"Id"` + Url string `json:"Url"` + ConnectedTo ServerInfo `json:"ConnectedTo"` +} + func (con ServersApi) GetBrowsers(ctx *gin.Context) { fileBrowsers, err := con.InstanceManager.ListFileBrowsers(ctx) @@ -15,7 +21,34 @@ func (con ServersApi) GetBrowsers(ctx *gin.Context) { return } - ctx.JSON(200, fileBrowsers) + fileBrowserInfos := make([]FileBrowserInfo, len(fileBrowsers)) + + for i, browser := range fileBrowsers { + server, err := con.ServersDbHandler.GetServer(ctx, browser.ServerId) + if err != nil { + ctx.Error(err) + continue + } + + serverInfo := ServerInfo{ + Id: server.Id, + Image: ImageInfo{ + Name: server.Image.Registry, + Version: server.Image.Tag, + }, + OwnerId: server.Owner, + DefaultCommand: server.Command, + Nickname: server.Nickname, + } + + fileBrowserInfos[i] = FileBrowserInfo{ + ConnectedTo: serverInfo, + Url: browser.Url, + Id: browser.Id, + } + } + + ctx.JSON(200, fileBrowserInfos) } func (con ServersApi) StopBrowser(ctx *gin.Context) { @@ -40,7 +73,7 @@ func LoadBrowsersGroup(group *gin.RouterGroup, config models.GlobalConfig) { panic(err) } - instanceManager, err := factories.GetInstanceManager(config.InstanceManager) + instanceManager, err := factories.GetInstanceManager(config.InstanceManager, config.Domain) if err != nil { panic(err) } diff --git a/servers/images.go b/servers/images.go index e195440..816be97 100644 --- a/servers/images.go +++ b/servers/images.go @@ -49,7 +49,7 @@ func (con ServersApi) GetImages(ctx *gin.Context) { } func LoadeImagesGroup(group *gin.RouterGroup, config models.GlobalConfig) { - instanceManager, err := factories.GetInstanceManager(config.InstanceManager) + instanceManager, err := factories.GetInstanceManager(config.InstanceManager, config.Domain) if err != nil { panic(err) } diff --git a/servers/servers.go b/servers/servers.go index 3bb8e1e..fca29b0 100644 --- a/servers/servers.go +++ b/servers/servers.go @@ -31,6 +31,7 @@ type ServersApi struct { ServersDbHandler dbhandler.ServersDbHandler InstanceManager instancemanager.InstanceManager ServerAuthorization dbhandler.ServersAuthorizationDbHandler + config models.GlobalConfig } type ImageInfo struct { @@ -513,7 +514,7 @@ func (con ServersApi) SetServerUserPermissions(ctx *gin.Context) { } func LoadGroup(group *gin.RouterGroup, config models.GlobalConfig) { - instanceManager, err := factories.GetInstanceManager(config.InstanceManager) + instanceManager, err := factories.GetInstanceManager(config.InstanceManager, config.Domain) if err != nil { panic(err) }