@jfvilas/plugin-kwirth-backend 0.12.3 → 0.12.5
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 +53 -31
- package/dist/index.cjs.js +96 -66
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/package.json +1 -2
package/README.md
CHANGED
|
@@ -1,28 +1,33 @@
|
|
|
1
1
|
# Backstage backend Kwirth plugin
|
|
2
|
-
This Backstage plugin is the backend for several
|
|
2
|
+
This Backstage plugin is the backend for several Backstage plugins that we have developed for integrating live streaming Kubernetes observability data (whatever be its type) into Backstage by using Kwirth plugins. It's important to understand that **Kwirth provides different kinds of information** (log, metrics, events, alerts, operations...), and due to this way of working, the whole set of Backstage Kwirth plugins are comprised by:
|
|
3
3
|
|
|
4
4
|
- One only backend plugin (**this one**).
|
|
5
|
-
- Several frontend plugins, each one including its own
|
|
6
|
-
|
|
7
|
-
**NOTE: Backstage Kwirth plugins requires a Kwirth server running on Kubernetes whose version is at least 0.3.155**
|
|
5
|
+
- Several frontend plugins, each one including its own feature set. Typically, there should exist one Backstage Kwirth frontend plugin for each Kwirth supported channel (please refer to information on Kwirth channels here [Kwirth Channels](https://jfvilas.github.io/kwirth/#/channels)).
|
|
6
|
+
One common plugin, containing common artifactos to use by frontend plugins and backend plugin.
|
|
8
7
|
|
|
9
8
|
This [Backstage]((https://backstage.io)) backend plugin is primarily responsible for the following tasks:
|
|
10
9
|
|
|
11
10
|
- Reading Kwirth config from your app-config YAML file.
|
|
12
|
-
-
|
|
11
|
+
- Performing login processes to remote Kwirth instances, and thus obtaining valid API keys for users to stream kubernetes data.
|
|
13
12
|
- Receiving and answering API calls from configured frontend Kwirth plugins on your Backstage instance.
|
|
14
13
|
|
|
15
|
-
##
|
|
14
|
+
## Version compatibility
|
|
15
|
+
Following table shows version compatibility between Kwirth Backstage plugin and Kwirth Core server.
|
|
16
|
+
|
|
17
|
+
| Plugin Kwirth version | Kwirth version |
|
|
18
|
+
|-|-|
|
|
19
|
+
|0.0.1|0.3.155|
|
|
20
|
+
|0.0.2|0.4.20|
|
|
16
21
|
|
|
17
|
-
|
|
18
|
-
Here
|
|
22
|
+
## Install plugin
|
|
23
|
+
Here we show 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
24
|
|
|
20
25
|
```sh
|
|
21
26
|
# From your Backstage root directory
|
|
22
27
|
yarn --cwd packages/backend add @jfvilas/plugin-kwirth-backend @jfvilas/plugin-kwirth-common
|
|
23
28
|
```
|
|
24
29
|
|
|
25
|
-
### New Backend System (we don't work with old backend system)
|
|
30
|
+
### Taylor your New Backend System (we don't work with old backend system)
|
|
26
31
|
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
32
|
|
|
28
33
|
```diff
|
|
@@ -38,29 +43,43 @@ Next, you need to modify your backend index file for starting Kwirth backend plu
|
|
|
38
43
|
```
|
|
39
44
|
|
|
40
45
|
## 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.
|
|
46
|
+
To have your Kwirth backend 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
47
|
|
|
43
|
-
Remember, Backstage Kwirth plugins
|
|
48
|
+
Remember, frontend Backstage Kwirth plugins help you in showing live-streaming kubernetes observability data inside Backstage to ease your develoment teams work, but take into account that **this Backstage backend 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" that data 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, you can receive alerts or security information related to your pods (based on [Trivy Operator](https://github.com/aquasecurity/trivy-operator)) etc.
|
|
44
49
|
|
|
45
50
|
### 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:
|
|
51
|
+
We will not cover a detailed approach on this subject here, we refer you to [Kwirth installation documentation](https://jfvilas.github.io/kwirth/#/0.4.20/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
52
|
|
|
48
53
|
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
|
|
51
|
-
4. Kwirth can export **
|
|
54
|
+
2. Kwirth doesn't need any persistenace layer (no database, no network storage, no block storage, no file storage). It uses only Kubernetes control-plane storage.
|
|
55
|
+
3. Kwirth includes user management, API security and multi-cluster access.
|
|
56
|
+
4. Kwirth can export **kubernetes observability data in real-time** wherever you need it.
|
|
57
|
+
5. The kind of observability data you need is served by differnet [Kwirth channels](https://jfvilas.github.io/kwirth/#/0.4.20/channels?id=channels).
|
|
52
58
|
|
|
53
59
|
### 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).
|
|
60
|
+
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 URL** (we will need it for configuring Kwirth backend plugin). In order to simplify this tutorial we will assume your Kwirth is published on: **http://your-external.dns.name/kwirth**.
|
|
55
61
|
|
|
56
62
|
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
63
|
|
|
60
|
-
|
|
64
|
+
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 observability data.
|
|
65
|
+
2. Create an API Key following this procedures:
|
|
66
|
+
- On the main menu (the burger icon) select 'API Security'.
|
|
67
|
+
- Click 'NEW' button on the bottom-left side of the dialog.
|
|
68
|
+
- Enter some description on the right, like 'API key for my Backstage instance'.
|
|
69
|
+
- Enter lease time (is the number of days the API Key will be valid).
|
|
70
|
+
- Don't worry about key type, it is fixed with 'permanent' value.
|
|
71
|
+
- On the 'Scopes' combo check only 'cluster' option.
|
|
72
|
+
- Click 'Save' on the bottom-right side for saving this resource access into your API key.
|
|
73
|
+
- Now click on th 'SAVE' button on the bottom-left side for saving this API Key.
|
|
74
|
+
3. API Key should appear on the API Key list, inlcuding its expiration date.
|
|
75
|
+
4. Select your API Key (clicking on it) and click on bottom-left 'COPY' button for copying the API Key that you will add to your app-config YAML file.
|
|
76
|
+
|
|
77
|
+
**NOTE:** Depending on the version of Kwirth you have deployed, maybe you find a dialog for performing all these actions automatically the first time you log in with the admin user.
|
|
78
|
+
|
|
79
|
+
This is all you need to do inside Kwirth. You can also do some play on Kwirth front application, it's very funny, I fully recommend it !!!
|
|
61
80
|
|
|
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,
|
|
81
|
+
### 3. Backstage base configuration
|
|
82
|
+
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, Kwirth 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
83
|
|
|
65
84
|
```yaml
|
|
66
85
|
kubernetes:
|
|
@@ -77,11 +96,12 @@ kubernetes:
|
|
|
77
96
|
skipMetricsLookup: true
|
|
78
97
|
```
|
|
79
98
|
|
|
80
|
-
|
|
99
|
+
For using Kwirth Backstage plugin we need to add (at least) 2 properties to the cluster configuration:
|
|
81
100
|
- kwirthHome: the home URL of the Kwirth installation.
|
|
82
|
-
- kwirthApiKey: the API key we created before.
|
|
101
|
+
- kwirthApiKey: the API key we created before (and should be kept in your clipboard).
|
|
83
102
|
|
|
84
103
|
The kubernetes section should look now something like this:
|
|
104
|
+
|
|
85
105
|
```diff
|
|
86
106
|
kubernetes:
|
|
87
107
|
serviceLocatorMethod:
|
|
@@ -100,12 +120,14 @@ kubernetes:
|
|
|
100
120
|
```
|
|
101
121
|
|
|
102
122
|
### 4. Channels
|
|
103
|
-
Kwirth live-streaming
|
|
123
|
+
Kwirth live-streaming system can export different kinds of data: log streaming, metrics streaming, alerts, events, security posture... In Kwirth, these different types of data are grouped in what we call **channels**. In fact, each channel may be viewed as an independent data service (please refer here to learn how the channel system works [Kwirth channels](https://jfvilas.github.io/kwirth/#/0.4.20/channelarch?id=channels)).
|
|
104
124
|
|
|
105
125
|
Each Kwirth channel is functionally mapped to a Kwirth frontend plugin, and thus, there exist a specific configuration for each channel. So:
|
|
106
126
|
|
|
107
|
-
- For the Kwirth metrics channel, you should use the 'plugin-kwirth-metrics' frontend plugin.
|
|
127
|
+
- For the Kwirth metrics streaming channel, you should use the 'plugin-kwirth-metrics' frontend plugin.
|
|
108
128
|
- For the Kwirth real-time log channel, you should use the 'plugin-kwirth-log' frontend plugin.
|
|
129
|
+
- For the Kwirth cybersecurity channel (named trivy channel), you should use the 'plugin-kwirth-trivy' frontend plugin.
|
|
130
|
+
- For the Kwirth alert channel, you should use the 'plugin-kwirth-alert' frontend plugin.
|
|
109
131
|
- ...
|
|
110
132
|
|
|
111
133
|
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:
|
|
@@ -139,7 +161,7 @@ kubernetes:
|
|
|
139
161
|
|
|
140
162
|
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
163
|
|
|
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.
|
|
164
|
+
So, for adding 'log' and 'alert' channel we have created two sections: 'kwirthlog' and 'kwirthalert'. The content of channel sections inside the app-config file is explained bellow, it is just the permission system.
|
|
143
165
|
|
|
144
166
|
### 4. Permissions
|
|
145
167
|
|
|
@@ -152,7 +174,7 @@ The permission system of Kwirth plugin for Backstage has been designed with thes
|
|
|
152
174
|
So, the permission system has been build using (right now) two layers:
|
|
153
175
|
|
|
154
176
|
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
|
|
177
|
+
2. **Pod layer**. If namespace permission layer is not coarse enough for you, you can refine your permissions by using the pod permission layer. In addition, the pod layer allows adding scopes to the different permissions you can assign.
|
|
156
178
|
|
|
157
179
|
|
|
158
180
|
#### Namespace layer
|
|
@@ -166,7 +188,7 @@ Let's build a sample situation. Typically, you would restrict access to kubernet
|
|
|
166
188
|
- Only Operations (devops) teams and Administrators can view stage logs.
|
|
167
189
|
- Only Administrators can see production logs. In addition to administrators, production can also be accessed by Nicklaus Wirth.
|
|
168
190
|
|
|
169
|
-
The way you can manage this in Kwirth
|
|
191
|
+
The way you can manage this in Kwirth plugin is **via Group entities** of Backstage. That is:
|
|
170
192
|
- You create a group where you can add all your developers.
|
|
171
193
|
- Another group with your devops team.
|
|
172
194
|
- And a group containing just the Administrators.
|
|
@@ -182,7 +204,7 @@ Once you have created the groups you can configure the namespace permission addi
|
|
|
182
204
|
title: 'Kubernetes local'
|
|
183
205
|
kwirthHome: http://your-external.dns.name/kwirth
|
|
184
206
|
kwirthApiKey: '40f5ea6c-bac3-df2f-d184-c9f3ab106ba9|permanent|cluster::::'
|
|
185
|
-
|
|
207
|
+
kwirthlog:
|
|
186
208
|
+ namespacePermissions:
|
|
187
209
|
+ - stage: ['group:default/devops', 'group:default/admin']
|
|
188
210
|
+ - production: ['group:default/admin', 'user:default/nicklaus-wirth']
|
|
@@ -192,7 +214,7 @@ Once you have created the groups you can configure the namespace permission addi
|
|
|
192
214
|
```
|
|
193
215
|
|
|
194
216
|
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
|
|
217
|
+
1. Everybody can access 'dev' namespace, since we have stated *no restrictions* at all (we added no 'dev' namespace in the namespacePermissions)
|
|
196
218
|
2. 'stage' namespace can be accessed by group 'devops' and group 'admin'.
|
|
197
219
|
3. The 'production' namespace can be accessed by the group of administrators ('admin' group) and the user Nicklaus Wirth ('nicklaus-wirth').
|
|
198
220
|
|
|
@@ -216,7 +238,7 @@ Let's consider a simple view-scoped pod permission sample based on previously de
|
|
|
216
238
|
authProvider: 'serviceAccount'
|
|
217
239
|
skipTLSVerify: true
|
|
218
240
|
skipMetricsLookup: true
|
|
219
|
-
|
|
241
|
+
kwirthlog:
|
|
220
242
|
namespacePermissions:
|
|
221
243
|
- stage: ['group:default/devops', 'group:default/admin']
|
|
222
244
|
- production: ['group:default/admin', 'user:default/nicklaus-wirth']
|
package/dist/index.cjs.js
CHANGED
|
@@ -14,16 +14,16 @@ var express__default = /*#__PURE__*/_interopDefaultCompat(express);
|
|
|
14
14
|
var Router__default = /*#__PURE__*/_interopDefaultCompat(Router);
|
|
15
15
|
|
|
16
16
|
const VERSION = "0.0.1";
|
|
17
|
-
const MIN_KWIRTH_VERSION = "0.
|
|
17
|
+
const MIN_KWIRTH_VERSION = "0.4.11";
|
|
18
18
|
class KwirthStaticData {
|
|
19
19
|
static clusterKwirthData = /* @__PURE__ */ new Map();
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
const loadNamespacePermissions = (
|
|
22
|
+
const loadNamespacePermissions = (channelConfig, logger) => {
|
|
23
23
|
var namespacePermissions = [];
|
|
24
|
-
if (
|
|
24
|
+
if (channelConfig.has("namespacePermissions")) {
|
|
25
25
|
logger.info(` Namespace permisson evaluation will be performed.`);
|
|
26
|
-
var permNamespaces =
|
|
26
|
+
var permNamespaces = channelConfig.getOptionalConfigArray("namespacePermissions");
|
|
27
27
|
for (var ns of permNamespaces) {
|
|
28
28
|
var namespace = ns.keys()[0];
|
|
29
29
|
var identityRefs = ns.getStringArray(namespace);
|
|
@@ -57,10 +57,11 @@ const loadPodRules = (config, category) => {
|
|
|
57
57
|
}
|
|
58
58
|
return rules;
|
|
59
59
|
};
|
|
60
|
-
const loadPodPermissions = (
|
|
60
|
+
const loadPodPermissions = (channelConfig, logger) => {
|
|
61
61
|
var clusterPodPermissions = [];
|
|
62
|
-
if (
|
|
63
|
-
|
|
62
|
+
if (channelConfig.has("podPermissions")) {
|
|
63
|
+
logger.info(` Pod permisson evaluation will be performed.`);
|
|
64
|
+
var namespaceList = channelConfig.getConfigArray("podPermissions");
|
|
64
65
|
for (var ns of namespaceList) {
|
|
65
66
|
var namespaceName = ns.keys()[0];
|
|
66
67
|
var podPermissions = { namespace: namespaceName };
|
|
@@ -84,20 +85,22 @@ const loadPodPermissions = (block, logger) => {
|
|
|
84
85
|
return clusterPodPermissions;
|
|
85
86
|
};
|
|
86
87
|
const addChannelPermissions = (channel, logger, cluster, kdata) => {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
88
|
+
let keyname = "kwirth" + channel;
|
|
89
|
+
let keyCamelName = "kwirth" + channel[0].toUpperCase() + channel.substring(1);
|
|
90
|
+
if (cluster.has(keyCamelName)) keyname = keyCamelName;
|
|
91
|
+
if (cluster.has(keyname)) {
|
|
92
|
+
logger.info(`Load permissions for channel ${channel} (config: ${keyname}).`);
|
|
93
|
+
var configChannel = cluster.getConfig(keyname);
|
|
94
|
+
if (configChannel.has("namespacePermissions")) {
|
|
92
95
|
logger.info(` Loading namespace permissions.`);
|
|
93
|
-
kdata.namespacePermissions.set(channel, loadNamespacePermissions(
|
|
96
|
+
kdata.namespacePermissions.set(channel, loadNamespacePermissions(configChannel, logger));
|
|
94
97
|
} else {
|
|
95
98
|
logger.info(` No namespace permissions.`);
|
|
96
99
|
kdata.namespacePermissions.set(channel, []);
|
|
97
100
|
}
|
|
98
|
-
if (
|
|
101
|
+
if (configChannel.has("podPermissions")) {
|
|
99
102
|
logger.info(` Loading pod permissions.`);
|
|
100
|
-
kdata.podPermissions.set(channel, loadPodPermissions(
|
|
103
|
+
kdata.podPermissions.set(channel, loadPodPermissions(configChannel, logger));
|
|
101
104
|
} else {
|
|
102
105
|
logger.info(` No pod permissions.`);
|
|
103
106
|
kdata.podPermissions.set(channel, []);
|
|
@@ -129,7 +132,10 @@ const loadClusters = async (logger, config) => {
|
|
|
129
132
|
inCluster: false,
|
|
130
133
|
namespace: "",
|
|
131
134
|
deployment: "",
|
|
132
|
-
lastVersion: ""
|
|
135
|
+
lastVersion: "",
|
|
136
|
+
clusterType: kwirthCommon.ClusterTypeEnum.KUBERNETES,
|
|
137
|
+
metricsInterval: 0,
|
|
138
|
+
channels: []
|
|
133
139
|
},
|
|
134
140
|
title,
|
|
135
141
|
namespacePermissions: /* @__PURE__ */ new Map(),
|
|
@@ -139,7 +145,7 @@ const loadClusters = async (logger, config) => {
|
|
|
139
145
|
logger.info(`Kwirth for ${name} is located at ${kwirthClusterData.kwirthHome}. Testing connection...`);
|
|
140
146
|
let enableCluster = false;
|
|
141
147
|
try {
|
|
142
|
-
var response = await fetch(kwirthClusterData.kwirthHome + "/config/
|
|
148
|
+
var response = await fetch(kwirthClusterData.kwirthHome + "/config/info");
|
|
143
149
|
try {
|
|
144
150
|
var data = await response.text();
|
|
145
151
|
try {
|
|
@@ -161,7 +167,10 @@ const loadClusters = async (logger, config) => {
|
|
|
161
167
|
inCluster: false,
|
|
162
168
|
namespace: "unknown",
|
|
163
169
|
deployment: "unknown",
|
|
164
|
-
lastVersion: "0.0.0"
|
|
170
|
+
lastVersion: "0.0.0",
|
|
171
|
+
clusterType: kwirthCommon.ClusterTypeEnum.KUBERNETES,
|
|
172
|
+
metricsInterval: 0,
|
|
173
|
+
channels: []
|
|
165
174
|
};
|
|
166
175
|
}
|
|
167
176
|
} catch (err) {
|
|
@@ -172,9 +181,11 @@ const loadClusters = async (logger, config) => {
|
|
|
172
181
|
logger.warn(`Kwirth home URL (${kwirthClusterData.kwirthHome}) at cluster '${kwirthClusterData.name}' cannot be accessed right now.`);
|
|
173
182
|
}
|
|
174
183
|
if (enableCluster) {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
184
|
+
[
|
|
185
|
+
kwirthCommon.InstanceMessageChannelEnum.LOG,
|
|
186
|
+
kwirthCommon.InstanceMessageChannelEnum.ALERT,
|
|
187
|
+
kwirthCommon.InstanceMessageChannelEnum.METRICS
|
|
188
|
+
].map((channel) => addChannelPermissions(channel, logger, cluster, kwirthClusterData));
|
|
178
189
|
KwirthStaticData.clusterKwirthData.set(name, kwirthClusterData);
|
|
179
190
|
} else {
|
|
180
191
|
logger.warn(`Cluster ${name} will be disabled`);
|
|
@@ -184,7 +195,7 @@ const loadClusters = async (logger, config) => {
|
|
|
184
195
|
}
|
|
185
196
|
}
|
|
186
197
|
}
|
|
187
|
-
logger.info("
|
|
198
|
+
logger.info("Kwirth static data has been set including following clusters:");
|
|
188
199
|
for (var c of KwirthStaticData.clusterKwirthData.keys()) {
|
|
189
200
|
logger.info(" " + c);
|
|
190
201
|
}
|
|
@@ -297,27 +308,27 @@ async function createRouter(options) {
|
|
|
297
308
|
const { configSvc, loggerSvc, userInfoSvc, authSvc, httpAuthSvc, discoverySvc } = options;
|
|
298
309
|
loggerSvc.info("Loading static config");
|
|
299
310
|
if (!configSvc.has("kubernetes.clusterLocatorMethods")) {
|
|
300
|
-
loggerSvc.error(`
|
|
301
|
-
throw new Error("
|
|
311
|
+
loggerSvc.error(`Kwirth will not start, there is no 'clusterLocatorMethods' defined in app-config.`);
|
|
312
|
+
throw new Error("Kwirth backend will not be available.");
|
|
302
313
|
}
|
|
303
314
|
try {
|
|
304
315
|
loadClusters(loggerSvc, configSvc);
|
|
305
316
|
} catch (err) {
|
|
306
|
-
|
|
317
|
+
let txt = `Errors detected reading static configuration: ${err}`;
|
|
307
318
|
loggerSvc.error(txt);
|
|
308
319
|
throw new Error(txt);
|
|
309
320
|
}
|
|
310
321
|
if (configSvc.subscribe) {
|
|
311
322
|
configSvc.subscribe(() => {
|
|
312
323
|
try {
|
|
313
|
-
loggerSvc.warn("Change detected on app-config,
|
|
324
|
+
loggerSvc.warn("Change detected on app-config, Kwirth will update config.");
|
|
314
325
|
loadClusters(loggerSvc, configSvc);
|
|
315
326
|
} catch (err) {
|
|
316
327
|
loggerSvc.error(`Errors detected reading new configuration: ${err}`);
|
|
317
328
|
}
|
|
318
329
|
});
|
|
319
330
|
} else {
|
|
320
|
-
loggerSvc.info("
|
|
331
|
+
loggerSvc.info("Kwirth cannot subscribe to config changes.");
|
|
321
332
|
}
|
|
322
333
|
const router = Router__default.default();
|
|
323
334
|
router.use(express__default.default.json());
|
|
@@ -334,16 +345,16 @@ async function createRouter(options) {
|
|
|
334
345
|
};
|
|
335
346
|
};
|
|
336
347
|
const getValidClusters = async (entityName) => {
|
|
337
|
-
|
|
348
|
+
let clusterList = [];
|
|
338
349
|
for (const clusterName of KwirthStaticData.clusterKwirthData.keys()) {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
350
|
+
let url = KwirthStaticData.clusterKwirthData.get(clusterName)?.kwirthHome;
|
|
351
|
+
let apiKeyStr = KwirthStaticData.clusterKwirthData.get(clusterName)?.kwirthApiKey;
|
|
352
|
+
let title = KwirthStaticData.clusterKwirthData.get(clusterName)?.title;
|
|
353
|
+
let queryUrl = url + `/managecluster/find?label=backstage.io%2fkubernetes-id&entity=${entityName}&type=pod&data=containers`;
|
|
343
354
|
try {
|
|
344
|
-
|
|
355
|
+
let fetchResp = await fetch(queryUrl, { headers: { "Authorization": "Bearer " + apiKeyStr } });
|
|
345
356
|
if (fetchResp.status === 200) {
|
|
346
|
-
|
|
357
|
+
let jsonResp = await fetchResp.json();
|
|
347
358
|
if (jsonResp) {
|
|
348
359
|
let podData = {
|
|
349
360
|
name: clusterName,
|
|
@@ -367,24 +378,26 @@ async function createRouter(options) {
|
|
|
367
378
|
return clusterList;
|
|
368
379
|
};
|
|
369
380
|
const createAccessKey = async (reqScope, cluster, reqPods, userName) => {
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
type: "bearer",
|
|
375
|
-
resource: resources,
|
|
381
|
+
let resources = reqPods.map((podData) => `${reqScope}:${podData.namespace}::${podData.name}:`).join(";");
|
|
382
|
+
let kwirthHome = KwirthStaticData.clusterKwirthData.get(cluster.name)?.kwirthHome;
|
|
383
|
+
let kwirthApiKey = KwirthStaticData.clusterKwirthData.get(cluster.name)?.kwirthApiKey;
|
|
384
|
+
let payload = {
|
|
376
385
|
description: `Backstage API key for user ${userName}`,
|
|
377
|
-
expire: Date.now() + 60 * 60 * 1e3
|
|
386
|
+
expire: Date.now() + 60 * 60 * 1e3,
|
|
387
|
+
days: 1,
|
|
388
|
+
accessKey: {
|
|
389
|
+
id: "",
|
|
390
|
+
type: "bearer",
|
|
391
|
+
resources
|
|
392
|
+
}
|
|
378
393
|
};
|
|
379
|
-
|
|
394
|
+
let fetchResp = await fetch(kwirthHome + "/key", { method: "POST", body: JSON.stringify(payload), headers: { "Content-Type": "application/json", Authorization: "Bearer " + kwirthApiKey } });
|
|
380
395
|
if (fetchResp.status === 200) {
|
|
381
|
-
|
|
382
|
-
console.log("data");
|
|
383
|
-
console.log(data);
|
|
396
|
+
let data = await fetchResp.json();
|
|
384
397
|
return data.accessKey;
|
|
385
398
|
} else {
|
|
386
399
|
loggerSvc.warn(`Invalid response asking for a key from cluster ${cluster.name}: ${fetchResp.status}`);
|
|
387
|
-
return
|
|
400
|
+
return void 0;
|
|
388
401
|
}
|
|
389
402
|
};
|
|
390
403
|
const addAccessKeys = async (channel, reqScope, foundClusters, entityName, userEntityRef, userGroups) => {
|
|
@@ -392,20 +405,20 @@ async function createRouter(options) {
|
|
|
392
405
|
loggerSvc.info(`Invalid scope requested: ${reqScope}`);
|
|
393
406
|
return;
|
|
394
407
|
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
for (
|
|
398
|
-
|
|
399
|
-
for (
|
|
400
|
-
|
|
408
|
+
let principal = userEntityRef.split(":")[1];
|
|
409
|
+
let username = principal.split("/")[1];
|
|
410
|
+
for (let foundCluster of foundClusters) {
|
|
411
|
+
let podList = [];
|
|
412
|
+
for (let podData of foundCluster.data) {
|
|
413
|
+
let allowedToNamespace = checkNamespaceAccess(channel, foundCluster, podData, userEntityRef, userGroups);
|
|
401
414
|
if (allowedToNamespace) {
|
|
402
|
-
|
|
403
|
-
|
|
415
|
+
let clusterDef = KwirthStaticData.clusterKwirthData.get(foundCluster.name);
|
|
416
|
+
let podPermissionSet = getPodPermissionSet(channel, clusterDef);
|
|
404
417
|
if (!podPermissionSet) {
|
|
405
418
|
loggerSvc.warn(`Pod permission set not found: ${channel}`);
|
|
406
419
|
continue;
|
|
407
420
|
}
|
|
408
|
-
|
|
421
|
+
let namespaceRestricted = podPermissionSet.some((pp) => pp.namespace === podData.namespace);
|
|
409
422
|
if (!namespaceRestricted || checkPodAccess(podData, podPermissionSet, entityName, userEntityRef, userGroups)) {
|
|
410
423
|
podList.push(podData);
|
|
411
424
|
}
|
|
@@ -413,11 +426,9 @@ async function createRouter(options) {
|
|
|
413
426
|
}
|
|
414
427
|
if (podList.length > 0) {
|
|
415
428
|
let accessKey = await createAccessKey(reqScope, foundCluster, podList, username);
|
|
416
|
-
|
|
417
|
-
console.log(accessKey);
|
|
418
|
-
foundCluster.accessKeys.set(reqScope, accessKey);
|
|
429
|
+
if (accessKey) foundCluster.accessKeys.set(reqScope, accessKey);
|
|
419
430
|
} else {
|
|
420
|
-
console.log(`No pods on podList for ${
|
|
431
|
+
console.log(`No pods on podList for '${reqScope}' on channel '${channel}'`);
|
|
421
432
|
}
|
|
422
433
|
}
|
|
423
434
|
};
|
|
@@ -431,7 +442,7 @@ async function createRouter(options) {
|
|
|
431
442
|
fetchApi: createAuthFetchApi(token)
|
|
432
443
|
});
|
|
433
444
|
const entity = await catalogClient$1.getEntityByRef(userInfo.userEntityRef);
|
|
434
|
-
|
|
445
|
+
let userGroupsRefs = [];
|
|
435
446
|
if (entity?.spec.memberOf) userGroupsRefs = entity?.spec.memberOf;
|
|
436
447
|
return userGroupsRefs;
|
|
437
448
|
};
|
|
@@ -439,7 +450,7 @@ async function createRouter(options) {
|
|
|
439
450
|
res.status(200).send({ version: VERSION });
|
|
440
451
|
};
|
|
441
452
|
const processAccess = async (req, res) => {
|
|
442
|
-
if (!req.query["scopes"] || !req.query["
|
|
453
|
+
if (!req.query["scopes"] || !req.query["channel"]) {
|
|
443
454
|
res.status(400).send(`'scopes' and 'channel' are required`);
|
|
444
455
|
return;
|
|
445
456
|
}
|
|
@@ -448,13 +459,32 @@ async function createRouter(options) {
|
|
|
448
459
|
const credentials = await httpAuthSvc.credentials(req, { allow: ["user"] });
|
|
449
460
|
const userInfo = await userInfoSvc.getUserInfo(credentials);
|
|
450
461
|
let userGroupsRefs = await getUserGroups(userInfo);
|
|
451
|
-
loggerSvc.info(`Checking reqScopes '${req.query["scopes"]}' scopes
|
|
462
|
+
loggerSvc.info(`Checking reqScopes '${req.query["scopes"]}' scopes for working with pod: '${req.body.metadata.namespace + "/" + req.body.metadata.name}' for user '${userInfo.userEntityRef}'`);
|
|
452
463
|
let foundClusters = await getValidClusters(req.body.metadata.name);
|
|
453
|
-
for (
|
|
454
|
-
|
|
464
|
+
for (let reqScopeStr of reqScopes) {
|
|
465
|
+
let reqScope = reqScopeStr;
|
|
455
466
|
await addAccessKeys(reqChannel, reqScope, foundClusters, req.body.metadata.name, userInfo.userEntityRef, userGroupsRefs);
|
|
467
|
+
if (reqScope === kwirthCommon.InstanceConfigScopeEnum.STREAM) {
|
|
468
|
+
for (let cluster of foundClusters) {
|
|
469
|
+
let ak = cluster.accessKeys.get(kwirthCommon.InstanceConfigScopeEnum.STREAM);
|
|
470
|
+
if (ak) {
|
|
471
|
+
let url = cluster.url + "/metrics";
|
|
472
|
+
let auth = "Bearer " + kwirthCommon.accessKeySerialize(ak);
|
|
473
|
+
console.log("urlauth", url, auth);
|
|
474
|
+
let fetchResp = await fetch(url, { headers: { "Authorization": auth } });
|
|
475
|
+
try {
|
|
476
|
+
let data = await fetchResp.json();
|
|
477
|
+
cluster.metrics = data;
|
|
478
|
+
} catch (err) {
|
|
479
|
+
console.log("err");
|
|
480
|
+
}
|
|
481
|
+
} else {
|
|
482
|
+
console.log("noak");
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
456
486
|
}
|
|
457
|
-
for (
|
|
487
|
+
for (let c of foundClusters) {
|
|
458
488
|
c.accessKeys = JSON.stringify(Array.from(c.accessKeys.entries()));
|
|
459
489
|
}
|
|
460
490
|
res.status(200).send(foundClusters);
|
|
@@ -469,7 +499,7 @@ async function createRouter(options) {
|
|
|
469
499
|
}
|
|
470
500
|
|
|
471
501
|
const kwirthPlugin = backendPluginApi.createBackendPlugin({
|
|
472
|
-
pluginId: "
|
|
502
|
+
pluginId: "kwirth",
|
|
473
503
|
register(env) {
|
|
474
504
|
env.registerInit({
|
|
475
505
|
deps: {
|
package/dist/index.cjs.js.map
CHANGED
|
@@ -1 +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\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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"}
|
|
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.4.11'\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 { ClusterTypeEnum, InstanceMessageChannelEnum, 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 channelConfig name of the Kwirth channel to read ('metrics', 'log'...)\r\n * @param logger Logger service\r\n */\r\nconst loadNamespacePermissions = (channelConfig:Config, logger:LoggerService):KwirthNamespacePermissions[] => {\r\n var namespacePermissions:KwirthNamespacePermissions[] = []\r\n if (channelConfig.has('namespacePermissions')) {\r\n logger.info(` Namespace permisson evaluation will be performed.`)\r\n var permNamespaces= (channelConfig.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 Kwirth channel\r\n * @param channelConfig 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 = (channelConfig:Config, logger:LoggerService):KwirthPodPermissions[] => {\r\n var clusterPodPermissions:KwirthPodPermissions[]=[]\r\n if (channelConfig.has('podPermissions')) {\r\n logger.info(` Pod permisson evaluation will be performed.`)\r\n var namespaceList=channelConfig.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 kwirth channel (like log, metrics...)\r\n * @param channel name of the channel\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 KwirthClusterData object to add channel permissions\r\n */\r\nconst addChannelPermissions = (channel: string, logger:LoggerService, cluster:Config, kdata:KwirthClusterData) => {\r\n let keyname = 'kwirth' + channel\r\n\r\n let keyCamelName = 'kwirth' + channel[0].toUpperCase() + channel.substring(1)\r\n if (cluster.has(keyCamelName)) keyname = keyCamelName\r\n\r\n if (cluster.has(keyname)) {\r\n logger.info(`Load permissions for channel ${channel} (config: ${keyname}).`)\r\n var configChannel=cluster.getConfig(keyname);\r\n if (configChannel.has('namespacePermissions')) {\r\n logger.info(` Loading namespace permissions.`)\r\n kdata.namespacePermissions.set(channel, loadNamespacePermissions(configChannel, logger))\r\n }\r\n else {\r\n logger.info(` No namespace permissions.`)\r\n kdata.namespacePermissions.set(channel, [])\r\n }\r\n if (configChannel.has('podPermissions')) {\r\n logger.info(` Loading pod permissions.`)\r\n kdata.podPermissions.set(channel, loadPodPermissions(configChannel, 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 clusterType: ClusterTypeEnum.KUBERNETES,\r\n metricsInterval: 0,\r\n channels: []\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/info endpoint returns JSON:\r\n {\r\n \"clusterName\": \"inCluster\",\r\n \"namespace\": \"default\",\r\n \"deployment\": \"kwirth\",\r\n \"inCluster\": true,\r\n \"version\": \"0.4.11\",\r\n \"lastVersion\": \"0.4.11\",\r\n \"clusterType\": \"kubernetes\",\r\n \"metricsInterval\": 60,\r\n \"channels\": [\r\n {\r\n \"id\": \"log\",\r\n \"routable\": false,\r\n \"pauseable\": true,\r\n \"modifyable\": false,\r\n \"reconnectable\": true,\r\n \"sources\": [\r\n \"docker\",\r\n \"kubernetes\"\r\n ],\r\n \"metrics\": false\r\n },\r\n {\r\n \"id\": \"alert\",\r\n \"routable\": false,\r\n \"pauseable\": true,\r\n \"modifyable\": false,\r\n \"reconnectable\": true,\r\n \"sources\": [\r\n \"docker\",\r\n \"kubernetes\"\r\n ],\r\n \"metrics\": false\r\n },\r\n {\r\n \"id\": \"metrics\",\r\n \"routable\": false,\r\n \"pauseable\": true,\r\n \"modifyable\": true,\r\n \"reconnectable\": true,\r\n \"sources\": [\r\n \"kubernetes\"\r\n ],\r\n \"metrics\": true\r\n },\r\n {\r\n \"id\": \"ops\",\r\n \"routable\": true,\r\n \"pauseable\": false,\r\n \"modifyable\": false,\r\n \"reconnectable\": false,\r\n \"sources\": [\r\n \"kubernetes\"\r\n ],\r\n \"metrics\": false\r\n },\r\n {\r\n \"id\": \"trivy\",\r\n \"routable\": false,\r\n \"pauseable\": false,\r\n \"modifyable\": false,\r\n \"reconnectable\": false,\r\n \"sources\": [\r\n \"kubernetes\"\r\n ],\r\n \"metrics\": false\r\n },\r\n {\r\n \"id\": \"echo\",\r\n \"routable\": false,\r\n \"pauseable\": true,\r\n \"modifyable\": false,\r\n \"reconnectable\": true,\r\n \"metrics\": false,\r\n \"sources\": [\r\n \"kubernetes\",\r\n \"docker\"\r\n ]\r\n }\r\n ]\r\n } \r\n */\r\n var response = await fetch (kwirthClusterData.kwirthHome+'/config/info')\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 clusterType: ClusterTypeEnum.KUBERNETES,\r\n metricsInterval: 0,\r\n channels: []\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 [ InstanceMessageChannelEnum.LOG, \r\n InstanceMessageChannelEnum.ALERT,\r\n InstanceMessageChannelEnum.METRICS\r\n ].map (channel => addChannelPermissions(channel,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('Kwirth 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\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// Kwirth\r\nimport { ClusterValidPods, MetricDefinition, 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 { accessKeySerialize, 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 Kwirth 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(`Kwirth will not start, there is no 'clusterLocatorMethods' defined in app-config.`)\r\n throw new Error('Kwirth 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 let 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, Kwirth 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('Kwirth 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 let clusterList:ClusterValidPods[]=[]\r\n\r\n for (const clusterName of KwirthStaticData.clusterKwirthData.keys()) {\r\n let url = KwirthStaticData.clusterKwirthData.get(clusterName)?.kwirthHome as string\r\n let apiKeyStr = KwirthStaticData.clusterKwirthData.get(clusterName)?.kwirthApiKey\r\n let title = KwirthStaticData.clusterKwirthData.get(clusterName)?.title\r\n let queryUrl=url+`/managecluster/find?label=backstage.io%2fkubernetes-id&entity=${entityName}&type=pod&data=containers`\r\n try {\r\n let fetchResp = await fetch (queryUrl, {headers:{'Authorization':'Bearer '+apiKeyStr}})\r\n if (fetchResp.status===200) {\r\n let 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 let resources = reqPods.map(podData => `${reqScope}:${podData.namespace}::${podData.name}:`).join(';')\r\n\r\n let kwirthHome = KwirthStaticData.clusterKwirthData.get(cluster.name)?.kwirthHome as string\r\n let kwirthApiKey = KwirthStaticData.clusterKwirthData.get(cluster.name)?.kwirthApiKey\r\n let payload= {\r\n description: `Backstage API key for user ${userName}`,\r\n expire: Date.now()+60*60*1000,\r\n days: 1,\r\n accessKey: {\r\n id: '',\r\n type:'bearer',\r\n resources\r\n }\r\n }\r\n\r\n let 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 let data = await fetchResp.json()\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 undefined\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 if (!reqScope) {\r\n loggerSvc.info(`Invalid scope requested: ${reqScope}`)\r\n return\r\n }\r\n let principal=userEntityRef.split(':')[1]\r\n let username=principal.split('/')[1]\r\n\r\n for (let foundCluster of foundClusters) {\r\n let podList:PodData[]=[]\r\n\r\n // for each pod we've found on the cluster we check all namespace permissions\r\n for (let podData of foundCluster.data) {\r\n // first we check if user is allowed to acccess namespace\r\n let 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 let clusterDef = KwirthStaticData.clusterKwirthData.get(foundCluster.name)\r\n let 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 let 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 if (accessKey) foundCluster.accessKeys.set(reqScope, accessKey)\r\n }\r\n else {\r\n console.log(`No pods on podList for '${reqScope}' on channel '${channel}'`)\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 let 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['channel']) {\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 for working with 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 contact 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 Kwirth config in app-config (namespace and pod permissions))\r\n for (let reqScopeStr of reqScopes) {\r\n let reqScope = reqScopeStr as InstanceConfigScopeEnum\r\n await addAccessKeys(reqChannel, reqScope, foundClusters, req.body.metadata.name, userInfo.userEntityRef, userGroupsRefs)\r\n if (reqScope === InstanceConfigScopeEnum.STREAM) {\r\n for (let cluster of foundClusters) {\r\n let ak = cluster.accessKeys.get(InstanceConfigScopeEnum.STREAM)\r\n if (ak) {\r\n let url=cluster.url+'/metrics'\r\n let auth = 'Bearer '+accessKeySerialize(ak)\r\n console.log('urlauth', url, auth)\r\n let fetchResp = await fetch (url, {headers:{'Authorization':auth}})\r\n try {\r\n let data = (await fetchResp.json()) as MetricDefinition[]\r\n cluster.metrics = data\r\n }\r\n catch (err) {\r\n console.log('err')\r\n }\r\n }\r\n else {\r\n console.log('noak')\r\n }\r\n }\r\n }\r\n }\r\n \r\n // *** we build a string of arrays from the Map (Maps cannot be serialized)\r\n for (let 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\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: 'kwirth',\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":["ClusterTypeEnum","versionGreatOrEqualThan","InstanceMessageChannelEnum","Router","express","catalogClient","CatalogClient","InstanceConfigScopeEnum","accessKeySerialize","createBackendPlugin","coreServices"],"mappings":";;;;;;;;;;;;;;;AAiBA,MAAM,OAAQ,GAAA,OAAA;AACd,MAAM,kBAAmB,GAAA,QAAA;AAEzB,MAAM,gBAAiB,CAAA;AAAA,EACnB,OAAc,iBAAoD,mBAAA,IAAI,GAAI,EAAA;AAC9E;;ACKA,MAAM,wBAAA,GAA2B,CAAC,aAAA,EAAsB,MAAsD,KAAA;AAC1G,EAAA,IAAI,uBAAoD,EAAC;AACzD,EAAI,IAAA,aAAA,CAAc,GAAI,CAAA,sBAAsB,CAAG,EAAA;AAC3C,IAAA,MAAA,CAAO,KAAK,CAAqD,mDAAA,CAAA,CAAA;AACjE,IAAI,IAAA,cAAA,GAAiB,aAAc,CAAA,sBAAA,CAAuB,sBAAsB,CAAA;AAChF,IAAA,KAAA,IAAS,MAAM,cAAgB,EAAA;AAC3B,MAAA,IAAI,SAAU,GAAA,EAAA,CAAG,IAAK,EAAA,CAAE,CAAC,CAAA;AACzB,MAAI,IAAA,YAAA,GAAa,EAAG,CAAA,cAAA,CAAe,SAAS,CAAA;AAC5C,MAAA,YAAA,GAAa,YAAa,CAAA,GAAA,CAAI,CAAK,CAAA,KAAA,CAAA,CAAE,aAAa,CAAA;AAClD,MAAA,oBAAA,CAAqB,IAAM,CAAA,EAAE,SAAW,EAAA,YAAA,EAAc,CAAA;AAAA;AAC1D,GAEC,MAAA;AACD,IAAA,MAAA,CAAO,KAAK,CAA8B,4BAAA,CAAA,CAAA;AAC1C,IAAA,oBAAA,GAAqB,EAAC;AAAA;AAE1B,EAAO,OAAA,oBAAA;AACX,CAAA;AAQA,MAAM,YAAA,GAAe,CAAC,MAAA,EAAe,QAAwC,KAAA;AACzE,EAAA,IAAI,QAA0B,EAAC;AAC/B,EAAA,KAAA,IAAS,IAAQ,IAAA,MAAA,CAAO,cAAe,CAAA,QAAQ,CAAG,EAAA;AAC9C,IAAA,IAAI,kBAAkB,IAAK,CAAA,sBAAA,CAAuB,MAAM,CAAA,IAAK,CAAC,IAAI,CAAA;AAClE,IAAA,IAAI,iBAAwB,EAAC;AAC7B,IAAA,KAAA,IAAS,QAAQ,eAAiB,EAAA;AAC9B,MAAA,cAAA,CAAe,IAAK,CAAA,IAAI,MAAO,CAAA,IAAI,CAAC,CAAA;AAAA;AAGxC,IAAA,IAAI,kBAAkB,IAAK,CAAA,sBAAA,CAAuB,MAAM,CAAA,IAAK,CAAC,IAAI,CAAA;AAClE,IAAA,IAAI,iBAAwB,EAAC;AAC7B,IAAA,KAAA,IAAS,QAAQ,eAAiB,EAAA;AAC9B,MAAA,cAAA,CAAe,IAAK,CAAA,IAAI,MAAO,CAAA,IAAI,CAAC,CAAA;AAAA;AAGxC,IAAA,IAAI,GAAsB,GAAA;AAAA,MACtB,IAAK,EAAA,cAAA;AAAA,MACL,IAAK,EAAA;AAAA,KACT;AACA,IAAA,KAAA,CAAM,KAAK,GAAG,CAAA;AAAA;AAElB,EAAO,OAAA,KAAA;AACX,CAAA;AAQA,MAAM,kBAAA,GAAqB,CAAC,aAAA,EAAsB,MAAgD,KAAA;AAC9F,EAAA,IAAI,wBAA6C,EAAC;AAClD,EAAI,IAAA,aAAA,CAAc,GAAI,CAAA,gBAAgB,CAAG,EAAA;AACrC,IAAA,MAAA,CAAO,KAAK,CAA+C,6CAAA,CAAA,CAAA;AAC3D,IAAI,IAAA,aAAA,GAAc,aAAc,CAAA,cAAA,CAAe,gBAAgB,CAAA;AAC/D,IAAA,KAAA,IAAS,MAAM,aAAe,EAAA;AAC1B,MAAA,IAAI,aAAc,GAAA,EAAA,CAAG,IAAK,EAAA,CAAE,CAAC,CAAA;AAC7B,MAAI,IAAA,cAAA,GAAoC,EAAE,SAAA,EAAU,aAAc,EAAA;AAElE,MAAA,IAAI,GAAG,SAAU,CAAA,aAAa,CAAE,CAAA,GAAA,CAAI,OAAO,CAAG,EAAA;AAC1C,QAAA,cAAA,CAAe,QAAM,YAAa,CAAA,EAAA,CAAG,SAAU,CAAA,aAAa,GAAG,OAAO,CAAA;AACtE,QAAA,IAAI,EAAG,CAAA,SAAA,CAAU,aAAa,CAAA,CAAE,IAAI,QAAQ,CAAA,EAAkB,cAAA,CAAA,MAAA,GAAO,YAAa,CAAA,EAAA,CAAG,SAAU,CAAA,aAAa,GAAG,QAAQ,CAAA;AACvH,QAAA,IAAI,EAAG,CAAA,SAAA,CAAU,aAAa,CAAA,CAAE,IAAI,MAAM,CAAA,EAAkB,cAAA,CAAA,IAAA,GAAK,YAAa,CAAA,EAAA,CAAG,SAAU,CAAA,aAAa,GAAG,MAAM,CAAA;AACjH,QAAA,IAAI,EAAG,CAAA,SAAA,CAAU,aAAa,CAAA,CAAE,IAAI,QAAQ,CAAA,EAAkB,cAAA,CAAA,MAAA,GAAO,YAAa,CAAA,EAAA,CAAG,SAAU,CAAA,aAAa,GAAG,QAAQ,CAAA;AAAA,OAEtH,MAAA;AACD,QAAA,cAAA,CAAe,QAAM,EAAC;AACtB,QAAA,cAAA,CAAe,MAAM,IAAK,CAAA;AAAA,UACtB,IAAM,EAAA,CAAC,IAAI,MAAA,CAAO,IAAI,CAAC,CAAA;AAAA,UACvB,IAAM,EAAA,CAAC,IAAI,MAAA,CAAO,IAAI,CAAC;AAAA,SAC1B,CAAA;AAAA;AAEL,MAAA,qBAAA,CAAsB,KAAK,cAAc,CAAA;AAAA;AAC7C,GAEC,MAAA;AACD,IAAA,MAAA,CAAO,KAAK,CAAuC,qCAAA,CAAA,CAAA;AAAA;AAEvD,EAAO,OAAA,qBAAA;AACX,CAAA;AASA,MAAM,qBAAwB,GAAA,CAAC,OAAiB,EAAA,MAAA,EAAsB,SAAgB,KAA4B,KAAA;AAC9G,EAAA,IAAI,UAAU,QAAW,GAAA,OAAA;AAEzB,EAAI,IAAA,YAAA,GAAe,WAAW,OAAQ,CAAA,CAAC,EAAE,WAAY,EAAA,GAAI,OAAQ,CAAA,SAAA,CAAU,CAAC,CAAA;AAC5E,EAAA,IAAI,OAAQ,CAAA,GAAA,CAAI,YAAY,CAAA,EAAa,OAAA,GAAA,YAAA;AAEzC,EAAI,IAAA,OAAA,CAAQ,GAAI,CAAA,OAAO,CAAG,EAAA;AACtB,IAAA,MAAA,CAAO,IAAK,CAAA,CAAA,6BAAA,EAAgC,OAAO,CAAA,UAAA,EAAa,OAAO,CAAI,EAAA,CAAA,CAAA;AAC3E,IAAI,IAAA,aAAA,GAAc,OAAQ,CAAA,SAAA,CAAU,OAAO,CAAA;AAC3C,IAAI,IAAA,aAAA,CAAc,GAAI,CAAA,sBAAsB,CAAG,EAAA;AAC3C,MAAA,MAAA,CAAO,KAAK,CAAkC,gCAAA,CAAA,CAAA;AAC9C,MAAA,KAAA,CAAM,qBAAqB,GAAI,CAAA,OAAA,EAAS,wBAAyB,CAAA,aAAA,EAAe,MAAM,CAAC,CAAA;AAAA,KAEtF,MAAA;AACD,MAAA,MAAA,CAAO,KAAK,CAA6B,2BAAA,CAAA,CAAA;AACzC,MAAA,KAAA,CAAM,oBAAqB,CAAA,GAAA,CAAI,OAAS,EAAA,EAAE,CAAA;AAAA;AAE9C,IAAI,IAAA,aAAA,CAAc,GAAI,CAAA,gBAAgB,CAAG,EAAA;AACrC,MAAA,MAAA,CAAO,KAAK,CAA4B,0BAAA,CAAA,CAAA;AACxC,MAAA,KAAA,CAAM,eAAe,GAAI,CAAA,OAAA,EAAS,kBAAmB,CAAA,aAAA,EAAe,MAAM,CAAC,CAAA;AAAA,KAE1E,MAAA;AACD,MAAA,MAAA,CAAO,KAAK,CAAuB,qBAAA,CAAA,CAAA;AACnC,MAAA,KAAA,CAAM,cAAe,CAAA,GAAA,CAAI,OAAS,EAAA,EAAE,CAAA;AAAA;AACxC,GAEC,MAAA;AACD,IAAO,MAAA,CAAA,IAAA,CAAK,WAAW,OAAQ,CAAA,SAAA,CAAU,MAAM,CAAC,CAAA,uBAAA,EAA0B,OAAO,CAAiB,eAAA,CAAA,CAAA;AAClG,IAAA,KAAA,CAAM,oBAAqB,CAAA,GAAA,CAAI,OAAQ,EAAA,EAAE,CAAA;AACzC,IAAA,KAAA,CAAM,cAAe,CAAA,GAAA,CAAI,OAAQ,EAAA,EAAE,CAAA;AAAA;AAE3C,CAAA;AAOA,MAAM,YAAA,GAAe,OAAO,MAAA,EAAsB,MAA6B,KAAA;AAC3E,EAAA,gBAAA,CAAiB,kBAAkB,KAAM,EAAA;AAEzC,EAAI,IAAA,eAAA,GAAgB,MAAO,CAAA,cAAA,CAAe,kCAAkC,CAAA;AAC5E,EAAA,KAAA,IAAS,UAAU,eAAiB,EAAA;AAElC,IAAI,IAAA,QAAA,GAAU,MAAO,CAAA,cAAA,CAAe,UAAU,CAAA;AAC9C,IAAA,KAAA,IAAS,WAAW,QAAU,EAAA;AAE5B,MAAI,IAAA,IAAA,GAAK,OAAQ,CAAA,SAAA,CAAU,MAAM,CAAA;AACjC,MAAA,IAAI,QAAQ,GAAI,CAAA,YAAY,KAAK,OAAQ,CAAA,GAAA,CAAI,cAAc,CAAG,EAAA;AAC1D,QAAI,IAAA,UAAA,GAAoB,OAAQ,CAAA,iBAAA,CAAkB,YAAY,CAAA;AAC9D,QAAI,IAAA,YAAA,GAAsB,OAAQ,CAAA,iBAAA,CAAkB,cAAc,CAAA;AAClE,QAAI,IAAA,KAAA,GAAgB,QAAQ,GAAI,CAAA,OAAO,IAAE,OAAQ,CAAA,SAAA,CAAU,OAAO,CAAE,GAAA,SAAA;AACpE,QAAA,IAAI,iBAAoC,GAAA;AAAA,UACpC,IAAA;AAAA,UACA,UAAA;AAAA,UACA,YAAA;AAAA,UACA,UAAY,EAAA;AAAA,YACR,OAAS,EAAA,EAAA;AAAA,YACT,WAAa,EAAA,EAAA;AAAA,YACb,SAAW,EAAA,KAAA;AAAA,YACX,SAAW,EAAA,EAAA;AAAA,YACX,UAAY,EAAA,EAAA;AAAA,YACZ,WAAa,EAAA,EAAA;AAAA,YACb,aAAaA,4BAAgB,CAAA,UAAA;AAAA,YAC7B,eAAiB,EAAA,CAAA;AAAA,YACjB,UAAU;AAAC,WACf;AAAA,UACA,KAAA;AAAA,UACA,oBAAA,sBAA0B,GAAI,EAAA;AAAA,UAC9B,cAAA,sBAAoB,GAAI,EAAA;AAAA,UACxB,OAAS,EAAA;AAAA,SACb;AAEA,QAAA,MAAA,CAAO,KAAK,CAAc,WAAA,EAAA,IAAI,CAAkB,eAAA,EAAA,iBAAA,CAAkB,UAAU,CAAyB,uBAAA,CAAA,CAAA;AACrG,QAAA,IAAI,aAAgB,GAAA,KAAA;AACpB,QAAI,IAAA;AAqFA,UAAA,IAAI,QAAW,GAAA,MAAM,KAAO,CAAA,iBAAA,CAAkB,aAAW,cAAc,CAAA;AACvE,UAAI,IAAA;AACA,YAAI,IAAA,IAAA,GAAO,MAAM,QAAA,CAAS,IAAK,EAAA;AAC/B,YAAI,IAAA;AACA,cAAI,IAAA,UAAA,GAAa,IAAK,CAAA,KAAA,CAAM,IAAI,CAAA;AAChC,cAAO,MAAA,CAAA,IAAA,CAAK,2BAA2B,iBAAkB,CAAA,IAAI,MAAM,IAAK,CAAA,SAAA,CAAU,UAAU,CAAC,CAAE,CAAA,CAAA;AAC/F,cAAA,iBAAA,CAAkB,UAAW,GAAA,UAAA;AAC7B,cAAA,IAAIC,oCAAwB,CAAA,UAAA,CAAW,OAAS,EAAA,kBAAkB,CAAG,EAAA;AACjE,gBAAgB,aAAA,GAAA,IAAA;AAAA,eAEf,MAAA;AACD,gBAAO,MAAA,CAAA,KAAA,CAAM,CAA0C,uCAAA,EAAA,IAAI,CAAM,GAAA,EAAA,KAAK,MAAM,UAAW,CAAA,OAAO,CAAqB,kBAAA,EAAA,kBAAkB,CAAE,CAAA,CAAA;AAAA;AAC3I,qBAEG,GAAK,EAAA;AACR,cAAA,MAAA,CAAO,MAAM,CAAqB,kBAAA,EAAA,iBAAA,CAAkB,IAAI,CAAA,kBAAA,EAAqB,GAAG,CAAE,CAAA,CAAA;AAClF,cAAA,MAAA,CAAO,KAAK,mBAAmB,CAAA;AAC/B,cAAA,MAAA,CAAO,KAAK,IAAI,CAAA;AAChB,cAAA,iBAAA,CAAkB,UAAa,GAAA;AAAA,gBAC3B,OAAQ,EAAA,OAAA;AAAA,gBACR,WAAY,EAAA,SAAA;AAAA,gBACZ,SAAU,EAAA,KAAA;AAAA,gBACV,SAAU,EAAA,SAAA;AAAA,gBACV,UAAW,EAAA,SAAA;AAAA,gBACX,WAAY,EAAA,OAAA;AAAA,gBACZ,aAAaD,4BAAgB,CAAA,UAAA;AAAA,gBAC7B,eAAiB,EAAA,CAAA;AAAA,gBACjB,UAAU;AAAC,eACf;AAAA;AACJ,mBAEG,GAAK,EAAA;AACR,YAAA,MAAA,CAAO,KAAK,CAAgD,6CAAA,EAAA,iBAAA,CAAkB,IAAI,CAAA,GAAA,EAAM,GAAG,CAAE,CAAA,CAAA;AAAA;AACjG,iBAEG,GAAK,EAAA;AACR,UAAO,MAAA,CAAA,IAAA,CAAK,CAAwB,qBAAA,EAAA,GAAG,CAAG,CAAA,CAAA,CAAA;AAC1C,UAAA,MAAA,CAAO,KAAK,CAAoB,iBAAA,EAAA,iBAAA,CAAkB,UAAU,CAAiB,cAAA,EAAA,iBAAA,CAAkB,IAAI,CAAiC,+BAAA,CAAA,CAAA;AAAA;AAGxI,QAAA,IAAI,aAAe,EAAA;AACf,UAAA;AAAA,YAAEE,uCAA2B,CAAA,GAAA;AAAA,YAC3BA,uCAA2B,CAAA,KAAA;AAAA,YAC3BA,uCAA2B,CAAA;AAAA,WAC7B,CAAE,IAAK,CAAW,OAAA,KAAA,qBAAA,CAAsB,SAAQ,MAAQ,EAAA,OAAA,EAAS,iBAAiB,CAAC,CAAA;AACnF,UAAiB,gBAAA,CAAA,iBAAA,CAAkB,GAAI,CAAA,IAAA,EAAM,iBAAiB,CAAA;AAAA,SAE7D,MAAA;AACD,UAAO,MAAA,CAAA,IAAA,CAAK,CAAW,QAAA,EAAA,IAAI,CAAmB,iBAAA,CAAA,CAAA;AAAA;AAClD,OAEC,MAAA;AACD,QAAO,MAAA,CAAA,IAAA,CAAK,CAAW,QAAA,EAAA,IAAI,CAAuE,qEAAA,CAAA,CAAA;AAAA;AACtG;AACF;AAGF,EAAA,MAAA,CAAO,KAAK,+DAA+D,CAAA;AAC3E,EAAA,KAAA,IAAS,CAAK,IAAA,gBAAA,CAAiB,iBAAkB,CAAA,IAAA,EAAQ,EAAA;AACrD,IAAO,MAAA,CAAA,IAAA,CAAM,OAAK,CAAC,CAAA;AAAA;AAEvB,EAAA,KAAA,IAAS,CAAK,IAAA,gBAAA,CAAiB,iBAAkB,CAAA,IAAA,EAAQ,EAAA;AACrD,IAAA,OAAA,CAAQ,GAAI,CAAA,gBAAA,CAAiB,iBAAkB,CAAA,GAAA,CAAI,CAAC,CAAC,CAAA;AAAA;AAG7D,CAAA;;ACjUA,MAAM,uBAAuB,CAAC,OAAA,EAAgB,OAA0B,EAAA,OAAA,EAAiB,eAAsB,UAAkC,KAAA;AAC7I,EAAA,IAAI,kBAAmB,GAAA,KAAA;AACvB,EAAA,IAAI,uBAAuB,gBAAiB,CAAA,iBAAA,CAAkB,GAAI,CAAA,OAAA,CAAQ,IAAI,CAAG,EAAA,oBAAA;AAEjF,EAAI,IAAA,oBAAA,EAAsB,GAAI,CAAA,OAAO,CAAG,EAAA;AACpC,IAAI,IAAA,IAAA,GAAO,oBAAsB,EAAA,GAAA,CAAI,OAAO,CAAA,CAAG,KAAK,CAAM,EAAA,KAAA,EAAA,CAAG,SAAY,KAAA,OAAA,CAAQ,SAAS,CAAA;AAC1F,IAAA,IAAI,IAAM,EAAA;AACN,MAAA,IAAI,KAAK,YAAa,CAAA,QAAA,CAAS,aAAc,CAAA,WAAA,EAAa,CAAG,EAAA;AAEzD,QAAmB,kBAAA,GAAA,IAAA;AAAA,OAElB,MAAA;AACD,QAAI,IAAA,WAAA,GAAY,KAAK,YAAa,CAAA,IAAA,CAAK,iBAAe,UAAW,CAAA,QAAA,CAAS,WAAW,CAAC,CAAA;AACtF,QAAA,IAAI,WAAa,EAAA;AAEb,UAAmB,kBAAA,GAAA,IAAA;AAAA;AACvB;AACJ,KAEC,MAAA;AAED,MAAmB,kBAAA,GAAA,IAAA;AAAA;AACvB,GAEC,MAAA;AACD,IAAQ,OAAA,CAAA,GAAA,CAAI,CAAoB,iBAAA,EAAA,OAAO,CAAE,CAAA,CAAA;AAAA;AAE7C,EAAO,OAAA,kBAAA;AACX,CAAA;AAEA,MAAM,sBAAyB,GAAA,CAAC,GAAuB,EAAA,UAAA,EAAmB,eAAsB,UAAkC,KAAA;AAC9H,EAAA,IAAI,QAAiB,GAAA,KAAA;AAErB,EAAS,KAAA,IAAA,YAAA,IAAgB,IAAI,IAAM,EAAA;AAC/B,IAAI,IAAA,YAAA,CAAa,IAAK,CAAA,UAAU,CAAG,EAAA;AAC/B,MAAS,KAAA,IAAA,QAAA,IAAY,IAAI,IAAM,EAAA;AAE3B,QAAA,QAAA,GAAS,QAAS,CAAA,IAAA,CAAK,aAAc,CAAA,WAAA,EAAa,CAAA;AAClD,QAAA,IAAI,QAAU,EAAA;AACV,UAAA;AAAA,SAEC,MAAA;AAED,UAAA,QAAA,GAAW,WAAW,IAAK,CAAA,CAAA,CAAA,KAAK,QAAS,CAAA,IAAA,CAAK,CAAC,CAAC,CAAA;AAChD,UAAA,IAAI,QAAU,EAAA;AACV,YAAA;AAAA;AACJ;AACJ;AACJ;AAIJ,IAAA,IAAI,QAAU,EAAA;AAAA;AAElB,EAAO,OAAA,QAAA;AACX,CAAA;AAcA,MAAM,mBAAA,GAAsB,CAAC,OAAA,EAAgB,OAA8B,KAAA;AACvE,EAAA,IAAI,OAAQ,CAAA,cAAA,CAAe,GAAI,CAAA,OAAO,CAAG,EAAA;AACrC,IAAO,OAAA,OAAA,CAAQ,cAAe,CAAA,GAAA,CAAI,OAAO,CAAA;AAAA,GAExC,MAAA;AACD,IAAQ,OAAA,CAAA,GAAA,CAAI,CAAmB,gBAAA,EAAA,OAAO,CAAqB,mBAAA,CAAA,CAAA;AAC3D,IAAO,OAAA,MAAA;AAAA;AAEf,CAAA;AAeA,MAAM,iBAAiB,CAAC,MAAA,EAAgB,gBAAyC,EAAA,UAAA,EAAmB,eAAsB,UAAgC,KAAA;AAItJ,EAAS,KAAA,IAAA,aAAA,IAAiB,iBAAiB,MAAO,CAAA,CAAA,EAAA,KAAM,GAAG,SAAY,KAAA,MAAA,CAAO,SAAS,CAAG,EAAA;AACtF,IAAA,IAAI,cAAc,KAAO,EAAA;AAGrB,MAAA,IAAI,YAAa,GAAA,KAAA;AACjB,MAAA,IAAI,aAAc,GAAA,KAAA;AAElB,MAAS,KAAA,IAAA,GAAA,IAAO,cAAc,KAAO,EAAA;AACjC,QAAA,YAAA,GAAe,sBAAuB,CAAA,GAAA,EAAK,UAAY,EAAA,aAAA,EAAe,UAAU,CAAA;AAAA;AAEpF,MAAA,IAAI,YAAc,EAAA;AACd,QAAA,IAAI,cAAc,MAAQ,EAAA;AAEtB,UAAS,KAAA,IAAA,GAAA,IAAO,cAAc,MAAQ,EAAA;AAElC,YAAA,aAAA,GAAgB,sBAAuB,CAAA,GAAA,EAAK,UAAY,EAAA,aAAA,EAAe,UAAU,CAAA;AAEjF,YAAA,IAAI,aAAe,EAAA;AACf,cAAA;AAAA;AACJ;AACJ;AAGJ;AAGJ,MAAI,IAAA,YAAA,IAAgB,CAAC,aAAe,EAAA;AAEhC,QAAA,IAAI,cAAc,IAAM,EAAA;AACpB,UAAA,IAAI,WAAY,GAAA,KAAA;AAChB,UAAA,IAAI,aAAc,GAAA,KAAA;AAClB,UAAS,KAAA,IAAA,GAAA,IAAO,cAAc,IAAM,EAAA;AAEhC,YAAA,WAAA,GAAc,sBAAuB,CAAA,GAAA,EAAK,UAAY,EAAA,aAAA,EAAe,UAAU,CAAA;AAC/E,YAAA,IAAI,WAAa,EAAA;AACb,cAAA;AAAA;AACJ;AAEJ,UAAI,IAAA,WAAA,IAAe,cAAc,MAAQ,EAAA;AACrC,YAAS,KAAA,IAAA,GAAA,IAAO,cAAc,MAAQ,EAAA;AAElC,cAAA,aAAA,GAAgB,sBAAuB,CAAA,GAAA,EAAK,UAAY,EAAA,aAAA,EAAe,UAAU,CAAA;AACjF,cAAA,IAAI,aAAe,EAAA;AACf,gBAAA;AAAA;AACJ;AACJ;AAEJ,UAAI,IAAA,CAAC,WAAgB,IAAA,WAAA,IAAe,aAAgB,EAAA;AAChD,YAAO,OAAA,IAAA;AAAA;AACX,SAEC,MAAA;AACD,UAAO,OAAA,IAAA;AAAA;AACX;AAIJ,KAEC,MAAA;AAGD,MAAO,OAAA,IAAA;AAAA;AACX;AAGJ,EAAO,OAAA,KAAA;AACX,CAAA;;AC5IA,eAAe,aAAa,OAAwD,EAAA;AAChF,EAAA,MAAM,EAAE,SAAW,EAAA,SAAA,EAAW,aAAa,OAAS,EAAA,WAAA,EAAa,cAAiB,GAAA,OAAA;AAElF,EAAA,SAAA,CAAU,KAAK,uBAAuB,CAAA;AAEtC,EAAA,IAAI,CAAC,SAAA,CAAU,GAAI,CAAA,kCAAkC,CAAG,EAAA;AACpD,IAAA,SAAA,CAAU,MAAM,CAAmF,iFAAA,CAAA,CAAA;AACnG,IAAM,MAAA,IAAI,MAAM,uCAAuC,CAAA;AAAA;AAG3D,EAAI,IAAA;AACA,IAAA,YAAA,CAAa,WAAW,SAAS,CAAA;AAAA,WAE9B,GAAK,EAAA;AACR,IAAI,IAAA,GAAA,GAAI,iDAAiD,GAAG,CAAA,CAAA;AAC5D,IAAA,SAAA,CAAU,MAAM,GAAG,CAAA;AACnB,IAAM,MAAA,IAAI,MAAM,GAAG,CAAA;AAAA;AAIvB,EAAA,IAAI,UAAU,SAAW,EAAA;AACrB,IAAA,SAAA,CAAU,UAAW,MAAM;AACvB,MAAI,IAAA;AACA,QAAA,SAAA,CAAU,KAAK,2DAA2D,CAAA;AAC1E,QAAA,YAAA,CAAa,WAAW,SAAS,CAAA;AAAA,eAE/B,GAAK,EAAA;AACP,QAAU,SAAA,CAAA,KAAA,CAAM,CAA8C,2CAAA,EAAA,GAAG,CAAE,CAAA,CAAA;AAAA;AACvE,KACH,CAAA;AAAA,GAEA,MAAA;AACD,IAAA,SAAA,CAAU,KAAK,4CAA4C,CAAA;AAAA;AAG/D,EAAA,MAAM,SAASC,uBAAO,EAAA;AAEtB,EAAO,MAAA,CAAA,GAAA,CAAIC,wBAAQ,CAAA,IAAA,EAAM,CAAA;AAGzB,EAAM,MAAA,kBAAA,GAAqB,CAAC,KAA6B,KAAA;AACrD,IAAO,OAAA;AAAA,MACH,KAAA,EAAO,OAAO,KAAA,EAAO,IAAS,KAAA;AAC1B,QAAA,IAAA,GAAO,QAAQ,EAAC;AAChB,QAAA,IAAA,CAAK,OAAU,GAAA;AAAA,UACX,GAAG,IAAK,CAAA,OAAA;AAAA,UACR,aAAA,EAAe,UAAU,KAAK,CAAA;AAAA,SAClC;AACA,QAAO,OAAA,KAAA,CAAM,OAAO,IAAI,CAAA;AAAA;AAC5B,KACJ;AAAA,GACJ;AAOA,EAAM,MAAA,gBAAA,GAAmB,OAAO,UAAoD,KAAA;AAChF,IAAA,IAAI,cAA+B,EAAC;AAEpC,IAAA,KAAA,MAAW,WAAe,IAAA,gBAAA,CAAiB,iBAAkB,CAAA,IAAA,EAAQ,EAAA;AACjE,MAAA,IAAI,GAAM,GAAA,gBAAA,CAAiB,iBAAkB,CAAA,GAAA,CAAI,WAAW,CAAG,EAAA,UAAA;AAC/D,MAAA,IAAI,SAAY,GAAA,gBAAA,CAAiB,iBAAkB,CAAA,GAAA,CAAI,WAAW,CAAG,EAAA,YAAA;AACrE,MAAA,IAAI,KAAQ,GAAA,gBAAA,CAAiB,iBAAkB,CAAA,GAAA,CAAI,WAAW,CAAG,EAAA,KAAA;AACjE,MAAI,IAAA,QAAA,GAAS,GAAI,GAAA,CAAA,8DAAA,EAAiE,UAAU,CAAA,yBAAA,CAAA;AAC5F,MAAI,IAAA;AACA,QAAI,IAAA,SAAA,GAAY,MAAM,KAAA,CAAO,QAAU,EAAA,EAAC,OAAQ,EAAA,EAAC,eAAgB,EAAA,SAAA,GAAU,SAAS,EAAA,EAAE,CAAA;AACtF,QAAI,IAAA,SAAA,CAAU,WAAS,GAAK,EAAA;AACxB,UAAI,IAAA,QAAA,GAAS,MAAM,SAAA,CAAU,IAAK,EAAA;AAClC,UAAA,IAAI,QAAU,EAAA;AACV,YAAA,IAAI,OAA2B,GAAA;AAAA,cAC3B,IAAM,EAAA,WAAA;AAAA,cAAa,GAAA;AAAA,cAAK,KAAA;AAAA,cAAO,IAAM,EAAA,QAAA;AAAA,cAAU,UAAA,sBAAgB,GAAI;AAAA,aACvE;AACA,YAAA,WAAA,CAAY,KAAK,OAAO,CAAA;AAAA;AAC5B,SAEC,MAAA;AACD,UAAA,SAAA,CAAU,KAAK,CAAiC,8BAAA,EAAA,WAAW,CAAK,EAAA,EAAA,SAAA,CAAU,MAAM,CAAE,CAAA,CAAA;AAClF,UAAA,OAAA,CAAQ,GAAI,CAAA,MAAM,SAAU,CAAA,IAAA,EAAM,CAAA;AAClC,UAAA,WAAA,CAAY,IAAK,CAAA,EAAE,IAAM,EAAA,WAAA,EAAa,GAAK,EAAA,KAAA,EAAO,IAAK,EAAA,EAAI,EAAA,UAAA,kBAAe,IAAA,GAAA,IAAO,CAAA;AAAA;AACrF,eAGG,GAAK,EAAA;AACR,QAAA,SAAA,CAAU,KAAK,CAAyB,sBAAA,EAAA,WAAW,UAAU,QAAQ,CAAA,GAAA,EAAM,GAAG,CAAE,CAAA,CAAA;AAChF,QAAA,WAAA,CAAY,IAAK,CAAA,EAAE,IAAM,EAAA,WAAA,EAAa,GAAK,EAAA,KAAA,EAAO,IAAK,EAAA,EAAI,EAAA,UAAA,kBAAe,IAAA,GAAA,IAAO,CAAA;AAAA;AACrF;AAGJ,IAAO,OAAA,WAAA;AAAA,GACX;AAEA,EAAA,MAAM,eAAkB,GAAA,OAAO,QAAkC,EAAA,OAAA,EAA0B,SAAmB,QAAmC,KAAA;AAC7I,IAAA,IAAI,SAAY,GAAA,OAAA,CAAQ,GAAI,CAAA,CAAA,OAAA,KAAW,GAAG,QAAQ,CAAA,CAAA,EAAI,OAAQ,CAAA,SAAS,KAAK,OAAQ,CAAA,IAAI,CAAG,CAAA,CAAA,CAAA,CAAE,KAAK,GAAG,CAAA;AAErG,IAAA,IAAI,aAAa,gBAAiB,CAAA,iBAAA,CAAkB,GAAI,CAAA,OAAA,CAAQ,IAAI,CAAG,EAAA,UAAA;AACvE,IAAA,IAAI,eAAe,gBAAiB,CAAA,iBAAA,CAAkB,GAAI,CAAA,OAAA,CAAQ,IAAI,CAAG,EAAA,YAAA;AACzE,IAAA,IAAI,OAAS,GAAA;AAAA,MACT,WAAA,EAAa,8BAA8B,QAAQ,CAAA,CAAA;AAAA,MACnD,MAAQ,EAAA,IAAA,CAAK,GAAI,EAAA,GAAE,KAAG,EAAG,GAAA,GAAA;AAAA,MACzB,IAAM,EAAA,CAAA;AAAA,MACN,SAAW,EAAA;AAAA,QACP,EAAI,EAAA,EAAA;AAAA,QACJ,IAAK,EAAA,QAAA;AAAA,QACL;AAAA;AACJ,KACJ;AAEA,IAAI,IAAA,SAAA,GAAU,MAAM,KAAM,CAAA,UAAA,GAAW,QAAO,EAAC,MAAA,EAAO,QAAQ,IAAK,EAAA,IAAA,CAAK,UAAU,OAAO,CAAA,EAAG,SAAQ,EAAC,cAAA,EAAe,oBAAoB,aAAc,EAAA,SAAA,GAAU,YAAY,EAAA,EAAE,CAAA;AAC5K,IAAI,IAAA,SAAA,CAAU,WAAS,GAAK,EAAA;AACxB,MAAI,IAAA,IAAA,GAAO,MAAM,SAAA,CAAU,IAAK,EAAA;AAChC,MAAA,OAAO,IAAK,CAAA,SAAA;AAAA,KAEX,MAAA;AACD,MAAA,SAAA,CAAU,KAAK,CAAkD,+CAAA,EAAA,OAAA,CAAQ,IAAI,CAAK,EAAA,EAAA,SAAA,CAAU,MAAM,CAAE,CAAA,CAAA;AACpG,MAAO,OAAA,MAAA;AAAA;AACX,GACJ;AAEA,EAAA,MAAM,gBAAgB,OAAO,OAAA,EAAgB,UAAkC,aAAkC,EAAA,UAAA,EAAmB,eAAsB,UAAwB,KAAA;AAC9K,IAAA,IAAI,CAAC,QAAU,EAAA;AACX,MAAU,SAAA,CAAA,IAAA,CAAK,CAA4B,yBAAA,EAAA,QAAQ,CAAE,CAAA,CAAA;AACrD,MAAA;AAAA;AAEJ,IAAA,IAAI,SAAU,GAAA,aAAA,CAAc,KAAM,CAAA,GAAG,EAAE,CAAC,CAAA;AACxC,IAAA,IAAI,QAAS,GAAA,SAAA,CAAU,KAAM,CAAA,GAAG,EAAE,CAAC,CAAA;AAEnC,IAAA,KAAA,IAAS,gBAAgB,aAAe,EAAA;AACpC,MAAA,IAAI,UAAkB,EAAC;AAGvB,MAAS,KAAA,IAAA,OAAA,IAAW,aAAa,IAAM,EAAA;AAEnC,QAAA,IAAI,qBAAqB,oBAAqB,CAAA,OAAA,EAAS,YAAc,EAAA,OAAA,EAAS,eAAe,UAAU,CAAA;AAEvG,QAAA,IAAI,kBAAoB,EAAA;AAEpB,UAAA,IAAI,UAAa,GAAA,gBAAA,CAAiB,iBAAkB,CAAA,GAAA,CAAI,aAAa,IAAI,CAAA;AACzE,UAAI,IAAA,gBAAA,GAAmB,mBAAoB,CAAA,OAAA,EAAS,UAAW,CAAA;AAC/D,UAAA,IAAI,CAAC,gBAAkB,EAAA;AACnB,YAAU,SAAA,CAAA,IAAA,CAAK,CAAiC,8BAAA,EAAA,OAAO,CAAE,CAAA,CAAA;AACzD,YAAA;AAAA;AAEJ,UAAA,IAAI,sBAAsB,gBAAiB,CAAA,IAAA,CAAK,QAAM,EAAG,CAAA,SAAA,KAAY,QAAQ,SAAS,CAAA;AACtF,UAAI,IAAA,CAAC,uBAAuB,cAAe,CAAA,OAAA,EAAS,kBAAkB,UAAY,EAAA,aAAA,EAAe,UAAU,CAAG,EAAA;AAE1G,YAAA,OAAA,CAAQ,KAAK,OAAO,CAAA;AAAA;AAGxB;AAKJ;AAEJ,MAAI,IAAA,OAAA,CAAQ,SAAO,CAAG,EAAA;AAClB,QAAA,IAAI,YAAY,MAAM,eAAA,CAAgB,QAAU,EAAA,YAAA,EAAc,SAAS,QAAQ,CAAA;AAC/E,QAAA,IAAI,SAAW,EAAA,YAAA,CAAa,UAAW,CAAA,GAAA,CAAI,UAAU,SAAS,CAAA;AAAA,OAE7D,MAAA;AACD,QAAA,OAAA,CAAQ,GAAI,CAAA,CAAA,wBAAA,EAA2B,QAAQ,CAAA,cAAA,EAAiB,OAAO,CAAG,CAAA,CAAA,CAAA;AAAA;AAC9E;AACJ,GACJ;AAOA,EAAM,MAAA,aAAA,GAAgB,OAAO,QAAmD,KAAA;AAC5E,IAAA,MAAM,EAAE,KAAA,EAAU,GAAA,MAAM,QAAQ,qBAAsB,CAAA;AAAA,MAClD,UAAA,EAAY,MAAM,OAAA,CAAQ,wBAAyB,EAAA;AAAA,MACnD,cAAgB,EAAA;AAAA,KACnB,CAAA;AACD,IAAM,MAAAC,eAAA,GAAgB,IAAIC,2BAAc,CAAA;AAAA,MACpC,YAAc,EAAA,YAAA;AAAA,MACd,QAAA,EAAU,mBAAmB,KAAK;AAAA,KACrC,CAAA;AAED,IAAA,MAAM,MAAS,GAAA,MAAMD,eAAc,CAAA,cAAA,CAAe,SAAS,aAAa,CAAA;AACxE,IAAA,IAAI,iBAAwB,EAAC;AAE7B,IAAA,IAAI,MAAQ,EAAA,IAAA,CAAK,QAAU,EAAA,cAAA,GAAe,QAAQ,IAAK,CAAA,QAAA;AACvD,IAAO,OAAA,cAAA;AAAA,GACX;AAGA,EAAM,MAAA,cAAA,GAAiB,OAAO,IAAA,EAAU,GAAY,KAAA;AAChD,IAAA,GAAA,CAAI,OAAO,GAAG,CAAA,CAAE,KAAK,EAAE,OAAA,EAAQ,SAAS,CAAA;AAAA,GAC5C;AAGA,EAAM,MAAA,aAAA,GAAgB,OAAO,GAAA,EAAqB,GAAyB,KAAA;AACvE,IAAI,IAAA,CAAC,IAAI,KAAM,CAAA,QAAQ,KAAK,CAAC,GAAA,CAAI,KAAM,CAAA,SAAS,CAAG,EAAA;AAC/C,MAAA,GAAA,CAAI,MAAO,CAAA,GAAG,CAAE,CAAA,IAAA,CAAK,CAAqC,mCAAA,CAAA,CAAA;AAC1D,MAAA;AAAA;AAEJ,IAAI,IAAA,SAAA,GAAa,IAAI,KAAM,CAAA,QAAQ,EAAE,QAAS,EAAA,CAAG,MAAM,GAAG,CAAA;AAC1D,IAAA,IAAI,UAAa,GAAA,GAAA,CAAI,KAAM,CAAA,SAAS,GAAG,QAAS,EAAA;AAGhD,IAAM,MAAA,WAAA,GAAc,MAAM,WAAA,CAAY,WAAY,CAAA,GAAA,EAAK,EAAE,KAAO,EAAA,CAAC,MAAM,CAAA,EAAG,CAAA;AAC1E,IAAA,MAAM,QAAW,GAAA,MAAM,WAAY,CAAA,WAAA,CAAY,WAAW,CAAA;AAE1D,IAAI,IAAA,cAAA,GAAe,MAAM,aAAA,CAAc,QAAQ,CAAA;AAE/C,IAAA,SAAA,CAAU,KAAK,CAAuB,oBAAA,EAAA,GAAA,CAAI,MAAM,QAAQ,CAAC,mCAAmC,GAAI,CAAA,IAAA,CAAK,SAAS,SAAU,GAAA,GAAA,GAAI,IAAI,IAAK,CAAA,QAAA,CAAS,IAAI,CAAe,YAAA,EAAA,QAAA,CAAS,aAAa,CAAG,CAAA,CAAA,CAAA;AAI1L,IAAA,IAAI,gBAAmC,MAAM,gBAAA,CAAiB,GAAI,CAAA,IAAA,CAAK,SAAS,IAAI,CAAA;AAGpF,IAAA,KAAA,IAAS,eAAe,SAAW,EAAA;AAC/B,MAAA,IAAI,QAAW,GAAA,WAAA;AACf,MAAM,MAAA,aAAA,CAAc,UAAY,EAAA,QAAA,EAAU,aAAe,EAAA,GAAA,CAAI,KAAK,QAAS,CAAA,IAAA,EAAM,QAAS,CAAA,aAAA,EAAe,cAAc,CAAA;AACvH,MAAI,IAAA,QAAA,KAAaE,qCAAwB,MAAQ,EAAA;AAC7C,QAAA,KAAA,IAAS,WAAW,aAAe,EAAA;AAC/B,UAAA,IAAI,EAAK,GAAA,OAAA,CAAQ,UAAW,CAAA,GAAA,CAAIA,qCAAwB,MAAM,CAAA;AAC9D,UAAA,IAAI,EAAI,EAAA;AACJ,YAAI,IAAA,GAAA,GAAI,QAAQ,GAAI,GAAA,UAAA;AACpB,YAAI,IAAA,IAAA,GAAO,SAAU,GAAAC,+BAAA,CAAmB,EAAE,CAAA;AAC1C,YAAQ,OAAA,CAAA,GAAA,CAAI,SAAW,EAAA,GAAA,EAAK,IAAI,CAAA;AAChC,YAAI,IAAA,SAAA,GAAY,MAAM,KAAA,CAAO,GAAK,EAAA,EAAC,SAAQ,EAAC,eAAA,EAAgB,IAAI,EAAA,EAAE,CAAA;AAClE,YAAI,IAAA;AACA,cAAI,IAAA,IAAA,GAAQ,MAAM,SAAA,CAAU,IAAK,EAAA;AACjC,cAAA,OAAA,CAAQ,OAAU,GAAA,IAAA;AAAA,qBAEf,GAAK,EAAA;AACR,cAAA,OAAA,CAAQ,IAAI,KAAK,CAAA;AAAA;AACrB,WAEC,MAAA;AACD,YAAA,OAAA,CAAQ,IAAI,MAAM,CAAA;AAAA;AACtB;AACJ;AACJ;AAIJ,IAAA,KAAA,IAAS,KAAK,aAAe,EAAA;AACzB,MAAC,CAAA,CAAU,UAAa,GAAA,IAAA,CAAK,SAAU,CAAA,KAAA,CAAM,KAAK,CAAE,CAAA,UAAA,CAAW,OAAQ,EAAC,CAAC,CAAA;AAAA;AAE7E,IAAA,GAAA,CAAI,MAAO,CAAA,GAAG,CAAE,CAAA,IAAA,CAAK,aAAa,CAAA;AAAA,GACtC;AAEA,EAAA,MAAA,CAAO,KAAK,CAAC,SAAS,CAAG,EAAA,CAAC,KAAK,GAAQ,KAAA;AACnC,IAAA,aAAA,CAAc,KAAI,GAAG,CAAA;AAAA,GACxB,CAAA;AAED,EAAA,MAAA,CAAO,IAAI,CAAC,UAAU,CAAG,EAAA,CAAC,KAAK,GAAQ,KAAA;AACnC,IAAA,cAAA,CAAe,KAAI,GAAG,CAAA;AAAA,GACzB,CAAA;AAED,EAAO,OAAA,MAAA;AACX;;AC9RO,MAAM,eAAeC,oCAAoB,CAAA;AAAA,EAC5C,QAAU,EAAA,QAAA;AAAA,EACV,SAAS,GAAK,EAAA;AACV,IAAA,GAAA,CAAI,YAAa,CAAA;AAAA,MACb,IAAM,EAAA;AAAA,QACF,WAAWC,6BAAa,CAAA,SAAA;AAAA,QACxB,QAAQA,6BAAa,CAAA,UAAA;AAAA,QACrB,QAAQA,6BAAa,CAAA,MAAA;AAAA,QACrB,MAAMA,6BAAa,CAAA,IAAA;AAAA,QACnB,UAAUA,6BAAa,CAAA,QAAA;AAAA,QACvB,YAAYA,6BAAa,CAAA,UAAA;AAAA,QACzB,UAAUA,6BAAa,CAAA;AAAA,OAC3B;AAAA,MACA,MAAM,IAAK,CAAA,EAAE,SAAW,EAAA,MAAA,EAAQ,YAAY,MAAQ,EAAA,IAAA,EAAM,QAAU,EAAA,QAAA,EAAY,EAAA;AAC5E,QAAW,UAAA,CAAA,GAAA;AAAA,UACP,MAAM,YAAa,CAAA;AAAA,YACf,YAAc,EAAA,SAAA;AAAA,YACd,SAAW,EAAA,MAAA;AAAA,YACX,SAAW,EAAA,MAAA;AAAA,YACX,OAAS,EAAA,IAAA;AAAA,YACT,WAAa,EAAA,QAAA;AAAA,YACb,WAAa,EAAA;AAAA,WAChB;AAAA,SACL;AAAA;AACJ,KACH,CAAA;AAAA;AAET,CAAC;;;;;"}
|
package/dist/index.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ type KwirthRouterOptions = {
|
|
|
12
12
|
};
|
|
13
13
|
/**
|
|
14
14
|
*
|
|
15
|
-
* @param options core services we need for
|
|
15
|
+
* @param options core services we need for Kwirth to work
|
|
16
16
|
* @returns an express Router
|
|
17
17
|
*/
|
|
18
18
|
declare function createRouter(options: KwirthRouterOptions): Promise<express.Router>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jfvilas/plugin-kwirth-backend",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.5",
|
|
4
4
|
"description": "Backstage backend plugin for Kwirth plugins",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Backstage",
|
|
@@ -52,7 +52,6 @@
|
|
|
52
52
|
"@backstage/config": "^1.2.0",
|
|
53
53
|
"@backstage/errors": "^1.2.4",
|
|
54
54
|
"@backstage/integration": "^1.13.0",
|
|
55
|
-
"@jfvilas/kwirth-common": "0.3.55",
|
|
56
55
|
"@types/express": "^4.17.6",
|
|
57
56
|
"express": "^4.17.1",
|
|
58
57
|
"express-promise-router": "^4.1.0",
|