@jfvilas/plugin-kubelog-backend
Version:
Backstage backend plugin for Kubelog
204 lines (198 loc) • 8.81 kB
JavaScript
;
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