@backstage-community/plugin-rbac-backend 5.2.3
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/CHANGELOG.md +671 -0
- package/README.md +220 -0
- package/config.d.ts +68 -0
- package/dist/admin-permissions/admin-creation.cjs.js +117 -0
- package/dist/admin-permissions/admin-creation.cjs.js.map +1 -0
- package/dist/audit-log/audit-logger.cjs.js +108 -0
- package/dist/audit-log/audit-logger.cjs.js.map +1 -0
- package/dist/audit-log/rest-errors-interceptor.cjs.js +100 -0
- package/dist/audit-log/rest-errors-interceptor.cjs.js.map +1 -0
- package/dist/conditional-aliases/alias-resolver.cjs.js +76 -0
- package/dist/conditional-aliases/alias-resolver.cjs.js.map +1 -0
- package/dist/database/casbin-adapter-factory.cjs.js +87 -0
- package/dist/database/casbin-adapter-factory.cjs.js.map +1 -0
- package/dist/database/conditional-storage.cjs.js +172 -0
- package/dist/database/conditional-storage.cjs.js.map +1 -0
- package/dist/database/migration.cjs.js +21 -0
- package/dist/database/migration.cjs.js.map +1 -0
- package/dist/database/role-metadata.cjs.js +89 -0
- package/dist/database/role-metadata.cjs.js.map +1 -0
- package/dist/file-permissions/csv-file-watcher.cjs.js +407 -0
- package/dist/file-permissions/csv-file-watcher.cjs.js.map +1 -0
- package/dist/file-permissions/file-watcher.cjs.js +46 -0
- package/dist/file-permissions/file-watcher.cjs.js.map +1 -0
- package/dist/file-permissions/yaml-conditional-file-watcher.cjs.js +208 -0
- package/dist/file-permissions/yaml-conditional-file-watcher.cjs.js.map +1 -0
- package/dist/helper.cjs.js +171 -0
- package/dist/helper.cjs.js.map +1 -0
- package/dist/index.cjs.js +14 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.d.ts +45 -0
- package/dist/plugin.cjs.js +79 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/policies/allow-all-policy.cjs.js +12 -0
- package/dist/policies/allow-all-policy.cjs.js.map +1 -0
- package/dist/policies/permission-policy.cjs.js +243 -0
- package/dist/policies/permission-policy.cjs.js.map +1 -0
- package/dist/providers/connect-providers.cjs.js +211 -0
- package/dist/providers/connect-providers.cjs.js.map +1 -0
- package/dist/role-manager/ancestor-search-memo.cjs.js +159 -0
- package/dist/role-manager/ancestor-search-memo.cjs.js.map +1 -0
- package/dist/role-manager/member-list.cjs.js +101 -0
- package/dist/role-manager/member-list.cjs.js.map +1 -0
- package/dist/role-manager/role-manager.cjs.js +281 -0
- package/dist/role-manager/role-manager.cjs.js.map +1 -0
- package/dist/service/enforcer-delegate.cjs.js +353 -0
- package/dist/service/enforcer-delegate.cjs.js.map +1 -0
- package/dist/service/permission-model.cjs.js +21 -0
- package/dist/service/permission-model.cjs.js.map +1 -0
- package/dist/service/plugin-endpoints.cjs.js +121 -0
- package/dist/service/plugin-endpoints.cjs.js.map +1 -0
- package/dist/service/policies-rest-api.cjs.js +949 -0
- package/dist/service/policies-rest-api.cjs.js.map +1 -0
- package/dist/service/policy-builder.cjs.js +134 -0
- package/dist/service/policy-builder.cjs.js.map +1 -0
- package/dist/service/router.cjs.js +24 -0
- package/dist/service/router.cjs.js.map +1 -0
- package/dist/validation/condition-validation.cjs.js +107 -0
- package/dist/validation/condition-validation.cjs.js.map +1 -0
- package/dist/validation/policies-validation.cjs.js +194 -0
- package/dist/validation/policies-validation.cjs.js.map +1 -0
- package/migrations/20231015161232_migrations.js +41 -0
- package/migrations/20231212224526_migrations.js +84 -0
- package/migrations/20231221113214_migrations.js +60 -0
- package/migrations/20240201144429_migrations.js +37 -0
- package/migrations/20240215154456_migrations.js +143 -0
- package/migrations/20240308134410_migrations.js +31 -0
- package/migrations/20240308134941_migrations.js +43 -0
- package/migrations/20240404111242_migrations.js +53 -0
- package/migrations/20240611092136_migrations.js +29 -0
- package/package.json +98 -0
package/README.md
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# RBAC backend plugin for Backstage
|
|
2
|
+
|
|
3
|
+
This plugin seamlessly integrates with the [Backstage permission framework](https://backstage.io/docs/permissions/overview/) to empower you with robust role-based access control capabilities within your Backstage environment.
|
|
4
|
+
|
|
5
|
+
The Backstage permission framework is a core component of the Backstage project, designed to provide meticulous control over resource and action access. Our RBAC plugin harnesses the power of this framework, allowing you to tailor access permissions without the need for coding. Instead, you can effortlessly manage your access policies through User interface embedded within Backstage or via the configuration files.
|
|
6
|
+
|
|
7
|
+
With the RBAC plugin, you'll have the means to efficiently administer permissions within your Backstage instance by assigning them to users and groups.
|
|
8
|
+
|
|
9
|
+
## Prerequisites
|
|
10
|
+
|
|
11
|
+
Before you dive into utilizing the RBAC plugin for Backstage, there are a few essential prerequisites to ensure a seamless experience. Please review the following requirements to make sure your environment is properly set up
|
|
12
|
+
|
|
13
|
+
### Setup Permission Framework
|
|
14
|
+
|
|
15
|
+
**NOTE**: This section is only relevant if you are still on the old backend system.
|
|
16
|
+
|
|
17
|
+
To effectively utilize the RBAC plugin, you must have the Backstage permission framework in place. If you're using the Red Hat Developer Hub, some of these steps may have already been completed for you. However, for other Backstage application instances, please verify that the following prerequisites are satisfied:
|
|
18
|
+
|
|
19
|
+
You need to [set up the permission framework in Backstage](https://backstage.io/docs/permissions/getting-started/).Since this plugin provides a dynamic policy that replaces the traditional one, there's no need to create a policy manually. Please note that one of the requirements for permission framework is enabling the [service-to-service authentication](https://backstage.io/docs/auth/service-to-service-auth/#setup). Ensure that you complete these authentication setup steps as well.
|
|
20
|
+
|
|
21
|
+
### Identity resolver
|
|
22
|
+
|
|
23
|
+
The permission framework, and consequently, this RBAC plugin, rely on the concept of group membership. To ensure smooth operation, please follow the [Sign-in identities and resolvers](https://backstage.io/docs/auth/identity-resolver/) documentation. It's crucial that when populating groups, you include any groups that you plan to assign permissions to.
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
To integrate the RBAC plugin into your Backstage instance, follow these steps.
|
|
28
|
+
|
|
29
|
+
### Installing the plugin
|
|
30
|
+
|
|
31
|
+
Add the RBAC plugin packages as dependencies by running the following command.
|
|
32
|
+
|
|
33
|
+
```SHELL
|
|
34
|
+
yarn workspace backend add @backstage-community/plugin-rbac-backend
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**NOTE**: If you are using Red Hat Developer Hub backend plugin is pre-installed and you do not need this step.
|
|
38
|
+
|
|
39
|
+
### Configuring the Backend
|
|
40
|
+
|
|
41
|
+
#### New Backend System
|
|
42
|
+
|
|
43
|
+
The RBAC plugin supports the integration with the new backend system.
|
|
44
|
+
|
|
45
|
+
Add the RBAC plugin to the `packages/backend/src/index.ts` file and remove the Permission backend plugin and Allow All Permission policy module.
|
|
46
|
+
|
|
47
|
+
```diff
|
|
48
|
+
// permission plugin
|
|
49
|
+
- backend.add(import('@backstage/plugin-permission-backend/alpha'));
|
|
50
|
+
- backend.add(
|
|
51
|
+
- import('@backstage/plugin-permission-backend-module-allow-all-policy'),
|
|
52
|
+
- );
|
|
53
|
+
+ backend.add(import('@backstage-community/plugin-rbac-backend'));
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Configure policy admins
|
|
57
|
+
|
|
58
|
+
The RBAC plugin empowers you to manage permission policies for users and groups with a designated group of individuals known as policy administrators. These administrators are granted access to the RBAC plugin's REST API and user interface as well as the ability to read from the catalog.
|
|
59
|
+
|
|
60
|
+
You can specify the policy administrators in your application configuration as follows:
|
|
61
|
+
|
|
62
|
+
```YAML
|
|
63
|
+
permission:
|
|
64
|
+
enabled: true
|
|
65
|
+
rbac:
|
|
66
|
+
admin:
|
|
67
|
+
users:
|
|
68
|
+
- name: user:default/alice
|
|
69
|
+
- name: group:default/admins
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The RBAC plugin also enables you to grant users the title of 'super user,' which provides them with unrestricted access throughout the Backstage instance.
|
|
73
|
+
|
|
74
|
+
You can specify the super users in your application configuration as follows:
|
|
75
|
+
|
|
76
|
+
```YAML
|
|
77
|
+
permission:
|
|
78
|
+
enabled: true
|
|
79
|
+
rbac:
|
|
80
|
+
admin:
|
|
81
|
+
superUsers:
|
|
82
|
+
- name: user:default/alice
|
|
83
|
+
- name: user:default/mike
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
For more information on the available API endpoints accessible to the policy administrators, refer to the [API documentation](./docs/apis.md).
|
|
87
|
+
|
|
88
|
+
### Configuring policies via file
|
|
89
|
+
|
|
90
|
+
The RBAC plugin also allows you to import policies from an external file. These policies are defined in the [Casbin rules format](https://casbin.org/docs/category/the-basics), known for its simplicity and clarity. For a quick start, please refer to the format details in the provided link.
|
|
91
|
+
|
|
92
|
+
Here's an example of an external permission policies configuration file named `rbac-policy.csv`:
|
|
93
|
+
|
|
94
|
+
```CSV
|
|
95
|
+
p, role:default/team_a, catalog-entity, read, deny
|
|
96
|
+
p, role:default/team_b, catalog.entity.create, create, deny
|
|
97
|
+
|
|
98
|
+
g, user:default/bob, role:default/team_a
|
|
99
|
+
|
|
100
|
+
g, group:default/team_b, role:default/team_b
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
**NOTE**: When you add a role in the permission policies configuration file, ensure that the role is associated with at least one permission policy with the `allow` effect.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
You can specify the path to this configuration file in your application configuration:
|
|
110
|
+
|
|
111
|
+
```YAML
|
|
112
|
+
permission:
|
|
113
|
+
enabled: true
|
|
114
|
+
rbac:
|
|
115
|
+
policies-csv-file: /some/path/rbac-policy.csv
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Also, there is an additional configuration value that allows for the reloading of the CSV file without the need to restart.
|
|
119
|
+
|
|
120
|
+
```YAML
|
|
121
|
+
permission:
|
|
122
|
+
enabled: true
|
|
123
|
+
rbac:
|
|
124
|
+
policies-csv-file: /some/path/rbac-policy.csv
|
|
125
|
+
policyFileReload: true
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
For more information on the available permissions within Showcase and RHDH, refer to the [permissions documentation](./docs/permissions.md).
|
|
129
|
+
|
|
130
|
+
We also have a fairly strict validation for permission policies and roles based on the originating role's source information, refer to the [api documentation](./docs/apis.md).
|
|
131
|
+
|
|
132
|
+
### Configuring conditional policies via file
|
|
133
|
+
|
|
134
|
+
The RBAC plugin allows you to import conditional policies from an external file. User can defined conditional policies for roles created with the help of the policies-csv-file. Conditional policies should be defined as object sequences in the YAML format.
|
|
135
|
+
|
|
136
|
+
You can specify the path to this configuration file in your application configuration:
|
|
137
|
+
|
|
138
|
+
```YAML
|
|
139
|
+
permission:
|
|
140
|
+
enabled: true
|
|
141
|
+
rbac:
|
|
142
|
+
conditionalPoliciesFile: /some/path/conditional-policies.yaml
|
|
143
|
+
policies-csv-file: /some/path/rbac-policy.csv
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Also, there is an additional configuration value that allows for the reloading of the file without the need to restart.
|
|
147
|
+
|
|
148
|
+
```YAML
|
|
149
|
+
permission:
|
|
150
|
+
enabled: true
|
|
151
|
+
rbac:
|
|
152
|
+
conditionalPoliciesFile: /some/path/conditional-policies.yaml
|
|
153
|
+
policies-csv-file: /some/path/rbac-policy.csv
|
|
154
|
+
policyFileReload: true
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
This feature supports nested conditional policies.
|
|
158
|
+
|
|
159
|
+
Example of the conditional policies file:
|
|
160
|
+
|
|
161
|
+
```yaml
|
|
162
|
+
---
|
|
163
|
+
result: CONDITIONAL
|
|
164
|
+
roleEntityRef: 'role:default/test'
|
|
165
|
+
pluginId: catalog
|
|
166
|
+
resourceType: catalog-entity
|
|
167
|
+
permissionMapping:
|
|
168
|
+
- read
|
|
169
|
+
- update
|
|
170
|
+
conditions:
|
|
171
|
+
rule: IS_ENTITY_OWNER
|
|
172
|
+
resourceType: catalog-entity
|
|
173
|
+
params:
|
|
174
|
+
claims:
|
|
175
|
+
- 'group:default/team-a'
|
|
176
|
+
- 'group:default/team-b'
|
|
177
|
+
---
|
|
178
|
+
result: CONDITIONAL
|
|
179
|
+
roleEntityRef: 'role:default/test'
|
|
180
|
+
pluginId: catalog
|
|
181
|
+
resourceType: catalog-entity
|
|
182
|
+
permissionMapping:
|
|
183
|
+
- delete
|
|
184
|
+
conditions:
|
|
185
|
+
rule: IS_ENTITY_OWNER
|
|
186
|
+
resourceType: catalog-entity
|
|
187
|
+
params:
|
|
188
|
+
claims:
|
|
189
|
+
- 'group:default/team-a'
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Information about condition policies format you can find in the doc: [Conditional policies documentation](./docs/conditions.md). There is only one difference: yaml format compare to json. But yaml and json are back convertiable.
|
|
193
|
+
|
|
194
|
+
### Configuring Database Storage for policies
|
|
195
|
+
|
|
196
|
+
The RBAC plugin offers the option to store policies in a database. It supports two database storage options:
|
|
197
|
+
|
|
198
|
+
- sqlite3: Suitable for development environments.
|
|
199
|
+
- postgres: Recommended for production environments.
|
|
200
|
+
|
|
201
|
+
Ensure that you have already configured the database backend for your Backstage instance, as the RBAC plugin utilizes the same database configuration.
|
|
202
|
+
|
|
203
|
+
### Optional maximum depth
|
|
204
|
+
|
|
205
|
+
The RBAC plugin also includes an option max depth feature for organizations with potentially complex group hierarchy, this configuration value will ensure that the RBAC plugin will stop at a certain depth when building user graphs.
|
|
206
|
+
|
|
207
|
+
```YAML
|
|
208
|
+
permission:
|
|
209
|
+
enabled: true
|
|
210
|
+
rbac:
|
|
211
|
+
maxDepth: 1
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
The maxDepth must be greater than 0 to ensure that the graphs are built correctly. Also the graph will be built with a hierarchy of 1 + maxDepth.
|
|
215
|
+
|
|
216
|
+
More information about group hierarchy can be found in the doc: [Group hierarchy](./docs/group-hierarchy.md).
|
|
217
|
+
|
|
218
|
+
### Optional RBAC provider module support
|
|
219
|
+
|
|
220
|
+
We also include the ability to create and load in RBAC backend plugin modules that can be used to make connections to third part access management tools. For more information, consult the [RBAC Providers documentation](./docs/providers.md).
|
package/config.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 The Backstage Authors
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
export interface Config {
|
|
17
|
+
permission: {
|
|
18
|
+
rbac: {
|
|
19
|
+
'policies-csv-file'?: string;
|
|
20
|
+
/**
|
|
21
|
+
* The path to the yaml file containing the conditional policies
|
|
22
|
+
* @visibility frontend
|
|
23
|
+
*/
|
|
24
|
+
conditionalPoliciesFile?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Allow for reloading of the CSV and conditional policies files.
|
|
27
|
+
* @visibility frontend
|
|
28
|
+
*/
|
|
29
|
+
policyFileReload?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Optional configuration for admins
|
|
32
|
+
* @visibility frontend
|
|
33
|
+
*/
|
|
34
|
+
admin?: {
|
|
35
|
+
/**
|
|
36
|
+
* The list of users and / or groups with admin access
|
|
37
|
+
* @visibility frontend
|
|
38
|
+
*/
|
|
39
|
+
users?: Array<{
|
|
40
|
+
/**
|
|
41
|
+
* @visibility frontend
|
|
42
|
+
*/
|
|
43
|
+
name: string;
|
|
44
|
+
}>;
|
|
45
|
+
/**
|
|
46
|
+
* The list of super users that will have allow all access, should be a list of only users
|
|
47
|
+
* @visibility frontend
|
|
48
|
+
*/
|
|
49
|
+
superUsers?: Array<{
|
|
50
|
+
/**
|
|
51
|
+
* @visibility frontend
|
|
52
|
+
*/
|
|
53
|
+
name: string;
|
|
54
|
+
}>;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* An optional list of plugin IDs.
|
|
58
|
+
* The RBAC plugin will handle access control for plugins included in this list.
|
|
59
|
+
*/
|
|
60
|
+
pluginsWithPermission?: string[];
|
|
61
|
+
/**
|
|
62
|
+
* An optional value that limits the depth when building the hierarchy group graph
|
|
63
|
+
* @visibility frontend
|
|
64
|
+
*/
|
|
65
|
+
maxDepth?: number;
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var auditLogger = require('../audit-log/audit-logger.cjs.js');
|
|
4
|
+
var helper = require('../helper.cjs.js');
|
|
5
|
+
var policiesValidation = require('../validation/policies-validation.cjs.js');
|
|
6
|
+
|
|
7
|
+
const ADMIN_ROLE_NAME = "role:default/rbac_admin";
|
|
8
|
+
const ADMIN_ROLE_AUTHOR = "application configuration";
|
|
9
|
+
const DEF_ADMIN_ROLE_DESCRIPTION = "The default permission policy for the admin role allows for the creation, deletion, updating, and reading of roles and permission policies.";
|
|
10
|
+
const getAdminRoleMetadata = () => {
|
|
11
|
+
const currentDate = /* @__PURE__ */ new Date();
|
|
12
|
+
return {
|
|
13
|
+
source: "configuration",
|
|
14
|
+
roleEntityRef: ADMIN_ROLE_NAME,
|
|
15
|
+
description: DEF_ADMIN_ROLE_DESCRIPTION,
|
|
16
|
+
author: ADMIN_ROLE_AUTHOR,
|
|
17
|
+
modifiedBy: ADMIN_ROLE_AUTHOR,
|
|
18
|
+
lastModified: currentDate.toUTCString(),
|
|
19
|
+
createdAt: currentDate.toUTCString()
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
const useAdminsFromConfig = async (admins, enf, auditLogger$1, roleMetadataStorage, knex) => {
|
|
23
|
+
const addedGroupPolicies = /* @__PURE__ */ new Map();
|
|
24
|
+
const newGroupPolicies = /* @__PURE__ */ new Map();
|
|
25
|
+
for (const admin of admins) {
|
|
26
|
+
const entityRef = admin.getString("name");
|
|
27
|
+
policiesValidation.validateEntityReference(entityRef);
|
|
28
|
+
addedGroupPolicies.set(entityRef, ADMIN_ROLE_NAME);
|
|
29
|
+
if (!await enf.hasGroupingPolicy(...[entityRef, ADMIN_ROLE_NAME])) {
|
|
30
|
+
newGroupPolicies.set(entityRef, ADMIN_ROLE_NAME);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const adminRoleMeta = await roleMetadataStorage.findRoleMetadata(
|
|
34
|
+
ADMIN_ROLE_NAME
|
|
35
|
+
);
|
|
36
|
+
const trx = await knex.transaction();
|
|
37
|
+
let addedRoleMembers;
|
|
38
|
+
try {
|
|
39
|
+
if (!adminRoleMeta) {
|
|
40
|
+
await roleMetadataStorage.createRoleMetadata(getAdminRoleMetadata(), trx);
|
|
41
|
+
} else if (adminRoleMeta.source === "legacy") {
|
|
42
|
+
await roleMetadataStorage.updateRoleMetadata(
|
|
43
|
+
getAdminRoleMetadata(),
|
|
44
|
+
ADMIN_ROLE_NAME,
|
|
45
|
+
trx
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
addedRoleMembers = Array.from(newGroupPolicies.entries());
|
|
49
|
+
await enf.addGroupingPolicies(
|
|
50
|
+
addedRoleMembers,
|
|
51
|
+
getAdminRoleMetadata(),
|
|
52
|
+
trx
|
|
53
|
+
);
|
|
54
|
+
await trx.commit();
|
|
55
|
+
} catch (error) {
|
|
56
|
+
await trx.rollback(error);
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
await auditLogger$1.auditLog({
|
|
60
|
+
actorId: auditLogger.RBAC_BACKEND,
|
|
61
|
+
message: `Created or updated role`,
|
|
62
|
+
eventName: auditLogger.RoleEvents.CREATE_OR_UPDATE_ROLE,
|
|
63
|
+
metadata: {
|
|
64
|
+
...getAdminRoleMetadata(),
|
|
65
|
+
members: addedRoleMembers.map((gp) => gp[0])
|
|
66
|
+
},
|
|
67
|
+
stage: auditLogger.HANDLE_RBAC_DATA_STAGE,
|
|
68
|
+
status: "succeeded"
|
|
69
|
+
});
|
|
70
|
+
const configGroupPolicies = await enf.getFilteredGroupingPolicy(
|
|
71
|
+
1,
|
|
72
|
+
ADMIN_ROLE_NAME
|
|
73
|
+
);
|
|
74
|
+
await helper.removeTheDifference(
|
|
75
|
+
configGroupPolicies.map((gp) => gp[0]),
|
|
76
|
+
Array.from(addedGroupPolicies.keys()),
|
|
77
|
+
"configuration",
|
|
78
|
+
ADMIN_ROLE_NAME,
|
|
79
|
+
enf,
|
|
80
|
+
auditLogger$1,
|
|
81
|
+
ADMIN_ROLE_AUTHOR
|
|
82
|
+
);
|
|
83
|
+
};
|
|
84
|
+
const addAdminPermissions = async (policies, enf, auditLogger$1) => {
|
|
85
|
+
const policiesToAdd = [];
|
|
86
|
+
for (const policy of policies) {
|
|
87
|
+
if (!await enf.hasPolicy(...policy)) {
|
|
88
|
+
policiesToAdd.push(policy);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
await enf.addPolicies(policiesToAdd);
|
|
92
|
+
await auditLogger$1.auditLog({
|
|
93
|
+
actorId: auditLogger.RBAC_BACKEND,
|
|
94
|
+
message: `Created RBAC admin permissions`,
|
|
95
|
+
eventName: auditLogger.PermissionEvents.CREATE_POLICY,
|
|
96
|
+
metadata: { policies, source: "configuration" },
|
|
97
|
+
stage: auditLogger.HANDLE_RBAC_DATA_STAGE,
|
|
98
|
+
status: "succeeded"
|
|
99
|
+
});
|
|
100
|
+
};
|
|
101
|
+
const setAdminPermissions = async (enf, auditLogger) => {
|
|
102
|
+
const adminPermissions = [
|
|
103
|
+
[ADMIN_ROLE_NAME, "policy-entity", "read", "allow"],
|
|
104
|
+
[ADMIN_ROLE_NAME, "policy-entity", "create", "allow"],
|
|
105
|
+
[ADMIN_ROLE_NAME, "policy-entity", "delete", "allow"],
|
|
106
|
+
[ADMIN_ROLE_NAME, "policy-entity", "update", "allow"],
|
|
107
|
+
// Needed for the RBAC frontend plugin.
|
|
108
|
+
[ADMIN_ROLE_NAME, "catalog-entity", "read", "allow"]
|
|
109
|
+
];
|
|
110
|
+
await addAdminPermissions(adminPermissions, enf, auditLogger);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
exports.ADMIN_ROLE_AUTHOR = ADMIN_ROLE_AUTHOR;
|
|
114
|
+
exports.ADMIN_ROLE_NAME = ADMIN_ROLE_NAME;
|
|
115
|
+
exports.setAdminPermissions = setAdminPermissions;
|
|
116
|
+
exports.useAdminsFromConfig = useAdminsFromConfig;
|
|
117
|
+
//# sourceMappingURL=admin-creation.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin-creation.cjs.js","sources":["../../src/admin-permissions/admin-creation.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { Config } from '@backstage/config';\n\nimport { AuditLogger } from '@janus-idp/backstage-plugin-audit-log-node';\nimport { Knex } from 'knex';\n\nimport {\n HANDLE_RBAC_DATA_STAGE,\n PermissionAuditInfo,\n PermissionEvents,\n RBAC_BACKEND,\n RoleAuditInfo,\n RoleEvents,\n} from '../audit-log/audit-logger';\nimport {\n RoleMetadataDao,\n RoleMetadataStorage,\n} from '../database/role-metadata';\nimport { removeTheDifference } from '../helper';\nimport { EnforcerDelegate } from '../service/enforcer-delegate';\nimport { validateEntityReference } from '../validation/policies-validation';\n\nexport const ADMIN_ROLE_NAME = 'role:default/rbac_admin';\nexport const ADMIN_ROLE_AUTHOR = 'application configuration';\nconst DEF_ADMIN_ROLE_DESCRIPTION =\n 'The default permission policy for the admin role allows for the creation, deletion, updating, and reading of roles and permission policies.';\n\nconst getAdminRoleMetadata = (): RoleMetadataDao => {\n const currentDate: Date = new Date();\n return {\n source: 'configuration',\n roleEntityRef: ADMIN_ROLE_NAME,\n description: DEF_ADMIN_ROLE_DESCRIPTION,\n author: ADMIN_ROLE_AUTHOR,\n modifiedBy: ADMIN_ROLE_AUTHOR,\n lastModified: currentDate.toUTCString(),\n createdAt: currentDate.toUTCString(),\n };\n};\n\nexport const useAdminsFromConfig = async (\n admins: Config[],\n enf: EnforcerDelegate,\n auditLogger: AuditLogger,\n roleMetadataStorage: RoleMetadataStorage,\n knex: Knex,\n) => {\n const addedGroupPolicies = new Map<string, string>();\n const newGroupPolicies = new Map<string, string>();\n\n for (const admin of admins) {\n const entityRef = admin.getString('name');\n validateEntityReference(entityRef);\n\n addedGroupPolicies.set(entityRef, ADMIN_ROLE_NAME);\n\n if (!(await enf.hasGroupingPolicy(...[entityRef, ADMIN_ROLE_NAME]))) {\n newGroupPolicies.set(entityRef, ADMIN_ROLE_NAME);\n }\n }\n\n const adminRoleMeta = await roleMetadataStorage.findRoleMetadata(\n ADMIN_ROLE_NAME,\n );\n\n const trx = await knex.transaction();\n let addedRoleMembers;\n try {\n if (!adminRoleMeta) {\n // even if there are no user, we still create default role metadata for admins\n await roleMetadataStorage.createRoleMetadata(getAdminRoleMetadata(), trx);\n } else if (adminRoleMeta.source === 'legacy') {\n await roleMetadataStorage.updateRoleMetadata(\n getAdminRoleMetadata(),\n ADMIN_ROLE_NAME,\n trx,\n );\n }\n\n addedRoleMembers = Array.from<string[]>(newGroupPolicies.entries());\n await enf.addGroupingPolicies(\n addedRoleMembers,\n getAdminRoleMetadata(),\n trx,\n );\n\n await trx.commit();\n } catch (error) {\n await trx.rollback(error);\n throw error;\n }\n\n await auditLogger.auditLog<RoleAuditInfo>({\n actorId: RBAC_BACKEND,\n message: `Created or updated role`,\n eventName: RoleEvents.CREATE_OR_UPDATE_ROLE,\n metadata: {\n ...getAdminRoleMetadata(),\n members: addedRoleMembers.map(gp => gp[0]),\n },\n stage: HANDLE_RBAC_DATA_STAGE,\n status: 'succeeded',\n });\n\n const configGroupPolicies = await enf.getFilteredGroupingPolicy(\n 1,\n ADMIN_ROLE_NAME,\n );\n\n await removeTheDifference(\n configGroupPolicies.map(gp => gp[0]),\n Array.from<string>(addedGroupPolicies.keys()),\n 'configuration',\n ADMIN_ROLE_NAME,\n enf,\n auditLogger,\n ADMIN_ROLE_AUTHOR,\n );\n};\n\nconst addAdminPermissions = async (\n policies: string[][],\n enf: EnforcerDelegate,\n auditLogger: AuditLogger,\n) => {\n const policiesToAdd: string[][] = [];\n for (const policy of policies) {\n if (!(await enf.hasPolicy(...policy))) {\n policiesToAdd.push(policy);\n }\n }\n await enf.addPolicies(policiesToAdd);\n\n await auditLogger.auditLog<PermissionAuditInfo>({\n actorId: RBAC_BACKEND,\n message: `Created RBAC admin permissions`,\n eventName: PermissionEvents.CREATE_POLICY,\n metadata: { policies: policies, source: 'configuration' },\n stage: HANDLE_RBAC_DATA_STAGE,\n status: 'succeeded',\n });\n};\n\nexport const setAdminPermissions = async (\n enf: EnforcerDelegate,\n auditLogger: AuditLogger,\n) => {\n const adminPermissions = [\n [ADMIN_ROLE_NAME, 'policy-entity', 'read', 'allow'],\n [ADMIN_ROLE_NAME, 'policy-entity', 'create', 'allow'],\n [ADMIN_ROLE_NAME, 'policy-entity', 'delete', 'allow'],\n [ADMIN_ROLE_NAME, 'policy-entity', 'update', 'allow'],\n // Needed for the RBAC frontend plugin.\n [ADMIN_ROLE_NAME, 'catalog-entity', 'read', 'allow'],\n ];\n await addAdminPermissions(adminPermissions, enf, auditLogger);\n};\n"],"names":["auditLogger","validateEntityReference","RBAC_BACKEND","RoleEvents","HANDLE_RBAC_DATA_STAGE","removeTheDifference","PermissionEvents"],"mappings":"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"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var pluginPermissionCommon = require('@backstage/plugin-permission-common');
|
|
4
|
+
var pluginRbacCommon = require('@backstage-community/plugin-rbac-common');
|
|
5
|
+
|
|
6
|
+
const RoleEvents = {
|
|
7
|
+
CREATE_ROLE: "CreateRole",
|
|
8
|
+
UPDATE_ROLE: "UpdateRole",
|
|
9
|
+
DELETE_ROLE: "DeleteRole",
|
|
10
|
+
CREATE_OR_UPDATE_ROLE: "CreateOrUpdateRole",
|
|
11
|
+
GET_ROLE: "GetRole",
|
|
12
|
+
CREATE_ROLE_ERROR: "CreateRoleError",
|
|
13
|
+
UPDATE_ROLE_ERROR: "UpdateRoleError",
|
|
14
|
+
DELETE_ROLE_ERROR: "DeleteRoleError",
|
|
15
|
+
GET_ROLE_ERROR: "GetRoleError"
|
|
16
|
+
};
|
|
17
|
+
const PermissionEvents = {
|
|
18
|
+
CREATE_POLICY: "CreatePolicy",
|
|
19
|
+
UPDATE_POLICY: "UpdatePolicy",
|
|
20
|
+
DELETE_POLICY: "DeletePolicy",
|
|
21
|
+
GET_POLICY: "GetPolicy",
|
|
22
|
+
CREATE_POLICY_ERROR: "CreatePolicyError",
|
|
23
|
+
UPDATE_POLICY_ERROR: "UpdatePolicyError",
|
|
24
|
+
DELETE_POLICY_ERROR: "DeletePolicyError",
|
|
25
|
+
GET_POLICY_ERROR: "GetPolicyError"
|
|
26
|
+
};
|
|
27
|
+
const EvaluationEvents = {
|
|
28
|
+
PERMISSION_EVALUATION_STARTED: "PermissionEvaluationStarted",
|
|
29
|
+
PERMISSION_EVALUATION_COMPLETED: "PermissionEvaluationCompleted",
|
|
30
|
+
CONDITION_EVALUATION_COMPLETED: "ConditionEvaluationCompleted",
|
|
31
|
+
PERMISSION_EVALUATION_FAILED: "PermissionEvaluationFailed"
|
|
32
|
+
};
|
|
33
|
+
const ListPluginPoliciesEvents = {
|
|
34
|
+
GET_PLUGINS_POLICIES: "GetPluginsPolicies",
|
|
35
|
+
GET_PLUGINS_POLICIES_ERROR: "GetPluginsPoliciesError"
|
|
36
|
+
};
|
|
37
|
+
const ListConditionEvents = {
|
|
38
|
+
GET_CONDITION_RULES: "GetConditionRules",
|
|
39
|
+
GET_CONDITION_RULES_ERROR: "GetConditionRulesError"
|
|
40
|
+
};
|
|
41
|
+
const ConditionEvents = {
|
|
42
|
+
CREATE_CONDITION: "CreateCondition",
|
|
43
|
+
UPDATE_CONDITION: "UpdateCondition",
|
|
44
|
+
DELETE_CONDITION: "DeleteCondition",
|
|
45
|
+
GET_CONDITION: "GetCondition",
|
|
46
|
+
CREATE_CONDITION_ERROR: "CreateConditionError",
|
|
47
|
+
UPDATE_CONDITION_ERROR: "UpdateConditionError",
|
|
48
|
+
DELETE_CONDITION_ERROR: "DeleteConditionError",
|
|
49
|
+
GET_CONDITION_ERROR: "GetConditionError",
|
|
50
|
+
PARSE_CONDITION_ERROR: "ParseConditionError",
|
|
51
|
+
CHANGE_CONDITIONAL_POLICIES_FILE_ERROR: "ChangeConditionalPoliciesError",
|
|
52
|
+
CONDITIONAL_POLICIES_FILE_NOT_FOUND: "ConditionalPoliciesFileNotFound"
|
|
53
|
+
};
|
|
54
|
+
const RBAC_BACKEND = "rbac-backend";
|
|
55
|
+
const HANDLE_RBAC_DATA_STAGE = "handleRBACData";
|
|
56
|
+
const EVALUATE_PERMISSION_ACCESS_STAGE = "evaluatePermissionAccess";
|
|
57
|
+
const SEND_RESPONSE_STAGE = "sendResponse";
|
|
58
|
+
const RESPONSE_ERROR = "responseError";
|
|
59
|
+
function createPermissionEvaluationOptions(message, userEntityRef, request, policyDecision) {
|
|
60
|
+
const auditInfo = {
|
|
61
|
+
userEntityRef,
|
|
62
|
+
permissionName: request.permission.name,
|
|
63
|
+
action: pluginRbacCommon.toPermissionAction(request.permission.attributes)
|
|
64
|
+
};
|
|
65
|
+
const resourceType = request.permission.resourceType;
|
|
66
|
+
if (resourceType) {
|
|
67
|
+
auditInfo.resourceType = resourceType;
|
|
68
|
+
}
|
|
69
|
+
let eventName;
|
|
70
|
+
if (!policyDecision) {
|
|
71
|
+
eventName = EvaluationEvents.PERMISSION_EVALUATION_STARTED;
|
|
72
|
+
} else {
|
|
73
|
+
auditInfo.decision = policyDecision;
|
|
74
|
+
switch (policyDecision.result) {
|
|
75
|
+
case pluginPermissionCommon.AuthorizeResult.DENY:
|
|
76
|
+
case pluginPermissionCommon.AuthorizeResult.ALLOW:
|
|
77
|
+
eventName = EvaluationEvents.PERMISSION_EVALUATION_COMPLETED;
|
|
78
|
+
break;
|
|
79
|
+
case pluginPermissionCommon.AuthorizeResult.CONDITIONAL:
|
|
80
|
+
eventName = EvaluationEvents.CONDITION_EVALUATION_COMPLETED;
|
|
81
|
+
break;
|
|
82
|
+
default:
|
|
83
|
+
throw new Error("Unknown policy decision result");
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
actorId: userEntityRef,
|
|
88
|
+
message,
|
|
89
|
+
eventName,
|
|
90
|
+
metadata: auditInfo,
|
|
91
|
+
stage: EVALUATE_PERMISSION_ACCESS_STAGE,
|
|
92
|
+
status: "succeeded"
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
exports.ConditionEvents = ConditionEvents;
|
|
97
|
+
exports.EVALUATE_PERMISSION_ACCESS_STAGE = EVALUATE_PERMISSION_ACCESS_STAGE;
|
|
98
|
+
exports.EvaluationEvents = EvaluationEvents;
|
|
99
|
+
exports.HANDLE_RBAC_DATA_STAGE = HANDLE_RBAC_DATA_STAGE;
|
|
100
|
+
exports.ListConditionEvents = ListConditionEvents;
|
|
101
|
+
exports.ListPluginPoliciesEvents = ListPluginPoliciesEvents;
|
|
102
|
+
exports.PermissionEvents = PermissionEvents;
|
|
103
|
+
exports.RBAC_BACKEND = RBAC_BACKEND;
|
|
104
|
+
exports.RESPONSE_ERROR = RESPONSE_ERROR;
|
|
105
|
+
exports.RoleEvents = RoleEvents;
|
|
106
|
+
exports.SEND_RESPONSE_STAGE = SEND_RESPONSE_STAGE;
|
|
107
|
+
exports.createPermissionEvaluationOptions = createPermissionEvaluationOptions;
|
|
108
|
+
//# sourceMappingURL=audit-logger.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-logger.cjs.js","sources":["../../src/audit-log/audit-logger.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n AuthorizeResult,\n PolicyDecision,\n ResourcePermission,\n} from '@backstage/plugin-permission-common';\nimport type { PolicyQuery } from '@backstage/plugin-permission-node';\n\nimport type { AuditLogOptions } from '@janus-idp/backstage-plugin-audit-log-node';\n\nimport {\n PermissionAction,\n RoleConditionalPolicyDecision,\n Source,\n toPermissionAction,\n} from '@backstage-community/plugin-rbac-common';\n\nexport const RoleEvents = {\n CREATE_ROLE: 'CreateRole',\n UPDATE_ROLE: 'UpdateRole',\n DELETE_ROLE: 'DeleteRole',\n CREATE_OR_UPDATE_ROLE: 'CreateOrUpdateRole',\n GET_ROLE: 'GetRole',\n\n CREATE_ROLE_ERROR: 'CreateRoleError',\n UPDATE_ROLE_ERROR: 'UpdateRoleError',\n DELETE_ROLE_ERROR: 'DeleteRoleError',\n GET_ROLE_ERROR: 'GetRoleError',\n} as const;\n\nexport const PermissionEvents = {\n CREATE_POLICY: 'CreatePolicy',\n UPDATE_POLICY: 'UpdatePolicy',\n DELETE_POLICY: 'DeletePolicy',\n GET_POLICY: 'GetPolicy',\n\n CREATE_POLICY_ERROR: 'CreatePolicyError',\n UPDATE_POLICY_ERROR: 'UpdatePolicyError',\n DELETE_POLICY_ERROR: 'DeletePolicyError',\n GET_POLICY_ERROR: 'GetPolicyError',\n} as const;\n\nexport type RoleAuditInfo = {\n roleEntityRef: string;\n description?: string;\n source: Source;\n\n members: string[];\n};\n\nexport type PermissionAuditInfo = {\n policies: string[][];\n source: Source;\n};\n\nexport const EvaluationEvents = {\n PERMISSION_EVALUATION_STARTED: 'PermissionEvaluationStarted',\n PERMISSION_EVALUATION_COMPLETED: 'PermissionEvaluationCompleted',\n CONDITION_EVALUATION_COMPLETED: 'ConditionEvaluationCompleted',\n PERMISSION_EVALUATION_FAILED: 'PermissionEvaluationFailed',\n} as const;\n\nexport const ListPluginPoliciesEvents = {\n GET_PLUGINS_POLICIES: 'GetPluginsPolicies',\n GET_PLUGINS_POLICIES_ERROR: 'GetPluginsPoliciesError',\n};\n\nexport const ListConditionEvents = {\n GET_CONDITION_RULES: 'GetConditionRules',\n GET_CONDITION_RULES_ERROR: 'GetConditionRulesError',\n};\n\nexport type EvaluationAuditInfo = {\n userEntityRef: string;\n permissionName: string;\n action: PermissionAction;\n resourceType?: string;\n decision?: PolicyDecision;\n};\n\nexport const ConditionEvents = {\n CREATE_CONDITION: 'CreateCondition',\n UPDATE_CONDITION: 'UpdateCondition',\n DELETE_CONDITION: 'DeleteCondition',\n GET_CONDITION: 'GetCondition',\n\n CREATE_CONDITION_ERROR: 'CreateConditionError',\n UPDATE_CONDITION_ERROR: 'UpdateConditionError',\n DELETE_CONDITION_ERROR: 'DeleteConditionError',\n GET_CONDITION_ERROR: 'GetConditionError',\n PARSE_CONDITION_ERROR: 'ParseConditionError',\n CHANGE_CONDITIONAL_POLICIES_FILE_ERROR: 'ChangeConditionalPoliciesError',\n CONDITIONAL_POLICIES_FILE_NOT_FOUND: 'ConditionalPoliciesFileNotFound',\n};\n\nexport type ConditionAuditInfo = {\n condition: RoleConditionalPolicyDecision<PermissionAction>;\n};\n\nexport const RBAC_BACKEND = 'rbac-backend';\n\n// Audit log stage for processing Role-Based Access Control (RBAC) data\nexport const HANDLE_RBAC_DATA_STAGE = 'handleRBACData';\n\n// Audit log stage for determining access rights based on user permissions and resource information\nexport const EVALUATE_PERMISSION_ACCESS_STAGE = 'evaluatePermissionAccess';\n\n// Audit log stage for sending the response to the client about handled permission policies, roles, and condition policies\nexport const SEND_RESPONSE_STAGE = 'sendResponse';\nexport const RESPONSE_ERROR = 'responseError';\n\nexport function createPermissionEvaluationOptions(\n message: string,\n userEntityRef: string,\n request: PolicyQuery,\n policyDecision?: PolicyDecision,\n): AuditLogOptions<EvaluationAuditInfo> {\n const auditInfo: EvaluationAuditInfo = {\n userEntityRef,\n permissionName: request.permission.name,\n action: toPermissionAction(request.permission.attributes),\n };\n\n const resourceType = (request.permission as ResourcePermission).resourceType;\n if (resourceType) {\n auditInfo.resourceType = resourceType;\n }\n\n let eventName;\n if (!policyDecision) {\n eventName = EvaluationEvents.PERMISSION_EVALUATION_STARTED;\n } else {\n auditInfo.decision = policyDecision;\n\n switch (policyDecision.result) {\n case AuthorizeResult.DENY:\n case AuthorizeResult.ALLOW:\n eventName = EvaluationEvents.PERMISSION_EVALUATION_COMPLETED;\n break;\n case AuthorizeResult.CONDITIONAL:\n eventName = EvaluationEvents.CONDITION_EVALUATION_COMPLETED;\n break;\n default:\n throw new Error('Unknown policy decision result');\n }\n }\n\n return {\n actorId: userEntityRef,\n message,\n eventName,\n metadata: auditInfo,\n stage: EVALUATE_PERMISSION_ACCESS_STAGE,\n status: 'succeeded',\n };\n}\n"],"names":["toPermissionAction","AuthorizeResult"],"mappings":"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"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var auditLogger = require('./audit-logger.cjs.js');
|
|
4
|
+
|
|
5
|
+
const eventMap = {
|
|
6
|
+
"/policies": {
|
|
7
|
+
POST: {
|
|
8
|
+
event: auditLogger.PermissionEvents.CREATE_POLICY_ERROR,
|
|
9
|
+
message: "Failed to create permission policies"
|
|
10
|
+
},
|
|
11
|
+
PUT: {
|
|
12
|
+
event: auditLogger.PermissionEvents.UPDATE_POLICY_ERROR,
|
|
13
|
+
message: "Failed to update permission policies"
|
|
14
|
+
},
|
|
15
|
+
DELETE: {
|
|
16
|
+
event: auditLogger.PermissionEvents.DELETE_POLICY_ERROR,
|
|
17
|
+
message: "Failed to delete permission policies"
|
|
18
|
+
},
|
|
19
|
+
GET: {
|
|
20
|
+
event: auditLogger.PermissionEvents.GET_POLICY_ERROR,
|
|
21
|
+
message: "Failed to get permission policies"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"/roles": {
|
|
25
|
+
POST: {
|
|
26
|
+
event: auditLogger.RoleEvents.CREATE_ROLE_ERROR,
|
|
27
|
+
message: "Failed to create role"
|
|
28
|
+
},
|
|
29
|
+
PUT: {
|
|
30
|
+
event: auditLogger.RoleEvents.UPDATE_ROLE_ERROR,
|
|
31
|
+
message: "Failed to update role"
|
|
32
|
+
},
|
|
33
|
+
DELETE: {
|
|
34
|
+
event: auditLogger.RoleEvents.DELETE_ROLE_ERROR,
|
|
35
|
+
message: "Failed to delete role"
|
|
36
|
+
},
|
|
37
|
+
GET: { event: auditLogger.RoleEvents.GET_ROLE_ERROR, message: "Failed to get role" }
|
|
38
|
+
},
|
|
39
|
+
"/roles/conditions": {
|
|
40
|
+
POST: {
|
|
41
|
+
event: auditLogger.ConditionEvents.CREATE_CONDITION_ERROR,
|
|
42
|
+
message: "Failed to create condition"
|
|
43
|
+
},
|
|
44
|
+
PUT: {
|
|
45
|
+
event: auditLogger.ConditionEvents.UPDATE_CONDITION_ERROR,
|
|
46
|
+
message: "Failed to update condition"
|
|
47
|
+
},
|
|
48
|
+
DELETE: {
|
|
49
|
+
event: auditLogger.ConditionEvents.DELETE_CONDITION_ERROR,
|
|
50
|
+
message: "Failed to delete condition"
|
|
51
|
+
},
|
|
52
|
+
GET: {
|
|
53
|
+
event: auditLogger.ConditionEvents.GET_CONDITION_ERROR,
|
|
54
|
+
message: "Failed to get condition"
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"/plugins/policies": {
|
|
58
|
+
GET: {
|
|
59
|
+
event: auditLogger.ListPluginPoliciesEvents.GET_PLUGINS_POLICIES_ERROR,
|
|
60
|
+
message: "Failed to get list permission policies"
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"/plugins/condition-rules": {
|
|
64
|
+
GET: {
|
|
65
|
+
event: auditLogger.ListConditionEvents.GET_CONDITION_RULES_ERROR,
|
|
66
|
+
message: "Failed to get list condition rules and schemas"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
function auditError(auditLogger$1) {
|
|
71
|
+
return async (err, req, _resp, next) => {
|
|
72
|
+
const matchedPath = Object.keys(eventMap).find(
|
|
73
|
+
(path) => req.path.startsWith(path)
|
|
74
|
+
);
|
|
75
|
+
if (matchedPath) {
|
|
76
|
+
const methodEvents = eventMap[matchedPath][req.method];
|
|
77
|
+
if (methodEvents) {
|
|
78
|
+
const { event, message } = methodEvents;
|
|
79
|
+
try {
|
|
80
|
+
await auditLogger$1.auditLog({
|
|
81
|
+
message,
|
|
82
|
+
eventName: event,
|
|
83
|
+
stage: auditLogger.RESPONSE_ERROR,
|
|
84
|
+
status: "failed",
|
|
85
|
+
request: req,
|
|
86
|
+
errors: [err]
|
|
87
|
+
});
|
|
88
|
+
next(err);
|
|
89
|
+
} catch (auditLogError) {
|
|
90
|
+
next(auditLogError);
|
|
91
|
+
}
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
next(err);
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
exports.auditError = auditError;
|
|
100
|
+
//# sourceMappingURL=rest-errors-interceptor.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rest-errors-interceptor.cjs.js","sources":["../../src/audit-log/rest-errors-interceptor.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { AuditLogger } from '@janus-idp/backstage-plugin-audit-log-node';\nimport type {\n ErrorRequestHandler,\n NextFunction,\n Request,\n Response,\n} from 'express';\n\nimport {\n ConditionEvents,\n ListConditionEvents,\n ListPluginPoliciesEvents,\n PermissionEvents,\n RESPONSE_ERROR,\n RoleEvents,\n} from './audit-logger';\n\n// Mapping paths and methods to corresponding events and messages\nconst eventMap: {\n [key: string]: { [key: string]: { event: string; message: string } };\n} = {\n '/policies': {\n POST: {\n event: PermissionEvents.CREATE_POLICY_ERROR,\n message: 'Failed to create permission policies',\n },\n PUT: {\n event: PermissionEvents.UPDATE_POLICY_ERROR,\n message: 'Failed to update permission policies',\n },\n DELETE: {\n event: PermissionEvents.DELETE_POLICY_ERROR,\n message: 'Failed to delete permission policies',\n },\n GET: {\n event: PermissionEvents.GET_POLICY_ERROR,\n message: 'Failed to get permission policies',\n },\n },\n '/roles': {\n POST: {\n event: RoleEvents.CREATE_ROLE_ERROR,\n message: 'Failed to create role',\n },\n PUT: {\n event: RoleEvents.UPDATE_ROLE_ERROR,\n message: 'Failed to update role',\n },\n DELETE: {\n event: RoleEvents.DELETE_ROLE_ERROR,\n message: 'Failed to delete role',\n },\n GET: { event: RoleEvents.GET_ROLE_ERROR, message: 'Failed to get role' },\n },\n '/roles/conditions': {\n POST: {\n event: ConditionEvents.CREATE_CONDITION_ERROR,\n message: 'Failed to create condition',\n },\n PUT: {\n event: ConditionEvents.UPDATE_CONDITION_ERROR,\n message: 'Failed to update condition',\n },\n DELETE: {\n event: ConditionEvents.DELETE_CONDITION_ERROR,\n message: 'Failed to delete condition',\n },\n GET: {\n event: ConditionEvents.GET_CONDITION_ERROR,\n message: 'Failed to get condition',\n },\n },\n '/plugins/policies': {\n GET: {\n event: ListPluginPoliciesEvents.GET_PLUGINS_POLICIES_ERROR,\n message: 'Failed to get list permission policies',\n },\n },\n '/plugins/condition-rules': {\n GET: {\n event: ListConditionEvents.GET_CONDITION_RULES_ERROR,\n message: 'Failed to get list condition rules and schemas',\n },\n },\n};\n\n// Audit log REST api errors interceptor.\nexport function auditError(auditLogger: AuditLogger): ErrorRequestHandler {\n return async (\n err: Error,\n req: Request,\n _resp: Response,\n next: NextFunction,\n ) => {\n const matchedPath = Object.keys(eventMap).find(path =>\n req.path.startsWith(path),\n );\n if (matchedPath) {\n const methodEvents = eventMap[matchedPath][req.method];\n if (methodEvents) {\n const { event, message } = methodEvents;\n try {\n await auditLogger.auditLog({\n message,\n eventName: event,\n stage: RESPONSE_ERROR,\n status: 'failed',\n request: req,\n errors: [err],\n });\n next(err);\n } catch (auditLogError) {\n next(auditLogError);\n }\n return;\n }\n }\n next(err);\n };\n}\n"],"names":["PermissionEvents","RoleEvents","ConditionEvents","ListPluginPoliciesEvents","ListConditionEvents","auditLogger","RESPONSE_ERROR"],"mappings":"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"}
|