diff --git a/OpenApi.yml b/OpenApi.yml index 791984d..536b02a 100644 --- a/OpenApi.yml +++ b/OpenApi.yml @@ -246,6 +246,10 @@ components: RoomMount: type: object properties: + type: + type: string + enum: [ private, template, public ] + example: private host_path: type: string example: /profile diff --git a/client/src/api/api.ts b/client/src/api/api.ts index f2f1246..18c1af1 100644 --- a/client/src/api/api.ts +++ b/client/src/api/api.ts @@ -113,6 +113,12 @@ export interface RoomMember { * @interface RoomMount */ export interface RoomMount { + /** + * + * @type {string} + * @memberof RoomMount + */ + type?: RoomMountTypeEnum; /** * * @type {string} @@ -126,6 +132,17 @@ export interface RoomMount { */ container_path?: string; } + +/** + * @export + * @enum {string} + */ +export enum RoomMountTypeEnum { + private = 'private', + template = 'template', + public = 'public' +} + /** * * @export diff --git a/client/src/components/RoomsCreate.vue b/client/src/components/RoomsCreate.vue index 61aec88..4513731 100644 --- a/client/src/components/RoomsCreate.vue +++ b/client/src/components/RoomsCreate.vue @@ -210,7 +210,7 @@

Mounts

- mdi-plus + mdi-plus
diff --git a/internal/config/room.go b/internal/config/room.go index 167c200..2c45ac7 100644 --- a/internal/config/room.go +++ b/internal/config/room.go @@ -22,6 +22,8 @@ type Room struct { StorageInternal string StorageExternal string + MountsWhitelist []string + InstanceName string InstanceUrl string @@ -73,6 +75,11 @@ func (Room) Init(cmd *cobra.Command) error { return err } + cmd.PersistentFlags().StringSlice("mounts.whitelist", []string{}, "whitelisted public mounts for containers") + if err := viper.BindPFlag("mounts.whitelist", cmd.PersistentFlags().Lookup("mounts.whitelist")); err != nil { + return err + } + // Instance cmd.PersistentFlags().String("instance.name", "neko-rooms", "unique instance name (if running muliple on the same host)") @@ -159,6 +166,15 @@ func (s *Room) Set() { s.StorageEnabled = false } + s.MountsWhitelist = viper.GetStringSlice("mounts.whitelist") + for _, path := range s.MountsWhitelist { + path = filepath.Clean(path) + + if !filepath.IsAbs(path) { + log.Panic().Msg("invalid `mounts.whitelist`, must be an absolute path") + } + } + s.InstanceName = viper.GetString("instance.name") if !dockerNames.RestrictedNamePattern.MatchString(s.InstanceName) { log.Panic().Msg("invalid `instance.name`, must match " + dockerNames.RestrictedNameChars) diff --git a/internal/room/manager.go b/internal/room/manager.go index 696ca33..ee8d24b 100644 --- a/internal/room/manager.go +++ b/internal/room/manager.go @@ -26,10 +26,11 @@ import ( ) const ( - frontendPort = 8080 - roomStoragePath = "./rooms" - roomStorageUid = 1000 - roomStorageGid = 1000 + frontendPort = 8080 + templateStoragePath = "./templates" + privateStoragePath = "./rooms" + privateStorageUid = 1000 + privateStorageGid = 1000 ) func New(config *config.Room) *RoomManagerCtx { @@ -195,6 +196,8 @@ func (manager *RoomManagerCtx) Create(settings types.RoomSettings) (string, erro mounts := []dockerMount.Mount{} for _, mount := range settings.Mounts { + readOnly := false + hostPath := filepath.Clean(mount.HostPath) containerPath := filepath.Clean(mount.ContainerPath) @@ -202,23 +205,49 @@ func (manager *RoomManagerCtx) Create(settings types.RoomSettings) (string, erro return "", fmt.Errorf("mount paths must be absolute") } - externalPath := path.Join(manager.config.StorageExternal, roomStoragePath, roomName, hostPath) - internalPath := path.Join(manager.config.StorageInternal, roomStoragePath, roomName, hostPath) + // private container's data + if mount.Type == types.MountPrivate { + // ensure that target exists + internalPath := path.Join(manager.config.StorageInternal, privateStoragePath, roomName, hostPath) + if err := os.MkdirAll(internalPath, os.ModePerm); err != nil { + return "", err + } - if err := os.MkdirAll(internalPath, os.ModePerm); err != nil { - return "", err - } + if err := utils.ChownR(internalPath, privateStorageUid, privateStorageGid); err != nil { + return "", err + } - if err := utils.ChownR(internalPath, roomStorageUid, roomStorageGid); err != nil { - return "", err + // prefix host path + hostPath = path.Join(manager.config.StorageExternal, privateStoragePath, roomName, hostPath) + } else if mount.Type == types.MountTemplate { + // readonly template data + readOnly = true + + // prefix host path + hostPath = path.Join(manager.config.StorageExternal, templateStoragePath, hostPath) + } else if mount.Type == types.MountPublic { + // public whitelisted mounts + var isAllowed = false + for _, path := range manager.config.MountsWhitelist { + if strings.HasPrefix(hostPath, path) { + isAllowed = true + break + } + } + + if !isAllowed { + return "", fmt.Errorf("mount path is not whitelisted in config") + } + } else { + return "", fmt.Errorf("unknown mount type %q", mount.Type) } mounts = append(mounts, dockerMount.Mount{ Type: dockerMount.TypeBind, - Source: externalPath, + Source: hostPath, Target: containerPath, - ReadOnly: false, + ReadOnly: readOnly, Consistency: dockerMount.ConsistencyDefault, BindOptions: &dockerMount.BindOptions{ @@ -349,11 +378,25 @@ func (manager *RoomManagerCtx) GetSettings(id string) (*types.RoomSettings, erro return nil, err } + privateStorageRoot := path.Join(manager.config.StorageExternal, privateStoragePath, roomName) + templateStorageRoot := path.Join(manager.config.StorageExternal, templateStoragePath) + mounts := []types.RoomMount{} - dataRoot := path.Join(manager.config.StorageExternal, roomStoragePath, roomName) for _, mount := range container.Mounts { + mountType := types.MountPublic + hostPath := mount.Source + + if strings.HasPrefix(hostPath, privateStorageRoot) { + mountType = types.MountPrivate + hostPath = strings.TrimPrefix(hostPath, privateStorageRoot) + } else if strings.HasPrefix(hostPath, templateStorageRoot) { + mountType = types.MountTemplate + hostPath = strings.TrimPrefix(hostPath, templateStorageRoot) + } + mounts = append(mounts, types.RoomMount{ - HostPath: strings.TrimPrefix(mount.Source, dataRoot), + Type: mountType, + HostPath: hostPath, ContainerPath: mount.Destination, }) } diff --git a/internal/types/room.go b/internal/types/room.go index bfcbdb9..f6808af 100644 --- a/internal/types/room.go +++ b/internal/types/room.go @@ -39,9 +39,18 @@ type RoomEntry struct { Created time.Time `json:"created"` } +type MountType string + +const ( + MountPrivate MountType = "private" + MountTemplate MountType = "template" + MountPublic MountType = "public" +) + type RoomMount struct { - HostPath string `json:"host_path"` - ContainerPath string `json:"container_path"` + Type MountType `json:"type"` + HostPath string `json:"host_path"` + ContainerPath string `json:"container_path"` } type RoomSettings struct {