feat(api): add /lsmod

This commit is contained in:
KernelDeimos
2024-08-21 18:31:16 -04:00
committed by Eric Dubé
parent d009cd0aaf
commit 32f0edb93a
7 changed files with 164 additions and 1 deletions
+3
View File
@@ -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
View File
@@ -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());
}
@@ -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
View File
@@ -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();
@@ -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
View File
@@ -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) {
@@ -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 = [];