UNPKG

@jfvilas/plugin-kwirth-backend

Version:
245 lines (239 loc) 11.3 kB
'use strict'; var express = require('express'); var Router = require('express-promise-router'); var catalogClient = require('@backstage/catalog-client'); var pluginKwirthCommon = require('@jfvilas/plugin-kwirth-common'); var config = require('./config.cjs.js'); var KwirthStaticData = require('../model/KwirthStaticData.cjs.js'); var permissions = require('./permissions.cjs.js'); var kwirthCommon = require('@jfvilas/kwirth-common'); var version = require('../version.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); async function createRouter(options) { const { configSvc, loggerSvc, userInfoSvc, authSvc, httpAuthSvc, discoverySvc } = options; loggerSvc.info("Loading static config"); if (!configSvc.has("kubernetes.clusterLocatorMethods")) { loggerSvc.error(`Kwirth will not start, there is no 'clusterLocatorMethods' defined in app-config.`); throw new Error("Kwirth backend will not be available."); } try { config.loadClusters(loggerSvc, configSvc); config.loadKwirthInfo(loggerSvc); } catch (err) { let txt = `Errors detected reading static configuration: ${err}`; loggerSvc.error(txt); throw new Error(txt); } if (configSvc.subscribe) { configSvc.subscribe(() => { try { loggerSvc.warn("Change detected on app-config, Kwirth will update config."); config.loadClusters(loggerSvc, configSvc); } catch (err) { loggerSvc.error(`Errors detected reading new configuration: ${err}`); } }); } else { loggerSvc.info("Kwirth 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 getValidClustersFromEntity = async (entity) => { let clusterList = []; for (const clusterName of KwirthStaticData.KwirthStaticData.clusterKwirthData.keys()) { let url = KwirthStaticData.KwirthStaticData.clusterKwirthData.get(clusterName)?.kwirthHome; let apiKeyStr = KwirthStaticData.KwirthStaticData.clusterKwirthData.get(clusterName)?.kwirthApiKey; let title = KwirthStaticData.KwirthStaticData.clusterKwirthData.get(clusterName)?.title; let clusterVersion = KwirthStaticData.KwirthStaticData.clusterKwirthData.get(clusterName)?.kwirthData.version || "0.0.0"; let queryUrl = void 0; if (entity.metadata.annotations["backstage.io/kubernetes-id"]) { queryUrl = url + `/managecluster/find?label=backstage.io%2fkubernetes-id&entity=${entity.metadata.annotations["backstage.io/kubernetes-id"]}&type=pod&data=containers`; } else if (entity.metadata.annotations["backstage.io/kubernetes-label-selector"]) { if (kwirthCommon.versionGreaterThan(clusterVersion, "0.4.40")) { let escapedLabelSelector = encodeURIComponent(entity.metadata.annotations["backstage.io/kubernetes-label-selector"]); queryUrl = url + `/managecluster/find?labelselector=${escapedLabelSelector}&type=pod&data=containers`; } else { loggerSvc.error(`Version ${clusterVersion} from cluster ${clusterName} is not valid for using ${pluginKwirthCommon.ANNOTATION_BACKSTAGE_KUBERNETES_LABELSELECTOR}`); clusterList.push({ name: clusterName, url, title, pods: [], accessKeys: /* @__PURE__ */ new Map() }); continue; } } else { loggerSvc.error("Received request without labelid/labelselector"); clusterList.push({ name: clusterName, url, title, pods: [], accessKeys: /* @__PURE__ */ new Map() }); continue; } try { let fetchResp = await fetch(queryUrl, { headers: { "Authorization": "Bearer " + apiKeyStr } }); if (fetchResp.status === 200) { let jsonResp = await fetchResp.json(); if (jsonResp) { let podData = { name: clusterName, url, title, pods: jsonResp, accessKeys: /* @__PURE__ */ new Map() }; clusterList.push(podData); } else { loggerSvc.warn(`Invalid data received from cluster ${clusterName}`); clusterList.push({ name: clusterName, url, title, pods: [], accessKeys: /* @__PURE__ */ new Map() }); } } else { loggerSvc.warn(`Invalid response from cluster ${clusterName}: ${fetchResp.status}`); let text = await fetchResp.text(); if (text) loggerSvc.warn(text); clusterList.push({ name: clusterName, url, title, pods: [], accessKeys: /* @__PURE__ */ new Map() }); } } catch (err) { loggerSvc.warn(`Cannot access cluster ${clusterName} (URL: ${queryUrl}): ${err}`); clusterList.push({ name: clusterName, url, title, pods: [], accessKeys: /* @__PURE__ */ new Map() }); } } return clusterList; }; const createAccessKey = async (reqScope, cluster, reqPods, userName) => { let resources = reqPods.map((podData) => `${reqScope}:${podData.namespace}::${podData.name}:`).join(";"); let kwirthHome = KwirthStaticData.KwirthStaticData.clusterKwirthData.get(cluster.name)?.kwirthHome; let kwirthApiKey = KwirthStaticData.KwirthStaticData.clusterKwirthData.get(cluster.name)?.kwirthApiKey; let payload = { description: `Backstage API key for user ${userName}`, expire: Date.now() + 60 * 60 * 1e3, days: 1, accessKey: { id: "", type: "bearer", resources } }; let fetchResp = await fetch(kwirthHome + "/key", { method: "POST", body: JSON.stringify(payload), headers: { "Content-Type": "application/json", Authorization: "Bearer " + kwirthApiKey } }); if (fetchResp.status === 200) { let data = await fetchResp.json(); return data.accessKey; } else { loggerSvc.warn(`Invalid response asking for a key from cluster ${cluster.name}: ${fetchResp.status}`); return void 0; } }; const addAccessKeys = async (channel, reqScope, foundClusters, entityName, userEntityRef, userGroups) => { if (!reqScope) { loggerSvc.info(`Invalid scope requested: ${reqScope}`); return; } let principal = userEntityRef.split(":")[1]; let username = principal.split("/")[1]; for (let foundCluster of foundClusters) { let podList = []; if (!KwirthStaticData.KwirthStaticData.clusterKwirthData.get(foundCluster.name)?.kwirthData.channels.some((c) => c.id === channel)) { loggerSvc.warn(`Cluster ${foundCluster.name} does not implement channel ${channel} (requested scope: ${reqScope})`); continue; } for (let podData of foundCluster.pods) { let allowedToNamespace = permissions.checkNamespaceAccess(channel, foundCluster, podData, userEntityRef, userGroups); if (allowedToNamespace) { let clusterDef = KwirthStaticData.KwirthStaticData.clusterKwirthData.get(foundCluster.name); let podPermissionSet = permissions.getPodPermissionSet(channel, clusterDef); if (!podPermissionSet) { loggerSvc.warn(`Pod permission set not found: ${channel}`); continue; } let namespaceRestricted = podPermissionSet.some((pp) => pp.namespace === podData.namespace); if (!namespaceRestricted || permissions.checkPodAccess(podData, podPermissionSet, entityName, userEntityRef, userGroups)) { podList.push(podData); } } } if (podList.length > 0) { let accessKey = await createAccessKey(reqScope, foundCluster, podList, username); if (accessKey) foundCluster.accessKeys.set(reqScope, accessKey); } else { loggerSvc.info(`No pods on podList for '${reqScope}' on channel '${channel}' in cluster '${foundCluster.name}' for searching for entity: '${entityName}'`); } } }; 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); let userGroupsRefs = []; if (entity?.spec.memberOf) userGroupsRefs = entity?.spec.memberOf; return userGroupsRefs; }; const processVersion = async (_req, res) => { res.status(200).send({ version: version.VERSION }); }; const processInfo = async (_req, res) => { res.status(200).send( KwirthStaticData.KwirthStaticData.latestVersions ); }; const processAccess = async (req, res) => { if (!req.query["scopes"] || !req.query["channel"]) { res.status(400).send(`'scopes' and 'channel' are required`); return; } let reqScopes = req.query["scopes"].toString().split(","); let reqChannel = req.query["channel"]?.toString(); const credentials = await httpAuthSvc.credentials(req, { allow: ["user"] }); const userInfo = await userInfoSvc.getUserInfo(credentials); let userGroupsRefs = await getUserGroups(userInfo); loggerSvc.info(`Checking reqScopes '${req.query["scopes"]}' scopes for working with pod: '${req.body.metadata.namespace + "/" + req.body.metadata.name}' for user '${userInfo.userEntityRef}'`); let foundClusters = await getValidClustersFromEntity(req.body); for (let reqScopeStr of reqScopes) { let reqScope = reqScopeStr; await addAccessKeys(reqChannel, reqScope, foundClusters, req.body.metadata.name, userInfo.userEntityRef, userGroupsRefs); if (reqScope === kwirthCommon.InstanceConfigScopeEnum.STREAM) { for (let cluster of foundClusters) { let accessKey = cluster.accessKeys.get(kwirthCommon.InstanceConfigScopeEnum.STREAM); if (accessKey) { let url = cluster.url + "/metrics"; let auth = "Bearer " + kwirthCommon.accessKeySerialize(accessKey); let fetchResp = await fetch(url, { headers: { "Authorization": auth } }); try { let data = await fetchResp.json(); cluster.metrics = data; } catch (err) { loggerSvc.error(`Cannot get metrics on cluster ${cluster.name}: ` + err); } } } } } for (let c of foundClusters) { c.accessKeys = JSON.stringify(Array.from(c.accessKeys.entries())); } res.status(200).send(foundClusters); }; router.post(["/access"], (req, res) => { processAccess(req, res); }); router.get(["/version"], (req, res) => { processVersion(req, res); }); router.get(["/info"], (req, res) => { processInfo(req, res); }); return router; } exports.createRouter = createRouter; //# sourceMappingURL=router.cjs.js.map