Bläddra i källkod

feat(api): add /lsmod

KernelDeimos 9 månader sedan
förälder
incheckning
32f0edb93a

+ 3 - 0
src/backend/src/CoreModule.js

@@ -317,6 +317,9 @@ const install = async ({ services, app, useapi }) => {
 
     const { FeatureFlagService } = require('./services/FeatureFlagService');
     services.registerService('feature-flag', FeatureFlagService);
+
+    const { KernelInfoService } = require('./services/KernelInfoService');
+    services.registerService('kernel-info', KernelInfoService);
 }
 
 const install_legacy = async ({ services }) => {

+ 1 - 0
src/backend/src/Kernel.js

@@ -136,6 +136,7 @@ class Kernel extends AdvancedBase {
 
         // Internal modules
         for ( const module of this.modules ) {
+            services.registerModule(module.constructor.name, module);
             await module.install(Context.get());
         }
 

+ 1 - 0
src/backend/src/data/hardcoded-permissions.js

@@ -78,6 +78,7 @@ const hardcoded_user_group_permissions = {
             'driver': {},
             'service': {},
             'feature': {},
+            'kernel-info': {},
         },
         'b7220104-7905-4985-b996-649fdcdb3c8f': {
             'service:hello-world:ii:hello-world': policy_perm('temp.es'),

+ 32 - 0
src/backend/src/services/Container.js

@@ -29,7 +29,33 @@ class Container {
         this.instances_ = {};
         this.implementors_ = {};
         this.ready = new TeePromise();
+        
+        this.modname_ = null;
+        this.modules_ = {}
+    }
+    
+    registerModule (name, module) {
+        this.modules_[name] = {
+            services_l: [],
+            services_m: {},
+            module
+        };
+        this.setModuleName(name);
     }
+    
+    /**
+     * Sets the name of the current module registering services.
+     * 
+     * Note: this is an antipattern; it would be a bit better to
+     * provide the module name while registering a service, but
+     * this requires making an implementor of Container's interface
+     * with this as a hidden variable so as not to break existing
+     * modules.
+     */
+    setModuleName (name) {
+        this.modname_ = name;
+    }
+
     /**
      * registerService registers a service with the servuces container.
      * 
@@ -44,6 +70,12 @@ class Container {
             : new cls({ services: this, config, my_config, name, args }) ;
         this.instances_[name] = instance;
         
+        if ( this.modname_ ) {
+            const mod_entry = this.modules_[this.modname_];
+            mod_entry.services_l.push(name);
+            mod_entry.services_m[name] = true;
+        }
+        
         if ( !(instance instanceof AdvancedBase) ) return;
         
         const traits = instance.list_traits();

+ 117 - 0
src/backend/src/services/KernelInfoService.js

@@ -0,0 +1,117 @@
+const configurable_auth = require("../middleware/configurable_auth");
+const { Context } = require("../util/context");
+const { Endpoint } = require("../util/expressutil");
+const BaseService = require("./BaseService");
+const { Interface } = require("./drivers/meta/Construct");
+
+const PERM_SEE_ALL = 'kernel-info:see-all-services';
+const PERM_SEE_DRIVERS = 'kernel-info:see-all-drivers';
+
+class KernelInfoService extends BaseService {
+    async _init () {
+        //
+    }
+
+    ['__on_install.routes'] (_, { app }) {
+        const router = (() => {
+            const require = this.require;
+            const express = require('express');
+            return express.Router();
+        })();
+        
+        app.use('/', router);
+        
+        Endpoint({
+            route: '/lsmod',
+            methods: ['GET', 'POST'],
+            mw: [
+                configurable_auth(),
+            ],
+            handler: async (req, res) => {
+                const svc_permission = this.services.get('permission');
+                
+                const actor = Context.get('actor');
+                const can_see_all = actor &&
+                    await svc_permission.check(actor, PERM_SEE_ALL);
+                const can_see_drivers = actor &&
+                    await svc_permission.check(actor, PERM_SEE_DRIVERS);
+                
+                const interfaces = {};
+                const svc_registry = this.services.get('registry');
+                const col_interfaces = svc_registry.get('interfaces');
+                for ( const interface_name of col_interfaces.keys() ) {
+                    const iface = col_interfaces.get(interface_name);
+                    console.log('-->', interface_name, iface);
+                    if ( iface === undefined ) continue;
+                    if ( iface.no_sdk ) continue;
+                    interfaces[interface_name] = {
+                        spec: (new Interface(
+                            iface,
+                            { name: interface_name }
+                        )).serialize(),
+                        implementors: {}
+                    }
+                }
+
+                const services = [];
+                const modules = [];
+                for ( const k in this.services.modules_ ) {
+                    const module_info = {
+                        name: k,
+                        services: []
+                    };
+                    
+                    modules.push(module_info);
+                    
+                    for ( const s_k of this.services.modules_[k].services_l ) {
+                        const service_info = {
+                            name: s_k,
+                            traits: []
+                        };
+                        services.push(service_info);
+                        
+                        const service = this.services.get(s_k);
+                        if ( service.list_traits ) {
+                            const traits = service.list_traits();
+                            for ( const trait of traits ) {
+                                const corresponding_iface = interfaces[trait];
+                                if ( ! corresponding_iface ) continue;
+                                corresponding_iface.implementors[s_k] = {};
+                            }
+                            service_info.traits = service.list_traits();
+                        }
+                    }
+                }
+                
+                // If actor doesn't have permission to see all drivers,
+                // (granted by either "can_see_all" or "can_see_drivers")
+                if ( ! can_see_all && ! can_see_drivers ) {
+                    // only show interfaces with at least one implementation
+                    // that the actor has permission to use
+                    for ( const iface_name in interfaces ) {
+                        for ( const impl_name in interfaces[iface_name].implementors ) {
+                            const perm = `service:${impl_name}:ii:${iface_name}`;
+                            const can_see_this = actor &&
+                                await svc_permission.check(actor, perm);
+                            if ( ! can_see_this ) {
+                                delete interfaces[iface_name].implementors[impl_name];
+                            }
+                        }
+                        if ( Object.keys(interfaces[iface_name].implementors).length < 1 ) {
+                            delete interfaces[iface_name];
+                        }
+                    }
+                }
+                
+                res.json({
+                    interfaces,
+                    ...(can_see_all ? { services } : {})
+                });
+            }
+        }).attach(router);
+    }
+}
+
+module.exports = {
+    KernelInfoService,
+};

+ 2 - 1
src/backend/src/services/RegistryService.js

@@ -49,7 +49,8 @@ class MapCollection extends AdvancedBase {
     }
     
     keys () {
-        return this.kv.keys(`registry:map:${this.map_id}:*`);
+        const keys = this.kv.keys(`registry:map:${this.map_id}:*`);
+        return keys.map(k => k.slice(`registry:map:${this.map_id}:`.length));
     }
 
     _mk_key (key) {

+ 8 - 0
src/backend/src/services/auth/PermissionService.js

@@ -214,6 +214,14 @@ class PermissionService extends BaseService {
         }
         return permission;
     }
+    
+    async check (actor, permission_options) {
+        // TODO: optimized implementation for check instead of
+        //       delegating to the scan() method
+        const reading = await this.scan(actor, permission_options);
+        const options = PermissionUtil.reading_to_options(reading);
+        return options.length > 0;
+    }
 
     async scan (actor, permission_options) {
         const reading = [];