@jfvilas/plugin-kwirth-backend 0.12.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,279 @@
1
+ # Backstage backend Kwirth plugin
2
+ This Backstage plugin is the backend for several Kwirth plugins that we have developed for integrating live streaming Kubernetes information into Backstage by using Kwirth. It's important to understand that **Kwirth provides different kinds of information** (log, metrics, events...), and due to this way of working, the whole set of Backstage Kwirth plugins are comprised by:
3
+
4
+ - One only backend plugin (**this one**).
5
+ - Several frontend plugins, each one including its own features. Typically, there sould exist one Backstage Kwirth frontend plugin for each Kwirth supported channel (please refer to information on Kwirth channels here [Kwirth Channels](https://github.com/jfvilas/kwrith#channels)).
6
+
7
+ **NOTE: Backstage Kwirth plugins requires a Kwirth server running on Kubernetes whose version is at least 0.3.155**
8
+
9
+ This [Backstage]((https://backstage.io)) backend plugin is primarily responsible for the following tasks:
10
+
11
+ - Reading Kwirth config from your app-config YAML file.
12
+ - Validating login processes to remote Kwirth instances, and thus obtaining valid API keys for users to stream kubernetes data.
13
+ - Receiving and answering API calls from configured frontend Kwirth plugins on your Backstage instance.
14
+
15
+ ## Install
16
+
17
+ ### Up and Running
18
+ Here's how to get this backend plugin up and running quickly. First we need to add the `@jfvilas/plugin-kwirth-backend` package to your Backstage project:
19
+
20
+ ```sh
21
+ # From your Backstage root directory
22
+ yarn --cwd packages/backend add @jfvilas/plugin-kwirth-backend @jfvilas/plugin-kwirth-common
23
+ ```
24
+
25
+ ### New Backend System (we don't work with old backend system)
26
+ Next, you need to modify your backend index file for starting Kwirth backend plugin when your Backstage instance starts. In your `packages/backend/src/index.ts` make the following change:
27
+
28
+ ```diff
29
+ const backend = createBackend();
30
+
31
+ // ... other feature additions
32
+
33
+ + backend.add(import('@jfvilas/plugin-kwirth-backend'));
34
+
35
+ // ... other feature additions
36
+
37
+ backend.start();
38
+ ```
39
+
40
+ ## Configure
41
+ To have your Kwirth plugins ready for work you must perform some previous additional tasks, like deploying Kwirth, creating API Keys, defining clusters, etc... In this section we cover all these needs in a structured way.
42
+
43
+ Remember, Backstage Kwirth plugins helps you in showing live-streaming kubernetes data inside Backstage to ease your develoment teams work, but take into account that **this plugin has no access to the kubernetes itself**, it relies on a Kwirth deployment to act as a "live-streaming data proxy", that is, Kwirth (a component that runs inside your Kubernetes clusters) has access to kubernetes data and can "export" them outside the cluster in a reliable and secure way, so kubernetes data can be consumed anywhere. For example, logs can be shown on Backstage entity pages, kubernetes metrics can be charted on your Backstage, etc.
44
+
45
+ ### 1. Kwirth installation
46
+ We will not cover this subject here, we refer you to [Kwirth installation documentation](https://jfvilas.github.io/kwirth/#/installation) where you will find more information on how Kwirth works and how to install it. We show here just a summary of what is Kwirth:
47
+
48
+ 1. Kwirth is built around the **one-only-pod concept**.
49
+ 2. Kwirth doesn't need any persistenace layer (no database, no network storage, no block storage, no file storage). It uses only Kubernetes storage.
50
+ 3. Kwirth provides user management, API security and multi-cluster access.
51
+ 4. Kwirth can export **kubernets data in real-time** wherever you need it.
52
+
53
+ ### 2. Kwirth server customization
54
+ Once you have a Kubernetes cluster with a Kwirth installation in place (in order to export kuberntes data, Kwirth must be accesible from outside your cluster, so you will need to install any flavour of Ingress Controller and an Ingress for publishing Kwirth access). Please **write down your Kwirth external access** (we will need it for configuring Kwirth plugin). For simplifying reading of this tutorial we will assume your Kwirth is published on: **http://your-external.dns.name/kwirth**.
55
+
56
+ Once Kwirth is running, you need to enter Kwirth front application to perform two simple actions:
57
+ 1. Login to your Kwirth and access the [API Key section](https://jfvilas.github.io/kwirth/#/apimanagement?id=api-management) to create an API Key that we will use for giving our Backstage Kwirth plugin the chance to connect to your Kwirth server and access kubernetes data.
58
+ 2. The API Key should be 'permanent', the scope has to be 'cluster' and set the expire term long enough. When the API Key has been created, copy the API Key that Kwirth will create for you and is displayed at the API Key list.
59
+
60
+ This is all you need to do inside Kwirth. You can also do some play on Kwirth front application, it's very funny.
61
+
62
+ ### 3. Backstage configuration
63
+ For finishing this backend Kwirth plugin configuration you need to **edit your app-config.yaml** in order to add Kwirth information to your Kubernetes cluster. Kwirth plugin doesn't have a specific section in the app-config, it just **uses the Backstage Kubernetes core component configuration** vitamined with some additional properties. Let's suppose you have a Kubernetes configuration like this in your current app-config.yaml:
64
+
65
+ ```yaml
66
+ kubernetes:
67
+ serviceLocatorMethod:
68
+ type: 'multiTenant'
69
+ clusterLocatorMethods:
70
+ - type: 'config'
71
+ clusters:
72
+ - url: https://kuebeapi.your-cluster.com
73
+ name: k3d-cluster
74
+ title: 'Kubernetes local'
75
+ authProvider: 'serviceAccount'
76
+ skipTLSVerify: true
77
+ skipMetricsLookup: true
78
+ ```
79
+
80
+ We will add (at least) 2 properties to the cluster configuration:
81
+ - kwirthHome: the home URL of the Kwirth installation.
82
+ - kwirthApiKey: the API key we created before.
83
+
84
+ The kubernetes section should look now something like this:
85
+ ```diff
86
+ kubernetes:
87
+ serviceLocatorMethod:
88
+ type: 'multiTenant'
89
+ clusterLocatorMethods:
90
+ - type: 'config'
91
+ clusters:
92
+ - url: https://kuebeapi.your-cluster.com
93
+ name: k3d-cluster
94
+ title: 'Kubernetes local'
95
+ + kwirthHome: http://your-external.dns.name/kwirth
96
+ + kwirthApiKey: '40f5ea6c-bac3-df2f-d184-c9f3ab106ba9|permanent|cluster::::'
97
+ authProvider: 'serviceAccount'
98
+ skipTLSVerify: true
99
+ skipMetricsLookup: true
100
+ ```
101
+
102
+ ### 4. Channels
103
+ Kwirth live-streaming data system can export different kinds of data: log streaming, metrics streaming, alerts... In Kwirth, these different types of data are grouped in **channels**. In fact, each channel may be viewed as a data service (please refer here to learn how the channel system works [Kwirth channels](https://jfvilas.github.io/kwirth/#/channels)).
104
+
105
+ Each Kwirth channel is functionally mapped to a Kwirth frontend plugin, and thus, there exist a specific configuration for each channel. So:
106
+
107
+ - For the Kwirth metrics channel, you should use the 'plugin-kwirth-metrics' frontend plugin.
108
+ - For the Kwirth real-time log channel, you should use the 'plugin-kwirth-log' frontend plugin.
109
+ - ...
110
+
111
+ In your app-config.yaml configuration file you must configure each one of the channels. Let's show an example of 'log' and 'alert' channels:
112
+ ```yaml
113
+ kubernetes:
114
+ serviceLocatorMethod:
115
+ type: 'multiTenant'
116
+ clusterLocatorMethods:
117
+ - type: 'config'
118
+ clusters:
119
+ - url: https://kuebeapi.your-cluster.com
120
+ name: k3d-cluster
121
+ title: 'Kubernetes local'
122
+ kwirthHome: http://your-external.dns.name/kwirth
123
+ kwirthApiKey: '40f5ea6c-bac3-df2f-d184-c9f3ab106ba9|permanent|cluster::::'
124
+ authProvider: 'serviceAccount'
125
+ skipTLSVerify: true
126
+ skipMetricsLookup: true
127
+ kwirthlog:
128
+ namespacePermissions:
129
+ - kube-system: []
130
+ - ingress-nginx: []
131
+ podPermissions:
132
+ kwirthalert:
133
+ namespacePermissions:
134
+ podPermissions:
135
+ - pro:
136
+ allow:
137
+ - refs: []
138
+ ```
139
+
140
+ As you may see, channel configuration takes place inside your Backstage cluster configuration, because, as we said before, Kwirth plugin **uses the Backstage Kubernetes core component configuration**.
141
+
142
+ So, for adding 'log' and 'alert' channel we have created two sections: 'kwirthlog' and 'kwirthalert'. The content of channel sections is explained bellow, it is just the permission system.
143
+
144
+ ### 4. Permissions
145
+
146
+ #### Introduction to the permission system
147
+ The permission system of Kwirth plugin for Backstage has been designed with these ideas in mind:
148
+
149
+ - **Keep it simple**, that is, people that don't want to get complicated should be able to configure permissions without a headache, using just a few lines (in some cases even with 0 lines)
150
+ - It must be **flexible**. Because *every house is a world*, the system should be flexible enough to accomodate every single permission need, whatever its size be.
151
+
152
+ So, the permission system has been build using (right now) two layers:
153
+
154
+ 1. **Namespace layer**. Assigning permissions to whole namespaces can be done in a extremely simple way using this layer.
155
+ 2. **Pod layer**. If namespace permission layer is not coarse enough for you, you can refine your permissions by using the pod permission layer which. In addition, the pod layer allows adding scopes to the different permissions you can assign.
156
+
157
+
158
+ #### Namespace layer
159
+ Let's suppose that you have 3 namespaces in your cluster:
160
+ - **dev**, for development workloads.
161
+ - **stage**, for canary deployments, a/b testing and so on.
162
+ - **production**, for productive workloads.
163
+
164
+ Let's build a sample situation. Typically, you would restrict access to kubernetes information in such a way that:
165
+ - Everybody should be able to view developoment (dev) logs.
166
+ - Only Operations (devops) teams and Administrators can view stage logs.
167
+ - Only Administrators can see production logs. In addition to administrators, production can also be accessed by Nicklaus Wirth.
168
+
169
+ The way you can manage this in Kwirth plugin is **via Group entities** of Backstage. That is:
170
+ - You create a group where you can add all your developers.
171
+ - Another group with your devops team.
172
+ - And a group containing just the Administrators.
173
+
174
+ **NOTE**: for simplicity we assume all your User refs and Group refs live in a Backstage namespace named 'default'
175
+
176
+ Once you have created the groups you can configure the namespace permission adding one additional property to the cluster definition, it is named '**namespacePermissions**'. This is an array of namespaces, where for each namespace you can declare an array of identity refs (that is, users or groups). The example below is self-explaining (in this example we are **configuring the plugin for the 'log' channel**).
177
+
178
+ ```diff
179
+ clusters:
180
+ - url: https://kuebeapi.your-cluster.com
181
+ name: k3d-cluster
182
+ title: 'Kubernetes local'
183
+ kwirthHome: http://your-external.dns.name/kwirth
184
+ kwirthApiKey: '40f5ea6c-bac3-df2f-d184-c9f3ab106ba9|permanent|cluster::::'
185
+ kwrithlog:
186
+ + namespacePermissions:
187
+ + - stage: ['group:default/devops', 'group:default/admin']
188
+ + - production: ['group:default/admin', 'user:default/nicklaus-wirth']
189
+ authProvider: 'serviceAccount'
190
+ skipTLSVerify: true
191
+ skipMetricsLookup: true
192
+ ```
193
+
194
+ It's easy to understand:
195
+ 1. Everybody can access 'dev' namespace, since we have stated *no restrictions* at all (we added no 'dev' namespace in the namePermissions)
196
+ 2. 'stage' namespace can be accessed by group 'devops' and group 'admin'.
197
+ 3. The 'production' namespace can be accessed by the group of administrators ('admin' group) and the user Nicklaus Wirth ('nicklaus-wirth').
198
+
199
+ **Remember, if you don't want to restrict a namespace, just do not add it to the configuration in app-config file, like we have done with 'dev' namespace.**
200
+
201
+ When a user working with Backstage enters a Kwirth tab (log, metrics or whatever) in the entity page, he will see a list of clusters. When he selects a cluster, a list of namespaces will be shown, that is, all namespaces that do contain pods tagged with the current entity id. If the user has no permission to a specific namespace, the namespace will be shown in <span style='color:red'>red</span> and will not be accesible. Allowed namespaced will be shown in <span style='color:blue'>**primary color**</span> and will be 'clickable'.
202
+
203
+
204
+ #### Pod permissions
205
+ In addition to namespace permissions, Kwirth plugin has added a pod permission layer in which you can refine your permissions.
206
+
207
+ Let's consider a simple view-scoped pod permission sample based on previously defined namespaces: 'dev', 'stage', 'production':
208
+
209
+ ```diff
210
+ clusters:
211
+ - url: https://kuebeapi.your-cluster.com
212
+ name: k3d-cluster
213
+ title: 'Kubernetes local'
214
+ kwirthHome: http://your-external.dns.name/kwirth
215
+ kwirthApiKey: '40f5ea6c-bac3-df2f-d184-c9f3ab106ba9|permanent|cluster::::'
216
+ authProvider: 'serviceAccount'
217
+ skipTLSVerify: true
218
+ skipMetricsLookup: true
219
+ kwrithlog:
220
+ namespacePermissions:
221
+ - stage: ['group:default/devops', 'group:default/admin']
222
+ - production: ['group:default/admin', 'user:default/nicklaus-wirth']
223
+ + podPermissions:
224
+ + - stage:
225
+ + allow:
226
+ + - pods: [^common-]
227
+ + - pods: [keys]
228
+ + refs: []
229
+ + - pods: [^ef.*]
230
+ + refs: [group:.+/admin, group:test/.+]
231
+ + - pods: [th$]
232
+ + refs: [.*]
233
+ + except:
234
+ + - pods: [kwirth]
235
+ + refs: [group:default/admin, user:defualt:nicklaus-wirth]
236
+ + - production
237
+ + deny:
238
+ + - refs: [.*]
239
+ + - others
240
+ + allow:
241
+ + - refs: []
242
+ ...
243
+ ```
244
+
245
+ ***VERY IMPORTANT NOTE:*** **All strings defined in the pod permission layer are regular expressions.**
246
+
247
+ About this example and about 'how to configure Kwirth plugin pod permissions':
248
+
249
+ - **podPermissions** is the section name for refining pod permission (inside a specific channel like 'kwirthlog' or 'kwirthmetrics').
250
+ - The main content of this section is a list of namespaces (like 'stage' in the sample).
251
+ - The content of each namespace is a rule system that works this way:
252
+ - Rules can be defined following a fixed schema by which you can **allow** or **deny** access to a set of pods from a set of identity references (users or groups)
253
+ - 'allow' can be refined by adding exceptions by means of 'except' keyword.
254
+ - 'deny' can be refined by adding exceptions by means of 'unless' keyword.
255
+ - The way rules are evaluated is as follows:
256
+ 1. Search for a pod name match in the allow section.
257
+ 2. If a match is found, look for any exception that may be applied, by searching for matches in the 'except' section.
258
+ 3. If no 'allow' is found, or an allow rule is found but there exists an except rule that matches, the access is not granted and the process finishes here.
259
+ 4. If the user is granted, Kwirth plugin looks then for a match in the 'deny' section.
260
+ 5. If there are no deny rules that match, the user is granted and the process finsihes here.
261
+ 6. If a deny rule matches, then Kwirth plugin will search for any 'unless' rule that matches. If no 'unless' rule match exists, the access is denied and the process finishes here.
262
+ 7. If there exists an 'unless' rule then the access is granted.
263
+ - It is important to note that 'allow' and 'deny' are optional, but if you don't specify them, they will match anything.
264
+ - It is even most important to know that **if a namespace is not specified, the access is granted**.
265
+
266
+ So, in our example:
267
+ - Access to 'dev' is granted, since 'dev' namespace is not specified.
268
+ - Access to 'stage' works this way:
269
+ - *Everybody can access pods whose name starts with 'common-'* (remember, **we always use regexes**). We have added no 'refs', so any identity ref matches.
270
+ - *Nobody can access pod named 'keys'* (pay attention to the refs set to '[]', that means **no identity ref can access**)
271
+ - *Admins and people on namespace 'test' can access any pod whose name starts with 'ef'*. The 'pods' contains a regex with '^ef.*' (starts with 'ef' and contain any number of characters afterwards). The identity refs that can access pods that match with this pod regex are the group of admins on any Backstage namespace ('group:.+/admin') and all the people that belongs to Backstage group 'test' (group:test/.+).
272
+ - *Everybody can access pods whose name ends with 'th'*. That is, the regex in pods is 'th$' (names ending with 'th'), and the refs contains '.*', that is, any number of characters, so there are no limits on the refs, everybody is included (it is the same behaviour as not adding the 'refs', everybody can)
273
+ - *But... if the pod name is 'kwirth' only admis can access*. This refers to the 'except' section, which is a refinement of the allow. Although the previous rule says *everybody can access pods ending with 'th'*, this is true **except** for the pod name 'kwirth', which can only be accesed by 'admins in the default' group or 'Nicklaus Wirth'.
274
+
275
+ Let's complete the example with the other namespaces declared:
276
+ - *Nobody can access pods in 'production' namespace*. The 'production' namespace doesn't have an 'allow' section, it ony contains a 'deny'. In addition, the 'deny' section only contains a 'refs' section (all pod names would match, since no 'pods' section means 'pods: [.*]', that is, all pod names match). The 'refs' inside the 'deny' contains '.*', what means every ref would match, so, finally, *nobody can access a pod*.
277
+ - *Nobody can access pods in 'others' namespace*. The 'others' namespace contains just an 'allow' rule, which have no pods (so all pod names would match), and it contains in the 'refs' this expression: '[]', so no identity ref would match. Finally, *nobody can access a pod*, the same as 'production' but achieved in other way.
278
+
279
+ Please be aware that not declaring 'pods' or 'refs' means using a **match-all** approach (like using ['.*']), what is completely different than declaring '[]', what **matches nothing**.
@@ -0,0 +1,502 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var express = require('express');
6
+ var Router = require('express-promise-router');
7
+ var catalogClient = require('@backstage/catalog-client');
8
+ var kwirthCommon = require('@jfvilas/kwirth-common');
9
+ var backendPluginApi = require('@backstage/backend-plugin-api');
10
+
11
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
12
+
13
+ var express__default = /*#__PURE__*/_interopDefaultCompat(express);
14
+ var Router__default = /*#__PURE__*/_interopDefaultCompat(Router);
15
+
16
+ const VERSION = "0.0.1";
17
+ const MIN_KWIRTH_VERSION = "0.3.128";
18
+ class KwirthStaticData {
19
+ static clusterKwirthData = /* @__PURE__ */ new Map();
20
+ }
21
+
22
+ const loadNamespacePermissions = (block, logger) => {
23
+ var namespacePermissions = [];
24
+ if (block.has("namespacePermissions")) {
25
+ logger.info(` Namespace permisson evaluation will be performed.`);
26
+ var permNamespaces = block.getOptionalConfigArray("namespacePermissions");
27
+ for (var ns of permNamespaces) {
28
+ var namespace = ns.keys()[0];
29
+ var identityRefs = ns.getStringArray(namespace);
30
+ identityRefs = identityRefs.map((g) => g.toLowerCase());
31
+ namespacePermissions.push({ namespace, identityRefs });
32
+ }
33
+ } else {
34
+ logger.info(` No namespace restrictions.`);
35
+ namespacePermissions = [];
36
+ }
37
+ return namespacePermissions;
38
+ };
39
+ const loadPodRules = (config, category) => {
40
+ var rules = [];
41
+ for (var rule of config.getConfigArray(category)) {
42
+ var podsStringArray = rule.getOptionalStringArray("pods") || [".*"];
43
+ var podsRegexArray = [];
44
+ for (var expr of podsStringArray) {
45
+ podsRegexArray.push(new RegExp(expr));
46
+ }
47
+ var refsStringArray = rule.getOptionalStringArray("refs") || [".*"];
48
+ var refsRegexArray = [];
49
+ for (var expr of refsStringArray) {
50
+ refsRegexArray.push(new RegExp(expr));
51
+ }
52
+ var prr = {
53
+ pods: podsRegexArray,
54
+ refs: refsRegexArray
55
+ };
56
+ rules.push(prr);
57
+ }
58
+ return rules;
59
+ };
60
+ const loadPodPermissions = (block, logger) => {
61
+ var clusterPodPermissions = [];
62
+ if (block.has("podPermissions")) {
63
+ var namespaceList = block.getConfigArray("podPermissions");
64
+ for (var ns of namespaceList) {
65
+ var namespaceName = ns.keys()[0];
66
+ var podPermissions = { namespace: namespaceName };
67
+ if (ns.getConfig(namespaceName).has("allow")) {
68
+ podPermissions.allow = loadPodRules(ns.getConfig(namespaceName), "allow");
69
+ if (ns.getConfig(namespaceName).has("except")) podPermissions.except = loadPodRules(ns.getConfig(namespaceName), "except");
70
+ if (ns.getConfig(namespaceName).has("deny")) podPermissions.deny = loadPodRules(ns.getConfig(namespaceName), "deny");
71
+ if (ns.getConfig(namespaceName).has("unless")) podPermissions.unless = loadPodRules(ns.getConfig(namespaceName), "unless");
72
+ } else {
73
+ podPermissions.allow = [];
74
+ podPermissions.allow.push({
75
+ pods: [new RegExp(".*")],
76
+ refs: [new RegExp(".*")]
77
+ });
78
+ }
79
+ clusterPodPermissions.push(podPermissions);
80
+ }
81
+ } else {
82
+ logger.info(` No pod permissions will be applied.`);
83
+ }
84
+ return clusterPodPermissions;
85
+ };
86
+ const addChannelPermissions = (channel, logger, cluster, kdata) => {
87
+ var keyName = "kwirth" + channel;
88
+ if (cluster.has(keyName)) {
89
+ logger.info(`Load permissions for block ${channel}.`);
90
+ var configBlock = cluster.getConfig(keyName);
91
+ if (configBlock.has("namespacePermissions")) {
92
+ logger.info(` Loading namespace permissions.`);
93
+ kdata.namespacePermissions.set(channel, loadNamespacePermissions(configBlock, logger));
94
+ } else {
95
+ logger.info(` No namespace permissions.`);
96
+ kdata.namespacePermissions.set(channel, []);
97
+ }
98
+ if (configBlock.has("podPermissions")) {
99
+ logger.info(` Loading pod permissions.`);
100
+ kdata.podPermissions.set(channel, loadPodPermissions(configBlock, logger));
101
+ } else {
102
+ logger.info(` No pod permissions.`);
103
+ kdata.podPermissions.set(channel, []);
104
+ }
105
+ } else {
106
+ logger.info(`Cluster ${cluster.getString("name")} will have no channel '${channel}' restrictions.`);
107
+ kdata.namespacePermissions.set(channel, []);
108
+ kdata.podPermissions.set(channel, []);
109
+ }
110
+ };
111
+ const loadClusters = async (logger, config) => {
112
+ KwirthStaticData.clusterKwirthData.clear();
113
+ var locatingMethods = config.getConfigArray("kubernetes.clusterLocatorMethods");
114
+ for (var method of locatingMethods) {
115
+ var clusters = method.getConfigArray("clusters");
116
+ for (var cluster of clusters) {
117
+ var name = cluster.getString("name");
118
+ if (cluster.has("kwirthHome") && cluster.has("kwirthApiKey")) {
119
+ var kwirthHome = cluster.getOptionalString("kwirthHome");
120
+ var kwirthApiKey = cluster.getOptionalString("kwirthApiKey");
121
+ var title = cluster.has("title") ? cluster.getString("title") : "No name";
122
+ var kwirthClusterData = {
123
+ name,
124
+ kwirthHome,
125
+ kwirthApiKey,
126
+ kwirthData: {
127
+ version: "",
128
+ clusterName: "",
129
+ inCluster: false,
130
+ namespace: "",
131
+ deployment: "",
132
+ lastVersion: ""
133
+ },
134
+ title,
135
+ namespacePermissions: /* @__PURE__ */ new Map(),
136
+ podPermissions: /* @__PURE__ */ new Map(),
137
+ enabled: false
138
+ };
139
+ logger.info(`Kwirth for ${name} is located at ${kwirthClusterData.kwirthHome}. Testing connection...`);
140
+ let enableCluster = false;
141
+ try {
142
+ var response = await fetch(kwirthClusterData.kwirthHome + "/config/version");
143
+ try {
144
+ var data = await response.text();
145
+ try {
146
+ var kwirthData = JSON.parse(data);
147
+ logger.info(`Kwirth info at cluster '${kwirthClusterData.name}': ${JSON.stringify(kwirthData)}`);
148
+ kwirthClusterData.kwirthData = kwirthData;
149
+ if (kwirthCommon.versionGreatOrEqualThan(kwirthData.version, MIN_KWIRTH_VERSION)) {
150
+ enableCluster = true;
151
+ } else {
152
+ logger.error(`Unsupported Kwirth version on cluster '${name}' (${title}) [${kwirthData.version}]. Min version is ${MIN_KWIRTH_VERSION}`);
153
+ }
154
+ } catch (err) {
155
+ logger.error(`Kwirth at cluster ${kwirthClusterData.name} returned errors: ${err}`);
156
+ logger.info("Returned data is:");
157
+ logger.info(data);
158
+ kwirthClusterData.kwirthData = {
159
+ version: "0.0.0",
160
+ clusterName: "unknown",
161
+ inCluster: false,
162
+ namespace: "unknown",
163
+ deployment: "unknown",
164
+ lastVersion: "0.0.0"
165
+ };
166
+ }
167
+ } catch (err) {
168
+ logger.warn(`Error parsing version response from cluster '${kwirthClusterData.name}': ${err}`);
169
+ }
170
+ } catch (err) {
171
+ logger.info(`Kwirth access error: ${err}.`);
172
+ logger.warn(`Kwirth home URL (${kwirthClusterData.kwirthHome}) at cluster '${kwirthClusterData.name}' cannot be accessed right now.`);
173
+ }
174
+ if (enableCluster) {
175
+ addChannelPermissions("log", logger, cluster, kwirthClusterData);
176
+ addChannelPermissions("alert", logger, cluster, kwirthClusterData);
177
+ addChannelPermissions("metrics", logger, cluster, kwirthClusterData);
178
+ KwirthStaticData.clusterKwirthData.set(name, kwirthClusterData);
179
+ } else {
180
+ logger.warn(`Cluster ${name} will be disabled`);
181
+ }
182
+ } else {
183
+ logger.warn(`Cluster ${name} has no Kwirth information (kwirthHome and kwirthApiKey are missing).`);
184
+ }
185
+ }
186
+ }
187
+ logger.info("Kwirthbackstage static data has been set including following clusters:");
188
+ for (var c of KwirthStaticData.clusterKwirthData.keys()) {
189
+ logger.info(" " + c);
190
+ }
191
+ for (var c of KwirthStaticData.clusterKwirthData.keys()) {
192
+ console.log(KwirthStaticData.clusterKwirthData.get(c));
193
+ }
194
+ };
195
+
196
+ const checkNamespaceAccess = (channel, cluster, podData, userEntityRef, userGroups) => {
197
+ let allowedToNamespace = false;
198
+ let namespacePermissions = KwirthStaticData.clusterKwirthData.get(cluster.name)?.namespacePermissions;
199
+ if (namespacePermissions?.has(channel)) {
200
+ let rule = namespacePermissions?.get(channel).find((ns) => ns.namespace === podData.namespace);
201
+ if (rule) {
202
+ if (rule.identityRefs.includes(userEntityRef.toLowerCase())) {
203
+ allowedToNamespace = true;
204
+ } else {
205
+ var groupResult = rule.identityRefs.some((identityRef) => userGroups.includes(identityRef));
206
+ if (groupResult) {
207
+ allowedToNamespace = true;
208
+ }
209
+ }
210
+ } else {
211
+ allowedToNamespace = true;
212
+ }
213
+ } else {
214
+ console.log(`Invalid channel: ${channel}`);
215
+ }
216
+ return allowedToNamespace;
217
+ };
218
+ const checkPodPermissionRule = (ppr, entityName, userEntityRef, userGroups) => {
219
+ var refMatch = false;
220
+ for (var podNameRegex of ppr.pods) {
221
+ if (podNameRegex.test(entityName)) {
222
+ for (var refRegex of ppr.refs) {
223
+ refMatch = refRegex.test(userEntityRef.toLowerCase());
224
+ if (refMatch) {
225
+ break;
226
+ } else {
227
+ refMatch = userGroups.some((g) => refRegex.test(g));
228
+ if (refMatch) {
229
+ break;
230
+ }
231
+ }
232
+ }
233
+ }
234
+ if (refMatch) break;
235
+ }
236
+ return refMatch;
237
+ };
238
+ const getPodPermissionSet = (channel, cluster) => {
239
+ if (cluster.podPermissions.has(channel)) {
240
+ return cluster.podPermissions.get(channel);
241
+ } else {
242
+ console.log(`Invalid channel ${channel} for permission set`);
243
+ return void 0;
244
+ }
245
+ };
246
+ const checkPodAccess = (reqPod, podPermissionSet, entityName, userEntityRef, userGroups) => {
247
+ for (var podPermission of podPermissionSet.filter((pp) => pp.namespace === reqPod.namespace)) {
248
+ if (podPermission.allow) {
249
+ var allowMatches = false;
250
+ var exceptMatches = false;
251
+ for (var prr of podPermission.allow) {
252
+ allowMatches = checkPodPermissionRule(prr, entityName, userEntityRef, userGroups);
253
+ }
254
+ if (allowMatches) {
255
+ if (podPermission.except) {
256
+ for (var prr of podPermission.except) {
257
+ exceptMatches = checkPodPermissionRule(prr, entityName, userEntityRef, userGroups);
258
+ if (exceptMatches) {
259
+ break;
260
+ }
261
+ }
262
+ }
263
+ }
264
+ if (allowMatches && !exceptMatches) {
265
+ if (podPermission.deny) {
266
+ var denyMatches = false;
267
+ var unlessMatches = false;
268
+ for (var prr of podPermission.deny) {
269
+ denyMatches = checkPodPermissionRule(prr, entityName, userEntityRef, userGroups);
270
+ if (denyMatches) {
271
+ break;
272
+ }
273
+ }
274
+ if (denyMatches && podPermission.unless) {
275
+ for (var prr of podPermission.unless) {
276
+ unlessMatches = checkPodPermissionRule(prr, entityName, userEntityRef, userGroups);
277
+ if (unlessMatches) {
278
+ break;
279
+ }
280
+ }
281
+ }
282
+ if (!denyMatches || denyMatches && unlessMatches) {
283
+ return true;
284
+ }
285
+ } else {
286
+ return true;
287
+ }
288
+ }
289
+ } else {
290
+ return true;
291
+ }
292
+ }
293
+ return false;
294
+ };
295
+
296
+ async function createRouter(options) {
297
+ const { configSvc, loggerSvc, userInfoSvc, authSvc, httpAuthSvc, discoverySvc } = options;
298
+ loggerSvc.info("Loading static config");
299
+ if (!configSvc.has("kubernetes.clusterLocatorMethods")) {
300
+ loggerSvc.error(`Kwirthbackstage will not start, there is no 'clusterLocatorMethods' defined in app-config.`);
301
+ throw new Error("Kwirthbackstage backend will not be available.");
302
+ }
303
+ try {
304
+ loadClusters(loggerSvc, configSvc);
305
+ } catch (err) {
306
+ var txt = `Errors detected reading static configuration: ${err}`;
307
+ loggerSvc.error(txt);
308
+ throw new Error(txt);
309
+ }
310
+ if (configSvc.subscribe) {
311
+ configSvc.subscribe(() => {
312
+ try {
313
+ loggerSvc.warn("Change detected on app-config, Kwirthbackstage will update config.");
314
+ loadClusters(loggerSvc, configSvc);
315
+ } catch (err) {
316
+ loggerSvc.error(`Errors detected reading new configuration: ${err}`);
317
+ }
318
+ });
319
+ } else {
320
+ loggerSvc.info("Kwirthbackstage cannot subscribe to config changes.");
321
+ }
322
+ const router = Router__default.default();
323
+ router.use(express__default.default.json());
324
+ const createAuthFetchApi = (token) => {
325
+ return {
326
+ fetch: async (input, init) => {
327
+ init = init || {};
328
+ init.headers = {
329
+ ...init.headers,
330
+ Authorization: `Bearer ${token}`
331
+ };
332
+ return fetch(input, init);
333
+ }
334
+ };
335
+ };
336
+ const getValidClusters = async (entityName) => {
337
+ var clusterList = [];
338
+ for (const clusterName of KwirthStaticData.clusterKwirthData.keys()) {
339
+ var url = KwirthStaticData.clusterKwirthData.get(clusterName)?.kwirthHome;
340
+ var apiKeyStr = KwirthStaticData.clusterKwirthData.get(clusterName)?.kwirthApiKey;
341
+ var title = KwirthStaticData.clusterKwirthData.get(clusterName)?.title;
342
+ var queryUrl = url + `/managecluster/find?label=backstage.io%2fkubernetes-id&entity=${entityName}&type=pod&data=containers`;
343
+ try {
344
+ var fetchResp = await fetch(queryUrl, { headers: { "Authorization": "Bearer " + apiKeyStr } });
345
+ if (fetchResp.status === 200) {
346
+ var jsonResp = await fetchResp.json();
347
+ if (jsonResp) {
348
+ let podData = {
349
+ name: clusterName,
350
+ url,
351
+ title,
352
+ data: jsonResp,
353
+ accessKeys: /* @__PURE__ */ new Map()
354
+ };
355
+ clusterList.push(podData);
356
+ }
357
+ } else {
358
+ loggerSvc.warn(`Invalid response from cluster ${clusterName}: ${fetchResp.status}`);
359
+ console.log(await fetchResp.text());
360
+ clusterList.push({ name: clusterName, url, title, data: [], accessKeys: /* @__PURE__ */ new Map() });
361
+ }
362
+ } catch (err) {
363
+ loggerSvc.warn(`Cannot access cluster ${clusterName} (URL: ${queryUrl}): ${err}`);
364
+ clusterList.push({ name: clusterName, url, title, data: [], accessKeys: /* @__PURE__ */ new Map() });
365
+ }
366
+ }
367
+ return clusterList;
368
+ };
369
+ const createAccessKey = async (reqScope, cluster, reqPods, userName) => {
370
+ var resources = reqPods.map((podData) => `${reqScope}:${podData.namespace}::${podData.name}:`).join(",");
371
+ var kwirthHome = KwirthStaticData.clusterKwirthData.get(cluster.name)?.kwirthHome;
372
+ var kwirthApiKey = KwirthStaticData.clusterKwirthData.get(cluster.name)?.kwirthApiKey;
373
+ var payload = {
374
+ type: "bearer",
375
+ resource: resources,
376
+ description: `Backstage API key for user ${userName}`,
377
+ expire: Date.now() + 60 * 60 * 1e3
378
+ };
379
+ var fetchResp = await fetch(kwirthHome + "/key", { method: "POST", body: JSON.stringify(payload), headers: { "Content-Type": "application/json", Authorization: "Bearer " + kwirthApiKey } });
380
+ if (fetchResp.status === 200) {
381
+ var data = await fetchResp.json();
382
+ console.log("data");
383
+ console.log(data);
384
+ return data.accessKey;
385
+ } else {
386
+ loggerSvc.warn(`Invalid response asking for a key from cluster ${cluster.name}: ${fetchResp.status}`);
387
+ return {};
388
+ }
389
+ };
390
+ const addAccessKeys = async (channel, reqScope, foundClusters, entityName, userEntityRef, userGroups) => {
391
+ if (!reqScope) {
392
+ loggerSvc.info(`Invalid scope requested: ${reqScope}`);
393
+ return;
394
+ }
395
+ var principal = userEntityRef.split(":")[1];
396
+ var username = principal.split("/")[1];
397
+ for (var foundCluster of foundClusters) {
398
+ var podList = [];
399
+ for (var podData of foundCluster.data) {
400
+ var allowedToNamespace = checkNamespaceAccess(channel, foundCluster, podData, userEntityRef, userGroups);
401
+ if (allowedToNamespace) {
402
+ var clusterDef = KwirthStaticData.clusterKwirthData.get(foundCluster.name);
403
+ var podPermissionSet = getPodPermissionSet(channel, clusterDef);
404
+ if (!podPermissionSet) {
405
+ loggerSvc.warn(`Pod permission set not found: ${channel}`);
406
+ continue;
407
+ }
408
+ var namespaceRestricted = podPermissionSet.some((pp) => pp.namespace === podData.namespace);
409
+ if (!namespaceRestricted || checkPodAccess(podData, podPermissionSet, entityName, userEntityRef, userGroups)) {
410
+ podList.push(podData);
411
+ }
412
+ }
413
+ }
414
+ if (podList.length > 0) {
415
+ let accessKey = await createAccessKey(reqScope, foundCluster, podList, username);
416
+ console.log("accessKey");
417
+ console.log(accessKey);
418
+ foundCluster.accessKeys.set(reqScope, accessKey);
419
+ } else {
420
+ console.log(`No pods on podList for ${channel} and ${reqScope}`);
421
+ }
422
+ }
423
+ };
424
+ const getUserGroups = async (userInfo) => {
425
+ const { token } = await authSvc.getPluginRequestToken({
426
+ onBehalfOf: await authSvc.getOwnServiceCredentials(),
427
+ targetPluginId: "catalog"
428
+ });
429
+ const catalogClient$1 = new catalogClient.CatalogClient({
430
+ discoveryApi: discoverySvc,
431
+ fetchApi: createAuthFetchApi(token)
432
+ });
433
+ const entity = await catalogClient$1.getEntityByRef(userInfo.userEntityRef);
434
+ var userGroupsRefs = [];
435
+ if (entity?.spec.memberOf) userGroupsRefs = entity?.spec.memberOf;
436
+ return userGroupsRefs;
437
+ };
438
+ const processVersion = async (_req, res) => {
439
+ res.status(200).send({ version: VERSION });
440
+ };
441
+ const processAccess = async (req, res) => {
442
+ if (!req.query["scopes"] || !req.query["scopes"]) {
443
+ res.status(400).send(`'scopes' and 'channel' are required`);
444
+ return;
445
+ }
446
+ let reqScopes = req.query["scopes"].toString().split(",");
447
+ let reqChannel = req.query["channel"]?.toString();
448
+ const credentials = await httpAuthSvc.credentials(req, { allow: ["user"] });
449
+ const userInfo = await userInfoSvc.getUserInfo(credentials);
450
+ let userGroupsRefs = await getUserGroups(userInfo);
451
+ loggerSvc.info(`Checking reqScopes '${req.query["scopes"]}' scopes to pod: '${req.body.metadata.namespace + "/" + req.body.metadata.name}' for user '${userInfo.userEntityRef}'`);
452
+ let foundClusters = await getValidClusters(req.body.metadata.name);
453
+ for (var reqScopeStr of reqScopes) {
454
+ var reqScope = reqScopeStr;
455
+ await addAccessKeys(reqChannel, reqScope, foundClusters, req.body.metadata.name, userInfo.userEntityRef, userGroupsRefs);
456
+ }
457
+ for (var c of foundClusters) {
458
+ c.accessKeys = JSON.stringify(Array.from(c.accessKeys.entries()));
459
+ }
460
+ res.status(200).send(foundClusters);
461
+ };
462
+ router.post(["/access"], (req, res) => {
463
+ processAccess(req, res);
464
+ });
465
+ router.get(["/version"], (req, res) => {
466
+ processVersion(req, res);
467
+ });
468
+ return router;
469
+ }
470
+
471
+ const kwirthPlugin = backendPluginApi.createBackendPlugin({
472
+ pluginId: "kwirthbackstage",
473
+ register(env) {
474
+ env.registerInit({
475
+ deps: {
476
+ discovery: backendPluginApi.coreServices.discovery,
477
+ config: backendPluginApi.coreServices.rootConfig,
478
+ logger: backendPluginApi.coreServices.logger,
479
+ auth: backendPluginApi.coreServices.auth,
480
+ httpAuth: backendPluginApi.coreServices.httpAuth,
481
+ httpRouter: backendPluginApi.coreServices.httpRouter,
482
+ userInfo: backendPluginApi.coreServices.userInfo
483
+ },
484
+ async init({ discovery, config, httpRouter, logger, auth, httpAuth, userInfo }) {
485
+ httpRouter.use(
486
+ await createRouter({
487
+ discoverySvc: discovery,
488
+ configSvc: config,
489
+ loggerSvc: logger,
490
+ authSvc: auth,
491
+ httpAuthSvc: httpAuth,
492
+ userInfoSvc: userInfo
493
+ })
494
+ );
495
+ }
496
+ });
497
+ }
498
+ });
499
+
500
+ exports.createRouter = createRouter;
501
+ exports.default = kwirthPlugin;
502
+ //# sourceMappingURL=index.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs.js","sources":["../src/model/KwirthStaticData.ts","../src/service/config.ts","../src/service/permissions.ts","../src/service/router.ts","../src/plugin.ts"],"sourcesContent":["import { KwirthClusterData } from './KwirthClusterData'\r\n\r\n/*\r\nCopyright 2024 Julio Fernandez\r\n\r\nLicensed under the Apache License, Version 2.0 (the \"License\");\r\nyou may not use this file except in compliance with the License.\r\nYou may obtain a copy of the License at\r\n\r\n http://www.apache.org/licenses/LICENSE-2.0\r\n\r\nUnless required by applicable law or agreed to in writing, software\r\ndistributed under the License is distributed on an \"AS IS\" BASIS,\r\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\nSee the License for the specific language governing permissions and\r\nlimitations under the License.\r\n*/\r\nconst VERSION='0.0.1'\r\nconst MIN_KWIRTH_VERSION='0.3.128'\r\n\r\nclass KwirthStaticData {\r\n public static clusterKwirthData : Map<string,KwirthClusterData> = new Map()\r\n}\r\n\r\nexport { KwirthStaticData, VERSION, MIN_KWIRTH_VERSION }","/*\r\nCopyright 2024 Julio Fernandez\r\n\r\nLicensed under the Apache License, Version 2.0 (the \"License\")\r\nyou may not use this file except in compliance with the License.\r\nYou may obtain a copy of the License at\r\n\r\n http://www.apache.org/licenses/LICENSE-2.0\r\n\r\nUnless required by applicable law or agreed to in writing, software\r\ndistributed under the License is distributed on an \"AS IS\" BASIS,\r\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\nSee the License for the specific language governing permissions and\r\nlimitations under the License.\r\n*/\r\nimport { LoggerService, RootConfigService } from '@backstage/backend-plugin-api'\r\nimport { KwirthStaticData, MIN_KWIRTH_VERSION } from '../model/KwirthStaticData'\r\nimport { KwirthClusterData, KwirthNamespacePermissions, KwirthPodPermissions, PodPermissionRule } from '../model/KwirthClusterData'\r\nimport { Config } from '@backstage/config'\r\nimport { KwirthData, versionGreatOrEqualThan } from '@jfvilas/kwirth-common'\r\n\r\n\r\n/**\r\n * loads Namespace Permissions setting from app-config xml\r\n * @param block name of the Kwirthbackstage block te read ('chart', 'log', 'audit'...)\r\n * @param logger Logger service\r\n */\r\nconst loadNamespacePermissions = (block:Config, logger:LoggerService):KwirthNamespacePermissions[] => {\r\n var namespacePermissions:KwirthNamespacePermissions[] = []\r\n if (block.has('namespacePermissions')) {\r\n logger.info(` Namespace permisson evaluation will be performed.`)\r\n var permNamespaces= (block.getOptionalConfigArray('namespacePermissions'))!\r\n for (var ns of permNamespaces) {\r\n var namespace=ns.keys()[0]\r\n var identityRefs=ns.getStringArray(namespace)\r\n identityRefs=identityRefs.map(g => g.toLowerCase())\r\n namespacePermissions.push ({ namespace, identityRefs })\r\n }\r\n }\r\n else {\r\n logger.info(` No namespace restrictions.`)\r\n namespacePermissions=[]\r\n }\r\n return namespacePermissions\r\n}\r\n\r\n/**\r\n * read rules about permissions to a pply to a set of pods\r\n * @param config an object config read from app-config\r\n * @param category a permission category, one of: allow, deny unless, except\r\n * @returns an array of PodPermissionRule's\r\n */\r\nconst loadPodRules = (config:Config, category:string):PodPermissionRule[] => {\r\n var rules:PodPermissionRule[]=[]\r\n for (var rule of config.getConfigArray(category)) {\r\n var podsStringArray = rule.getOptionalStringArray('pods') || ['.*']\r\n var podsRegexArray:RegExp[]=[]\r\n for (var expr of podsStringArray) {\r\n podsRegexArray.push(new RegExp(expr))\r\n }\r\n\r\n var refsStringArray = rule.getOptionalStringArray('refs') || ['.*']\r\n var refsRegexArray:RegExp[]=[]\r\n for (var expr of refsStringArray) {\r\n refsRegexArray.push(new RegExp(expr))\r\n }\r\n\r\n var prr:PodPermissionRule={\r\n pods:podsRegexArray,\r\n refs:refsRegexArray\r\n }\r\n rules.push(prr)\r\n }\r\n return rules\r\n}\r\n\r\n/**\r\n * loads pod permissions (namespace and pod) for a specific Kwirthbackstage block\r\n * @param block then name of the key (inside app-config) to read config from\r\n * @param logger Logger service\r\n * @returns an array of pod permissions\r\n */\r\nconst loadPodPermissions = (block:Config, logger:LoggerService):KwirthPodPermissions[] => {\r\n var clusterPodPermissions:KwirthPodPermissions[]=[]\r\n if (block.has('podPermissions')) {\r\n var namespaceList=block.getConfigArray('podPermissions')\r\n for (var ns of namespaceList) {\r\n var namespaceName=ns.keys()[0]\r\n var podPermissions:KwirthPodPermissions={ namespace:namespaceName }\r\n\r\n if (ns.getConfig(namespaceName).has('allow')) {\r\n podPermissions.allow=loadPodRules(ns.getConfig(namespaceName), 'allow')\r\n if (ns.getConfig(namespaceName).has('except')) podPermissions.except=loadPodRules(ns.getConfig(namespaceName), 'except')\r\n if (ns.getConfig(namespaceName).has('deny')) podPermissions.deny=loadPodRules(ns.getConfig(namespaceName), 'deny')\r\n if (ns.getConfig(namespaceName).has('unless')) podPermissions.unless=loadPodRules(ns.getConfig(namespaceName), 'unless')\r\n }\r\n else {\r\n podPermissions.allow=[]\r\n podPermissions.allow.push({\r\n pods: [new RegExp('.*')],\r\n refs: [new RegExp('.*')]\r\n })\r\n }\r\n clusterPodPermissions.push(podPermissions)\r\n }\r\n }\r\n else {\r\n logger.info(` No pod permissions will be applied.`)\r\n }\r\n return clusterPodPermissions\r\n}\r\n\r\n/**\r\n * Reads permissions for a kwirthbackstage block (like log, metrics...)\r\n * @param channel name of the block\r\n * @param logger bs logger service\r\n * @param cluster the app-config object containing the cluster to process\r\n * @param kdata current KwirthbackstageClusterData object to add block permissions\r\n */\r\nconst addChannelPermissions = (channel: string, logger:LoggerService, cluster:Config, kdata:KwirthClusterData) => {\r\n var keyName = 'kwirth'+channel\r\n if (cluster.has(keyName)) {\r\n logger.info(`Load permissions for block ${channel}.`)\r\n var configBlock=cluster.getConfig(keyName);\r\n if (configBlock.has('namespacePermissions')) {\r\n logger.info(` Loading namespace permissions.`)\r\n kdata.namespacePermissions.set(channel, loadNamespacePermissions(configBlock, logger))\r\n }\r\n else {\r\n logger.info(` No namespace permissions.`)\r\n kdata.namespacePermissions.set(channel, [])\r\n }\r\n if (configBlock.has('podPermissions')) {\r\n logger.info(` Loading pod permissions.`)\r\n kdata.podPermissions.set(channel, loadPodPermissions(configBlock, logger))\r\n }\r\n else {\r\n logger.info(` No pod permissions.`)\r\n kdata.podPermissions.set(channel, [])\r\n }\r\n }\r\n else {\r\n logger.info(`Cluster ${cluster.getString('name')} will have no channel '${channel}' restrictions.`)\r\n kdata.namespacePermissions.set(channel,[])\r\n kdata.podPermissions.set(channel,[])\r\n }\r\n}\r\n\r\n/**\r\n * reads app-config and builds a list of valid clusters\r\n * @param logger core service for logging\r\n * @param config core service for reading config info\r\n */\r\nconst loadClusters = async (logger:LoggerService, config:RootConfigService) => {\r\n KwirthStaticData.clusterKwirthData.clear()\r\n\r\n var locatingMethods=config.getConfigArray('kubernetes.clusterLocatorMethods')\r\n for (var method of locatingMethods) {\r\n\r\n var clusters=(method.getConfigArray('clusters'))\r\n for (var cluster of clusters) {\r\n\r\n var name=cluster.getString('name')\r\n if (cluster.has('kwirthHome') && cluster.has('kwirthApiKey')) { \r\n var kwirthHome:string = cluster.getOptionalString('kwirthHome')!\r\n var kwirthApiKey:string = cluster.getOptionalString('kwirthApiKey')!\r\n var title:string = (cluster.has('title')?cluster.getString('title'):'No name')\r\n var kwirthClusterData:KwirthClusterData={\r\n name,\r\n kwirthHome,\r\n kwirthApiKey,\r\n kwirthData: {\r\n version: '',\r\n clusterName: '',\r\n inCluster: false,\r\n namespace: '',\r\n deployment: '',\r\n lastVersion: ''\r\n },\r\n title,\r\n namespacePermissions: new Map(),\r\n podPermissions: new Map(),\r\n enabled: false\r\n }\r\n\r\n logger.info(`Kwirth for ${name} is located at ${kwirthClusterData.kwirthHome}. Testing connection...`)\r\n let enableCluster = false\r\n try {\r\n /*\r\n /config/version endpoint returns JSON (KwirthData object):\r\n {\r\n \"clusterName\": \"inCluster\",\r\n \"namespace\": \"default\",\r\n \"deployment\": \"kwirth\",\r\n \"inCluster\": true,\r\n \"version\": \"0.2.213\",\r\n \"lastVersion\": \"0.2.213\"\r\n }\r\n */\r\n var response = await fetch (kwirthClusterData.kwirthHome+'/config/version')\r\n try {\r\n var data = await response.text()\r\n try {\r\n var kwirthData=JSON.parse(data) as KwirthData\r\n logger.info(`Kwirth info at cluster '${kwirthClusterData.name}': ${JSON.stringify(kwirthData)}`)\r\n kwirthClusterData.kwirthData=kwirthData\r\n if (versionGreatOrEqualThan(kwirthData.version, MIN_KWIRTH_VERSION)) {\r\n enableCluster = true\r\n }\r\n else {\r\n logger.error(`Unsupported Kwirth version on cluster '${name}' (${title}) [${kwirthData.version}]. Min version is ${MIN_KWIRTH_VERSION}`)\r\n }\r\n }\r\n catch (err) {\r\n logger.error(`Kwirth at cluster ${kwirthClusterData.name} returned errors: ${err}`)\r\n logger.info('Returned data is:')\r\n logger.info(data)\r\n kwirthClusterData.kwirthData = {\r\n version:'0.0.0',\r\n clusterName:'unknown',\r\n inCluster:false,\r\n namespace:'unknown',\r\n deployment:'unknown',\r\n lastVersion:'0.0.0'\r\n }\r\n }\r\n }\r\n catch (err) {\r\n logger.warn(`Error parsing version response from cluster '${kwirthClusterData.name}': ${err}`)\r\n }\r\n }\r\n catch (err) {\r\n logger.info(`Kwirth access error: ${err}.`)\r\n logger.warn(`Kwirth home URL (${kwirthClusterData.kwirthHome}) at cluster '${kwirthClusterData.name}' cannot be accessed right now.`)\r\n }\r\n\r\n if (enableCluster) {\r\n addChannelPermissions('log',logger, cluster, kwirthClusterData)\r\n addChannelPermissions('alert',logger, cluster, kwirthClusterData)\r\n addChannelPermissions('metrics',logger, cluster, kwirthClusterData)\r\n KwirthStaticData.clusterKwirthData.set(name, kwirthClusterData)\r\n }\r\n else {\r\n logger.warn(`Cluster ${name} will be disabled`)\r\n }\r\n }\r\n else {\r\n logger.warn(`Cluster ${name} has no Kwirth information (kwirthHome and kwirthApiKey are missing).`)\r\n }\r\n }\r\n }\r\n\r\n logger.info('Kwirthbackstage static data has been set including following clusters:')\r\n for (var c of KwirthStaticData.clusterKwirthData.keys()) {\r\n logger.info (' '+c)\r\n }\r\n for (var c of KwirthStaticData.clusterKwirthData.keys()) {\r\n console.log(KwirthStaticData.clusterKwirthData.get(c))\r\n }\r\n\r\n}\r\n\r\nexport { loadClusters }","/*\r\nCopyright 2024 Julio Fernandez\r\n\r\nLicensed under the Apache License, Version 2.0 (the \"License\");\r\nyou may not use this file except in compliance with the License.\r\nYou may obtain a copy of the License at\r\n\r\n http://www.apache.org/licenses/LICENSE-2.0\r\n\r\nUnless required by applicable law or agreed to in writing, software\r\ndistributed under the License is distributed on an \"AS IS\" BASIS,\r\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\nSee the License for the specific language governing permissions and\r\nlimitations under the License.\r\n*/\r\nimport { ClusterValidPods, PodData } from '@jfvilas/plugin-kwirth-common'\r\nimport { KwirthClusterData, KwirthPodPermissions, PodPermissionRule } from '../model/KwirthClusterData'\r\nimport { KwirthStaticData } from '../model/KwirthStaticData';\r\n//import { InstanceConfigScopeEnum } from '@jfvilas/kwirth-common'\r\n\r\n// const debug = (a:any) => {\r\n// if (process.env.KWIRTHDEBUG) console.log(a)\r\n// }\r\n\r\nconst checkNamespaceAccess = (channel:string, cluster:ClusterValidPods, podData:PodData, userEntityRef:string, userGroups:string[]) : boolean => {\r\n let allowedToNamespace=false\r\n let namespacePermissions = KwirthStaticData.clusterKwirthData.get(cluster.name)?.namespacePermissions\r\n\r\n if (namespacePermissions?.has(channel)) {\r\n let rule = namespacePermissions?.get(channel)!.find(ns => ns.namespace===podData.namespace)\r\n if (rule) {\r\n if (rule.identityRefs.includes(userEntityRef.toLowerCase())) {\r\n // a user ref has been found on a namespace rule\r\n allowedToNamespace=true\r\n }\r\n else {\r\n var groupResult=rule.identityRefs.some(identityRef => userGroups.includes(identityRef));\r\n if (groupResult) {\r\n // a group ref match has been found\r\n allowedToNamespace=true\r\n }\r\n }\r\n }\r\n else {\r\n // no restrictions for this namespace\r\n allowedToNamespace=true\r\n }\r\n }\r\n else {\r\n console.log(`Invalid channel: ${channel}`)\r\n }\r\n return allowedToNamespace\r\n}\r\n\r\nconst checkPodPermissionRule = (ppr:PodPermissionRule, entityName:string, userEntityRef:string, userGroups:string[]) : boolean => {\r\n var refMatch:boolean=false;\r\n\r\n for (var podNameRegex of ppr.pods) {\r\n if (podNameRegex.test(entityName)) {\r\n for (var refRegex of ppr.refs) {\r\n // find userRef\r\n refMatch=refRegex.test(userEntityRef.toLowerCase())\r\n if (refMatch) {\r\n break;\r\n }\r\n else {\r\n // find group ref\r\n refMatch = userGroups.some(g => refRegex.test(g))\r\n if (refMatch) {\r\n break\r\n }\r\n }\r\n }\r\n }\r\n else {\r\n }\r\n if (refMatch) break\r\n }\r\n return refMatch\r\n}\r\n\r\n// const getPodPermissionSet = (reqScope:InstanceConfigScopeEnum, cluster:KwirthClusterData) => {\r\n// switch (reqScope) {\r\n// // case InstanceConfigScopeEnum.FILTER:\r\n// case InstanceConfigScopeEnum.SNAPSHOT:\r\n// case InstanceConfigScopeEnum.STREAM:\r\n// console.log(cluster.podPermissions)\r\n// return cluster.podPermissions.get(reqScope)\r\n// default:\r\n// console.log(`Invalid scope ${reqScope} for permission set`)\r\n// }\r\n// return undefined\r\n// }\r\nconst getPodPermissionSet = (channel:string, cluster:KwirthClusterData) => {\r\n if (cluster.podPermissions.has(channel)) {\r\n return cluster.podPermissions.get(channel)\r\n }\r\n else {\r\n console.log(`Invalid channel ${channel} for permission set`)\r\n return undefined\r\n }\r\n}\r\n\r\n/**\r\n * This funciton checks permissions according to app-config rules (not kwirth rules), that is, namespace rules,\r\n * viewing rules and restarting rules\r\n * @param loggerSvc Backstage logger\r\n * @param reqCluster the cluster the pod belongs to\r\n * @param reqPod data about the pod the user wants to access\r\n * @param podPermissionSet a set of permission for the cluster (extracted from app-config)\r\n * @param entityName the name of the entity to search for\r\n * @param userEntityRef the canonical identity reference for the user ('type:namespace/ref')\r\n * @param userGroups ana array containing a list of groups the user belongs to\r\n * @returns booelan indicating if the user can access the pod for doing what scaope says (view or restart)\r\n */\r\n\r\n// const checkPodAccess = (loggerSvc:LoggerService, reqCluster:ClusterValidPods, reqPod:PodData, podPermissionSet:KubelogPodPermissions[], entityName:string, userEntityRef:string, userGroups:string[]):boolean => {\r\n// var cluster = KubelogStaticData.clusterKubelogData.get(reqCluster.name)\r\n\r\n// if (!cluster) {\r\n// loggerSvc.warn(`Invalid cluster specified ${reqCluster.name}`)\r\n// return false\r\n// }\r\n\r\nconst checkPodAccess = (reqPod:PodData, podPermissionSet:KwirthPodPermissions[], entityName:string, userEntityRef:string, userGroups:string[]):boolean => {\r\n // we check all pod permissions until one of them evaluates to true (must be true on allow/except and false on deny/unless)\r\n\r\n // we use 'filter' here beacause a namespace can be specified more than once\r\n for (var podPermission of podPermissionSet.filter(pp => pp.namespace===reqPod.namespace)) {\r\n if (podPermission.allow) {\r\n \r\n // **** evaluate allow/except rules ****\r\n var allowMatches=false;\r\n var exceptMatches=false;\r\n // we test all allow rules, we stop if one matches\r\n for (var prr of podPermission.allow) {\r\n allowMatches = checkPodPermissionRule(prr, entityName, userEntityRef, userGroups);\r\n }\r\n if (allowMatches) {\r\n if (podPermission.except) {\r\n // we test all except rules, will stop if found one that matches, no need to continue\r\n for (var prr of podPermission.except) {\r\n //exceptMatches = checkPodPermissionRule(prr, reqPod, entityName, userEntityRef, userGroups);\r\n exceptMatches = checkPodPermissionRule(prr, entityName, userEntityRef, userGroups)\r\n // if there is a exception the process finishes now for this podPermission)\r\n if (exceptMatches) {\r\n break\r\n }\r\n }\r\n }\r\n else {\r\n }\r\n }\r\n\r\n if (allowMatches && !exceptMatches) {\r\n // **** evaluate deny/unless rules ****\r\n if (podPermission.deny) {\r\n var denyMatches=false\r\n var unlessMatches=false\r\n for (var prr of podPermission.deny) {\r\n //denyMatches = checkPodPermissionRule(prr, reqPod, entityName, userEntityRef, userGroups)\r\n denyMatches = checkPodPermissionRule(prr, entityName, userEntityRef, userGroups)\r\n if (denyMatches) {\r\n break;\r\n }\r\n }\r\n if (denyMatches && podPermission.unless) {\r\n for (var prr of podPermission.unless) {\r\n //unlessMatches = checkPodPermissionRule(prr, reqPod, entityName, userEntityRef, userGroups)\r\n unlessMatches = checkPodPermissionRule(prr, entityName, userEntityRef, userGroups)\r\n if (unlessMatches) {\r\n break;\r\n }\r\n }\r\n }\r\n if (!denyMatches || (denyMatches && unlessMatches)) {\r\n return true\r\n }\r\n }\r\n else {\r\n return true\r\n }\r\n }\r\n else {\r\n // do nothing, just continue podpermissionset loop\r\n }\r\n }\r\n else {\r\n // if no 'allow' is specified everybody has access\r\n // we continue loop checking other namespaces\r\n return true\r\n }\r\n }\r\n \r\n return false\r\n}\r\n\r\nexport { checkNamespaceAccess, checkPodAccess, getPodPermissionSet }","/*\r\nCopyright 2024 Julio Fernandez\r\n\r\nLicensed under the Apache License, Version 2.0 (the \"License\");\r\nyou may not use this file except in compliance with the License.\r\nYou may obtain a copy of the License at\r\n\r\n http://www.apache.org/licenses/LICENSE-2.0\r\n\r\nUnless required by applicable law or agreed to in writing, software\r\ndistributed under the License is distributed on an \"AS IS\" BASIS,\r\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\nSee the License for the specific language governing permissions and\r\nlimitations under the License.\r\n*/\r\nimport express from 'express'\r\nimport Router from 'express-promise-router'\r\nimport { AuthService, BackstageUserInfo, DiscoveryService, HttpAuthService, LoggerService, RootConfigService, UserInfoService } from '@backstage/backend-plugin-api'\r\nimport { CatalogClient } from '@backstage/catalog-client'\r\nimport { UserEntity } from '@backstage/catalog-model'\r\nimport { FetchApi } from '@backstage/core-plugin-api'\r\n\r\n// Kwirthbackstage\r\nimport { ClusterValidPods, PodData } from '@jfvilas/plugin-kwirth-common'\r\nimport { loadClusters } from './config'\r\nimport { KwirthStaticData, VERSION } from '../model/KwirthStaticData'\r\nimport { checkNamespaceAccess, checkPodAccess, getPodPermissionSet } from './permissions'\r\nimport { InstanceConfigScopeEnum } from '@jfvilas/kwirth-common'\r\n\r\nexport type KwirthRouterOptions = {\r\n discoverySvc: DiscoveryService\r\n configSvc: RootConfigService\r\n loggerSvc: LoggerService\r\n userInfoSvc: UserInfoService\r\n authSvc: AuthService\r\n httpAuthSvc: HttpAuthService\r\n}\r\n\r\n// const debug = (a:any) => {\r\n// if (process.env.KWIRTHDEBUG) console.log(a)\r\n// }\r\n\r\n/**\r\n * \r\n * @param options core services we need for Kwirthbackstage to work\r\n * @returns an express Router\r\n */\r\nasync function createRouter(options: KwirthRouterOptions) : Promise<express.Router> {\r\n const { configSvc, loggerSvc, userInfoSvc, authSvc, httpAuthSvc, discoverySvc } = options;\r\n\r\n loggerSvc.info('Loading static config')\r\n\r\n if (!configSvc.has('kubernetes.clusterLocatorMethods')) {\r\n loggerSvc.error(`Kwirthbackstage will not start, there is no 'clusterLocatorMethods' defined in app-config.`)\r\n throw new Error('Kwirthbackstage backend will not be available.')\r\n }\r\n\r\n try {\r\n loadClusters(loggerSvc, configSvc)\r\n }\r\n catch (err) {\r\n var txt=`Errors detected reading static configuration: ${err}`\r\n loggerSvc.error(txt)\r\n throw new Error(txt)\r\n }\r\n\r\n // subscribe to changes on app-config\r\n if (configSvc.subscribe) {\r\n configSvc.subscribe( () => {\r\n try {\r\n loggerSvc.warn('Change detected on app-config, Kwirthbackstage will update config.')\r\n loadClusters(loggerSvc, configSvc)\r\n }\r\n catch(err) {\r\n loggerSvc.error(`Errors detected reading new configuration: ${err}`)\r\n }\r\n })\r\n }\r\n else {\r\n loggerSvc.info('Kwirthbackstage cannot subscribe to config changes.')\r\n }\r\n\r\n const router = Router()\r\n\r\n router.use(express.json())\r\n\r\n // we need this function to be able to invoke another backend plugin passing an authorization token\r\n const createAuthFetchApi = (token: string) : FetchApi => {\r\n return {\r\n fetch: async (input, init) => {\r\n init = init || {}\r\n init.headers = {\r\n ...init.headers,\r\n Authorization: `Bearer ${token}`,\r\n }\r\n return fetch(input, init)\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Invokes Kwirth to obtain a list of pods that are tagged with the kubernetes-id of the entity we are looking for.\r\n * @param entityName name of the tagge dentity\r\n * @returns a ClusterValidPods[] (each ClusterValidPods is a cluster info with a list of pods tagged with the entityName).\r\n */\r\n const getValidClusters = async (entityName:string) : Promise<ClusterValidPods[]> => {\r\n var clusterList:ClusterValidPods[]=[]\r\n\r\n for (const clusterName of KwirthStaticData.clusterKwirthData.keys()) {\r\n var url = KwirthStaticData.clusterKwirthData.get(clusterName)?.kwirthHome as string\r\n var apiKeyStr = KwirthStaticData.clusterKwirthData.get(clusterName)?.kwirthApiKey\r\n var title = KwirthStaticData.clusterKwirthData.get(clusterName)?.title\r\n var queryUrl=url+`/managecluster/find?label=backstage.io%2fkubernetes-id&entity=${entityName}&type=pod&data=containers`\r\n try {\r\n var fetchResp = await fetch (queryUrl, {headers:{'Authorization':'Bearer '+apiKeyStr}})\r\n if (fetchResp.status===200) {\r\n var jsonResp=await fetchResp.json()\r\n if (jsonResp) {\r\n let podData:ClusterValidPods = {\r\n name: clusterName, url, title, data: jsonResp, accessKeys: new Map()\r\n }\r\n clusterList.push(podData)\r\n }\r\n }\r\n else {\r\n loggerSvc.warn(`Invalid response from cluster ${clusterName}: ${fetchResp.status}`)\r\n console.log(await fetchResp.text())\r\n clusterList.push({ name: clusterName, url, title, data:[], accessKeys:new Map() })\r\n }\r\n\r\n }\r\n catch (err) {\r\n loggerSvc.warn(`Cannot access cluster ${clusterName} (URL: ${queryUrl}): ${err}`)\r\n clusterList.push({ name: clusterName, url, title, data:[], accessKeys:new Map() })\r\n }\r\n }\r\n\r\n return clusterList\r\n }\r\n\r\n const createAccessKey = async (reqScope:InstanceConfigScopeEnum, cluster:ClusterValidPods, reqPods:PodData[], userName:string) : Promise<any> => {\r\n var resources = reqPods.map(podData => `${reqScope}:${podData.namespace}::${podData.name}:`).join(',')\r\n\r\n var kwirthHome = KwirthStaticData.clusterKwirthData.get(cluster.name)?.kwirthHome as string\r\n var kwirthApiKey = KwirthStaticData.clusterKwirthData.get(cluster.name)?.kwirthApiKey\r\n var payload={\r\n type:'bearer',\r\n resource: resources,\r\n description:`Backstage API key for user ${userName}`,\r\n expire:Date.now()+60*60*1000\r\n }\r\n var fetchResp=await fetch(kwirthHome+'/key',{method:'POST', body:JSON.stringify(payload), headers:{'Content-Type':'application/json', Authorization:'Bearer '+kwirthApiKey}})\r\n if (fetchResp.status===200) {\r\n var data = await fetchResp.json();\r\n console.log('data')\r\n console.log(data)\r\n return data.accessKey\r\n }\r\n else {\r\n loggerSvc.warn(`Invalid response asking for a key from cluster ${cluster.name}: ${fetchResp.status}`)\r\n return {}\r\n }\r\n }\r\n\r\n const addAccessKeys = async (channel:string, reqScope:InstanceConfigScopeEnum, foundClusters:ClusterValidPods[], entityName:string, userEntityRef:string, userGroups:string[]) => {\r\n //var reqScope:InstanceConfigScopeEnum = reqScopeStr as InstanceConfigScopeEnum\r\n if (!reqScope) {\r\n loggerSvc.info(`Invalid scope requested: ${reqScope}`)\r\n return\r\n }\r\n var principal=userEntityRef.split(':')[1]\r\n var username=principal.split('/')[1]\r\n\r\n for (var foundCluster of foundClusters) {\r\n var podList:PodData[]=[]\r\n\r\n // for each pod we've found on the cluster we check all namespace permissions\r\n\r\n for (var podData of foundCluster.data) {\r\n // first we check if user is allowed to acccess namespace\r\n var allowedToNamespace = checkNamespaceAccess(channel, foundCluster, podData, userEntityRef, userGroups)\r\n\r\n if (allowedToNamespace) {\r\n // then we check if required pod namespace has pod access restriccions for requested namespace\r\n var clusterDef = KwirthStaticData.clusterKwirthData.get(foundCluster.name)\r\n var podPermissionSet = getPodPermissionSet(channel, clusterDef!)\r\n if (!podPermissionSet) {\r\n loggerSvc.warn(`Pod permission set not found: ${channel}`)\r\n continue\r\n }\r\n var namespaceRestricted = podPermissionSet.some(pp => pp.namespace===podData.namespace);\r\n if (!namespaceRestricted || checkPodAccess(podData, podPermissionSet, entityName, userEntityRef, userGroups)) {\r\n // there are no namespace restrictions specified in the pod permission set\r\n podList.push(podData)\r\n }\r\n else {\r\n }\r\n }\r\n else {\r\n // user is not allowed to namespace, so we don't need to check pod permissions\r\n // the loop cotinues with other pods\r\n }\r\n }\r\n if (podList.length>0) {\r\n let accessKey = await createAccessKey(reqScope, foundCluster, podList, username)\r\n console.log('accessKey')\r\n console.log(accessKey)\r\n foundCluster.accessKeys.set(reqScope, accessKey)\r\n }\r\n else {\r\n console.log(`No pods on podList for ${channel} and ${reqScope}`)\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * builds a list of groups (expressed as identity refs) that the user belongs to.\r\n * @param userInfo Backstage user info of the user to search groups for\r\n * @returns an array of group refs in canonical form\r\n */\r\n const getUserGroups = async (userInfo:BackstageUserInfo) : Promise<string[]> => {\r\n const { token } = await authSvc.getPluginRequestToken({\r\n onBehalfOf: await authSvc.getOwnServiceCredentials(),\r\n targetPluginId: 'catalog'\r\n });\r\n const catalogClient = new CatalogClient({\r\n discoveryApi: discoverySvc,\r\n fetchApi: createAuthFetchApi(token),\r\n });\r\n\r\n const entity = await catalogClient.getEntityByRef(userInfo.userEntityRef) as UserEntity\r\n var userGroupsRefs:string[]=[]\r\n //+++ future use: recursive memberOf\r\n if (entity?.spec.memberOf) userGroupsRefs=entity?.spec.memberOf\r\n return userGroupsRefs\r\n }\r\n\r\n // this is and API endpoint controller\r\n const processVersion = async (_req:any, res:any) => {\r\n res.status(200).send({ version:VERSION })\r\n }\r\n\r\n // this is and API endpoint controller\r\n const processAccess = async (req:express.Request, res:express.Response) => {\r\n if (!req.query['scopes'] || !req.query['scopes']) {\r\n res.status(400).send(`'scopes' and 'channel' are required`)\r\n return\r\n }\r\n let reqScopes = (req.query['scopes'].toString()).split(',')\r\n let reqChannel = req.query['channel']?.toString()!\r\n \r\n // obtain basic user info\r\n const credentials = await httpAuthSvc.credentials(req, { allow: ['user'] })\r\n const userInfo = await userInfoSvc.getUserInfo(credentials)\r\n // get user groups list\r\n let userGroupsRefs=await getUserGroups(userInfo)\r\n\r\n loggerSvc.info(`Checking reqScopes '${req.query['scopes']}' scopes to pod: '${req.body.metadata.namespace+'/'+req.body.metadata.name}' for user '${userInfo.userEntityRef}'`)\r\n\r\n // get a list of clusters that contain pods related to entity\r\n //+++ control errors here (maybe we cannot conntact the cluster, for example)\r\n let foundClusters:ClusterValidPods[]=await getValidClusters(req.body.metadata.name)\r\n\r\n // add access keys to authorized resources (according to group membership and Kwirthbackstage config in app-config (namespace and pod permissions))\r\n for (var reqScopeStr of reqScopes) {\r\n var reqScope = reqScopeStr as InstanceConfigScopeEnum\r\n await addAccessKeys(reqChannel, reqScope, foundClusters, req.body.metadata.name, userInfo.userEntityRef, userGroupsRefs)\r\n }\r\n \r\n // we build a stringn of arrays from the Map /that can not be serialized)\r\n for (var c of foundClusters) {\r\n (c as any).accessKeys = JSON.stringify(Array.from(c.accessKeys.entries()))\r\n }\r\n res.status(200).send(foundClusters)\r\n }\r\n\r\n router.post(['/access'], (req, res) => {\r\n processAccess(req,res)\r\n })\r\n\r\n router.get(['/version'], (req, res) => {\r\n processVersion(req,res)\r\n })\r\n\r\n return router\r\n}\r\n\r\nexport { createRouter }\r\n","/*\r\nCopyright 2024 Julio Fernandez\r\n\r\nLicensed under the Apache License, Version 2.0 (the \"License\");\r\nyou may not use this file except in compliance with the License.\r\nYou may obtain a copy of the License at\r\n\r\n http://www.apache.org/licenses/LICENSE-2.0\r\n\r\nUnless required by applicable law or agreed to in writing, software\r\ndistributed under the License is distributed on an \"AS IS\" BASIS,\r\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\nSee the License for the specific language governing permissions and\r\nlimitations under the License.\r\n*/\r\nimport { coreServices, createBackendPlugin } from '@backstage/backend-plugin-api'\r\nimport { createRouter } from './service/router'\r\n\r\nexport const kwirthPlugin = createBackendPlugin({\r\n pluginId: 'kwirthbackstage',\r\n register(env) {\r\n env.registerInit({\r\n deps: {\r\n discovery: coreServices.discovery,\r\n config: coreServices.rootConfig,\r\n logger: coreServices.logger,\r\n auth: coreServices.auth,\r\n httpAuth: coreServices.httpAuth,\r\n httpRouter: coreServices.httpRouter,\r\n userInfo: coreServices.userInfo\r\n },\r\n async init({ discovery, config, httpRouter, logger, auth, httpAuth, userInfo }) {\r\n httpRouter.use(\r\n await createRouter({\r\n discoverySvc: discovery,\r\n configSvc: config,\r\n loggerSvc: logger,\r\n authSvc: auth,\r\n httpAuthSvc: httpAuth,\r\n userInfoSvc: userInfo\r\n })\r\n )\r\n }\r\n })\r\n }\r\n})\r\n"],"names":["versionGreatOrEqualThan","Router","express","catalogClient","CatalogClient","createBackendPlugin","coreServices"],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import express from 'express';
2
+ import * as _backstage_backend_plugin_api from '@backstage/backend-plugin-api';
3
+ import { DiscoveryService, RootConfigService, LoggerService, UserInfoService, AuthService, HttpAuthService } from '@backstage/backend-plugin-api';
4
+
5
+ type KwirthRouterOptions = {
6
+ discoverySvc: DiscoveryService;
7
+ configSvc: RootConfigService;
8
+ loggerSvc: LoggerService;
9
+ userInfoSvc: UserInfoService;
10
+ authSvc: AuthService;
11
+ httpAuthSvc: HttpAuthService;
12
+ };
13
+ /**
14
+ *
15
+ * @param options core services we need for Kwirthbackstage to work
16
+ * @returns an express Router
17
+ */
18
+ declare function createRouter(options: KwirthRouterOptions): Promise<express.Router>;
19
+
20
+ declare const kwirthPlugin: _backstage_backend_plugin_api.BackendFeatureCompat;
21
+
22
+ export { createRouter, kwirthPlugin as default };
23
+ export type { KwirthRouterOptions };
package/package.json ADDED
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "@jfvilas/plugin-kwirth-backend",
3
+ "version": "0.12.2",
4
+ "description": "Backstage backend plugin for Kwirth plugins",
5
+ "keywords": [
6
+ "Backstage",
7
+ "Kubernetes",
8
+ "chart",
9
+ "observability",
10
+ "Kwirth",
11
+ "plugin"
12
+ ],
13
+ "backstage": {
14
+ "role": "backend-plugin",
15
+ "pluginId": "kwirth",
16
+ "pluginPackages": [
17
+ "@jfvilas/plugin-kwirth-backend",
18
+ "@jfvilas/plugin-kwirth-common"
19
+ ]
20
+ },
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "https://github.com/jfvilas/plugin-kwirth-backend"
24
+ },
25
+ "main": "dist/index.cjs.js",
26
+ "types": "dist/index.d.ts",
27
+ "license": "Apache-2.0",
28
+ "author": {
29
+ "name": "Julio Fernandez",
30
+ "url": "https://github.com/jfvilas",
31
+ "email": "jfvilas@outlook.com"
32
+ },
33
+ "publishConfig": {
34
+ "access": "public",
35
+ "main": "dist/index.cjs.js",
36
+ "types": "dist/index.d.ts"
37
+ },
38
+ "scripts": {
39
+ "start": "backstage-cli package start",
40
+ "build": "backstage-cli package build",
41
+ "lint": "backstage-cli package lint",
42
+ "test": "backstage-cli package test",
43
+ "clean": "backstage-cli package clean",
44
+ "prepack": "backstage-cli package prepack",
45
+ "postpack": "backstage-cli package postpack"
46
+ },
47
+ "dependencies": {
48
+ "@backstage/backend-common": "^0.23.3",
49
+ "@backstage/backend-plugin-api": "^0.8.1",
50
+ "@backstage/catalog-client": "^1.6.5",
51
+ "@backstage/catalog-model": "^1.5.0",
52
+ "@backstage/config": "^1.2.0",
53
+ "@backstage/errors": "^1.2.4",
54
+ "@backstage/integration": "^1.13.0",
55
+ "@jfvilas/kwirth-common": "0.3.55",
56
+ "@types/express": "^4.17.6",
57
+ "express": "^4.17.1",
58
+ "express-promise-router": "^4.1.0",
59
+ "node-fetch": "^2.6.5",
60
+ "yn": "^4.0.0"
61
+ },
62
+ "devDependencies": {
63
+ "@backstage/cli": "^0.26.11",
64
+ "@types/node-fetch": "^2.5.12",
65
+ "@types/supertest": "^2.0.8",
66
+ "supertest": "^6.1.3"
67
+ },
68
+ "files": [
69
+ "dist"
70
+ ]
71
+ }