UNPKG

@jfvilas/plugin-kubelog-backend

Version:
204 lines (198 loc) 8.81 kB
'use strict'; var express = require('express'); var Router = require('express-promise-router'); var catalogClient = require('@backstage/catalog-client'); var config = require('./config.cjs.js'); var KubelogStaticData = require('../model/KubelogStaticData.cjs.js'); var permissions = require('./permissions.cjs.js'); function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; } var express__default = /*#__PURE__*/_interopDefaultCompat(express); var Router__default = /*#__PURE__*/_interopDefaultCompat(Router); const debug = (a) => { if (process.env.KUBELOGDEBUG) console.log(a); }; async function createRouter(options) { const { configSvc, loggerSvc, userInfoSvc, authSvc, httpAuthSvc, discoverySvc } = options; loggerSvc.info("Loading static config"); if (!configSvc.has("kubernetes.clusterLocatorMethods")) { loggerSvc.error(`Kueblog will not start, there is no 'clusterLocatorMethods' defined in app-config.`); throw new Error("Kueblog backend will not be available."); } try { config.loadClusters(loggerSvc, configSvc); } catch (err) { var txt = `Errors detected reading static configuration: ${err}`; loggerSvc.error(txt); throw new Error(txt); } loggerSvc.info("Static config loaded"); if (configSvc.subscribe) { configSvc.subscribe(() => { try { loggerSvc.warn("Change detected on app-config, Kubelog will update config."); config.loadClusters(loggerSvc, configSvc); } catch (err) { loggerSvc.error(`Errors detected reading new configuration: ${err}`); } }); } else { loggerSvc.info("Kubelog cannot subscribe to config changes."); } const router = Router__default.default(); router.use(express__default.default.json()); const createAuthFetchApi = (token) => { return { fetch: async (input, init) => { init = init || {}; init.headers = { ...init.headers, Authorization: `Bearer ${token}` }; return fetch(input, init); } }; }; const getValidClusters = async (entityName) => { var clusterList = []; for (const name of KubelogStaticData.KubelogStaticData.clusterKubelogData.keys()) { var url = KubelogStaticData.KubelogStaticData.clusterKubelogData.get(name)?.kwirthHome; var apiKeyStr = KubelogStaticData.KubelogStaticData.clusterKubelogData.get(name)?.kwirthApiKey; var title = KubelogStaticData.KubelogStaticData.clusterKubelogData.get(name)?.title; var queryUrl = url + `/managecluster/find?label=backstage.io%2fkubernetes-id&entity=${entityName}&type=pod&data=id`; debug("queryUrl"); debug(queryUrl); try { var fetchResp = await fetch(queryUrl, { headers: { "Authorization": "Bearer " + apiKeyStr } }); if (fetchResp.status === 200) { var jsonResp = await fetchResp.json(); if (jsonResp) { debug("jsonResp"); debug(jsonResp); clusterList.push({ name, url, title, data: jsonResp }); } } else { loggerSvc.warn(`Invalid response from cluster ${name}: ${fetchResp.status}`); clusterList.push({ name, url, title, data: [] }); } } catch (err) { loggerSvc.warn(`Cannot access cluster ${name} (URL: ${queryUrl}): ${err}`); clusterList.push({ name, url, title, data: [] }); } } return clusterList; }; const setAccessKey = async (reqScope, cluster, reqPod, userName, keyName) => { var kwirthResource = `${permissions.KWIRTH_SCOPE[reqScope]}:${reqPod.namespace}::${reqPod.name}:`; var url = KubelogStaticData.KubelogStaticData.clusterKubelogData.get(cluster.name)?.kwirthHome; var apiKeyStr = KubelogStaticData.KubelogStaticData.clusterKubelogData.get(cluster.name)?.kwirthApiKey; let apiKey = { description: `Backstage API key for user ${userName} accessing pod ${reqPod.namespace}/${reqPod.name}`, expire: Date.now() + 60 * 60 * 1e3, accessKey: { //id: '', type: "volatile", resources: kwirthResource } }; var fetchResp = await fetch(url + "/key", { method: "POST", body: JSON.stringify(apiKey), headers: { "Content-Type": "application/json", Authorization: "Bearer " + apiKeyStr } }); if (fetchResp.status === 200) { var data = await fetchResp.json(); reqPod[keyName] = data.accessKey; } else { loggerSvc.warn(`Invalid response obtaining key from cluster ${cluster.name}: ${fetchResp.status}`); } }; const addAccessKeys = async (reqScopeStr, foundClusters, entityName, userEntityRef, userGroups, keyName) => { var reqScope = permissions.KWIRTH_SCOPE[reqScopeStr]; if (!reqScope) { loggerSvc.info(`Invalid scope requested: ${reqScopeStr}`); return; } var principal = userEntityRef.split(":")[1]; var username = principal.split("/")[1]; debug("addAccesssKeys"); debug("reqScope " + reqScopeStr); for (var foundCluster of foundClusters) { debug("foundCluster " + foundCluster.name); debug("podDataLength " + foundCluster.data.length); for (var podData of foundCluster.data) { debug(">>> checkNamespaceAccess"); var allowedToNamespace = permissions.checkNamespaceAccess(foundCluster, podData, userEntityRef, userGroups); debug("<<< checkNamespaceAccess"); debug("allowedToNamespace: " + allowedToNamespace); if (allowedToNamespace) { var clusterDef = KubelogStaticData.KubelogStaticData.clusterKubelogData.get(foundCluster.name); var podPermissionSet = permissions.getPodPermissionSet(reqScope, clusterDef); if (!podPermissionSet) { loggerSvc.warn(`Pod permission set not found: ${reqScope}`); continue; } var namespaceRestricted = podPermissionSet.some((pp) => pp.namespace === podData.namespace); if (!namespaceRestricted) { await setAccessKey(reqScope, foundCluster, podData, username, keyName); } else { var allowedToPod = permissions.checkPodAccess(podData, podPermissionSet, entityName, userEntityRef, userGroups); if (allowedToPod) { await setAccessKey(reqScope, foundCluster, podData, username, keyName); } } } } } }; const getUserGroups = async (userInfo) => { const { token } = await authSvc.getPluginRequestToken({ onBehalfOf: await authSvc.getOwnServiceCredentials(), targetPluginId: "catalog" }); const catalogClient$1 = new catalogClient.CatalogClient({ discoveryApi: discoverySvc, fetchApi: createAuthFetchApi(token) }); const entity = await catalogClient$1.getEntityByRef(userInfo.userEntityRef); var userGroupsRefs = []; if (entity?.spec.memberOf) userGroupsRefs = entity?.spec.memberOf; return userGroupsRefs; }; const processStart = async (req, res) => { const credentials = await httpAuthSvc.credentials(req, { allow: ["user"] }); const userInfo = await userInfoSvc.getUserInfo(credentials); var userGroupsRefs = await getUserGroups(userInfo); var foundClusters = await getValidClusters(req.body.metadata.name); await addAccessKeys("view", foundClusters, req.body.metadata.name, userInfo.userEntityRef, userGroupsRefs, "accessKey"); res.status(200).send(foundClusters); }; const processVersion = async (_req, res) => { res.status(200).send({ version: KubelogStaticData.VERSION }); }; const processAccess = async (req, res) => { if (!req.query["scopes"]) { res.status(400).send(); return; } var reqScopes = req.query["scopes"].toString().split(","); const credentials = await httpAuthSvc.credentials(req, { allow: ["user"] }); const userInfo = await userInfoSvc.getUserInfo(credentials); var userGroupsRefs = await getUserGroups(userInfo); loggerSvc.info(`Checking reqScopes '${req.query["scopes"]}' scopes to pod: '${req.body.metadata.namespace + "/" + req.body.metadata.name}' for user '${userInfo.userEntityRef}'`); var foundClusters = await getValidClusters(req.body.metadata.name); debug("foundClusters"); debug(foundClusters); for (var reqScopeStr of reqScopes) { await addAccessKeys(reqScopeStr, foundClusters, req.body.metadata.name, userInfo.userEntityRef, userGroupsRefs, reqScopeStr + "AccessKey"); } res.status(200).send(foundClusters); }; router.post(["/start"], (req, res) => { processStart(req, res); }); router.post(["/access"], (req, res) => { processAccess(req, res); }); router.get(["/version"], (req, res) => { processVersion(req, res); }); return router; } exports.createRouter = createRouter; //# sourceMappingURL=router.cjs.js.map