@backstage-community/plugin-catalog-backend-module-keycloak 3.1.1
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 +519 -0
- package/README.md +171 -0
- package/app-config.yaml +11 -0
- package/config.d.ts +88 -0
- package/dist/extensions.cjs.js +10 -0
- package/dist/extensions.cjs.js.map +1 -0
- package/dist/index.cjs.js +18 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.d.ts +219 -0
- package/dist/lib/config.cjs.js +63 -0
- package/dist/lib/config.cjs.js.map +1 -0
- package/dist/lib/constants.cjs.js +10 -0
- package/dist/lib/constants.cjs.js.map +1 -0
- package/dist/lib/read.cjs.js +239 -0
- package/dist/lib/read.cjs.js.map +1 -0
- package/dist/lib/transformers.cjs.js +13 -0
- package/dist/lib/transformers.cjs.js.map +1 -0
- package/dist/module/catalogModuleKeycloakEntityProvider.cjs.js +57 -0
- package/dist/module/catalogModuleKeycloakEntityProvider.cjs.js.map +1 -0
- package/dist/providers/KeycloakOrgEntityProvider.cjs.js +201 -0
- package/dist/providers/KeycloakOrgEntityProvider.cjs.js.map +1 -0
- package/package.json +90 -0
package/README.md
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# Keycloak backend plugin for Backstage
|
|
2
|
+
|
|
3
|
+
The Keycloak backend plugin integrates Keycloak into Backstage.
|
|
4
|
+
|
|
5
|
+
## Capabilities
|
|
6
|
+
|
|
7
|
+
The Keycloak backend plugin has the following capabilities:
|
|
8
|
+
|
|
9
|
+
- Synchronization of Keycloak users in a realm
|
|
10
|
+
- Synchronization of Keycloak groups and their users in a realm
|
|
11
|
+
|
|
12
|
+
## For administrators
|
|
13
|
+
|
|
14
|
+
### Installation
|
|
15
|
+
|
|
16
|
+
Install the Backstage package into the backend. When not integrating with a published package, clone the repository locally and add the Backstage as follows:
|
|
17
|
+
|
|
18
|
+
```console
|
|
19
|
+
yarn workspace backend add @backstage-community/plugin-catalog-backend-module-keycloak
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Configuration
|
|
23
|
+
|
|
24
|
+
#### New Backend Configuration
|
|
25
|
+
|
|
26
|
+
1. Add the following configuration to the `app-config.yaml` file. The default schedule is a frequency of 30 minutes and a timeout of 3 minutes, please configure the schedule in the `app-config.yaml` as per your requirement.
|
|
27
|
+
|
|
28
|
+
```yaml title="app-config.yaml"
|
|
29
|
+
catalog:
|
|
30
|
+
providers:
|
|
31
|
+
keycloakOrg:
|
|
32
|
+
default:
|
|
33
|
+
baseUrl: https://<keycloak_host>
|
|
34
|
+
loginRealm: ${KEYCLOAK_REALM}
|
|
35
|
+
realm: ${KEYCLOAK_REALM}
|
|
36
|
+
clientId: ${KEYCLOAK_CLIENTID}
|
|
37
|
+
clientSecret: ${KEYCLOAK_CLIENTSECRET}
|
|
38
|
+
schedule: # Optional (defaults to the configurations below if not provided); same options as in TaskScheduleDefinition
|
|
39
|
+
# supports cron, ISO duration, "human duration" as used in code
|
|
40
|
+
frequency: { minutes: 30 } # Customize this to fit your needs
|
|
41
|
+
# supports ISO duration, "human duration" as used in code
|
|
42
|
+
timeout: { minutes: 3 } # Customize this to fit your needs
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
1. Register the plugin in the `packages/backend/src/index.ts` file:
|
|
46
|
+
|
|
47
|
+
```ts title="packages/backend/src/index.ts"
|
|
48
|
+
const backend = createBackend();
|
|
49
|
+
|
|
50
|
+
/* highlight-add-next-line */
|
|
51
|
+
backend.add(
|
|
52
|
+
import('@backstage-community/plugin-catalog-backend-module-keycloak'),
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
backend.start();
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
1. Optional: To configure custom transformer function for user/group to mutate the entity generated by the catalog-backend-module-keycloak. Create a new backend module with the `yarn new` command and add your custom user and group transformers to the `keycloakTransformerExtensionPoint`. Then install this new backend module into your backstage backend. Below is an example of how the backend module can be defined:
|
|
59
|
+
|
|
60
|
+
```ts title="plugins/<module-name>/src/module.ts"
|
|
61
|
+
/* highlight-add-start */
|
|
62
|
+
import {
|
|
63
|
+
GroupTransformer,
|
|
64
|
+
keycloakTransformerExtensionPoint,
|
|
65
|
+
UserTransformer,
|
|
66
|
+
} from '@backstage-community/plugin-catalog-backend-module-keycloak';
|
|
67
|
+
|
|
68
|
+
const customGroupTransformer: GroupTransformer = async (
|
|
69
|
+
entity,
|
|
70
|
+
realm,
|
|
71
|
+
groups,
|
|
72
|
+
) => {
|
|
73
|
+
/* apply transformations */
|
|
74
|
+
return entity;
|
|
75
|
+
};
|
|
76
|
+
const customUserTransformer: UserTransformer = async (
|
|
77
|
+
entity,
|
|
78
|
+
user,
|
|
79
|
+
realm,
|
|
80
|
+
groups,
|
|
81
|
+
) => {
|
|
82
|
+
/* apply transformations */
|
|
83
|
+
return entity;
|
|
84
|
+
};
|
|
85
|
+
/* highlight-add-end */
|
|
86
|
+
|
|
87
|
+
export const keycloakBackendModuleTransformer = createBackendModule({
|
|
88
|
+
pluginId: 'catalog',
|
|
89
|
+
moduleId: 'keycloak-transformer',
|
|
90
|
+
register(reg) {
|
|
91
|
+
reg.registerInit({
|
|
92
|
+
deps: {
|
|
93
|
+
/* highlight-add-start */
|
|
94
|
+
keycloak: keycloakTransformerExtensionPoint,
|
|
95
|
+
/* highlight-add-end */
|
|
96
|
+
},
|
|
97
|
+
/* highlight-add-start */
|
|
98
|
+
async init({ keycloak }) {
|
|
99
|
+
keycloak.setUserTransformer(customUserTransformer);
|
|
100
|
+
keycloak.setGroupTransformer(customGroupTransformer);
|
|
101
|
+
/* highlight-add-end */
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
***
|
|
109
|
+
|
|
110
|
+
**IMPORTANT**
|
|
111
|
+
|
|
112
|
+
The `pluginId` for the module **MUST** be set to `catalog` to match the `pluginId` of the `catalog-backend-module-keycloak` or else the module will fail to initialize.
|
|
113
|
+
|
|
114
|
+
***
|
|
115
|
+
|
|
116
|
+
Communication between Backstage and Keycloak is enabled by using the Keycloak API. Username/password or client credentials are supported authentication methods.
|
|
117
|
+
|
|
118
|
+
The following table describes the parameters that you can configure to enable the plugin under `catalog.providers.keycloakOrg.<ENVIRONMENT_NAME>` object in the `app-config.yaml` file:
|
|
119
|
+
|
|
120
|
+
| Name | Description | Default Value | Required |
|
|
121
|
+
| ---------------- | ------------------------------------------------------------------ | ------------- | ---------------------------------------------------- |
|
|
122
|
+
| `baseUrl` | Location of the Keycloak server, such as `https://localhost:8443`. | "" | Yes |
|
|
123
|
+
| `realm` | Realm to synchronize | `master` | No |
|
|
124
|
+
| `loginRealm` | Realm used to authenticate | `master` | No |
|
|
125
|
+
| `username` | Username to authenticate | "" | Yes if using password based authentication |
|
|
126
|
+
| `password` | Password to authenticate | "" | Yes if using password based authentication |
|
|
127
|
+
| `clientId` | Client ID to authenticate | "" | Yes if using client credentials based authentication |
|
|
128
|
+
| `clientSecret` | Client Secret to authenticate | "" | Yes if using client credentials based authentication |
|
|
129
|
+
| `userQuerySize` | Number of users to query at a time | `100` | No |
|
|
130
|
+
| `groupQuerySize` | Number of groups to query at a time | `100` | No |
|
|
131
|
+
|
|
132
|
+
When using client credentials, the access type must be set to `confidential` and service accounts must be enabled. You must also add the following roles from the `realm-management` client role:
|
|
133
|
+
|
|
134
|
+
- `query-groups`
|
|
135
|
+
- `query-users`
|
|
136
|
+
- `view-users`
|
|
137
|
+
|
|
138
|
+
### Limitations
|
|
139
|
+
|
|
140
|
+
If you have self-signed or corporate certificate issues, you can set the following environment variable before starting Backstage:
|
|
141
|
+
|
|
142
|
+
`NODE_TLS_REJECT_UNAUTHORIZED=0`
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
**NOTE**
|
|
147
|
+
The solution of setting the `NODE_TLS_REJECT_UNAUTHORIZED` environment variable is not recommended.
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## For users
|
|
152
|
+
|
|
153
|
+
### Imported users and groups in Backstage using Keycloak plugin
|
|
154
|
+
|
|
155
|
+
After configuring the plugin successfully, the plugin imports the users and groups each time when started.
|
|
156
|
+
|
|
157
|
+
After the first import is complete, you can select **User** to list the users from the catalog page:
|
|
158
|
+
|
|
159
|
+

|
|
160
|
+
|
|
161
|
+
You can see the list of users on the page:
|
|
162
|
+
|
|
163
|
+

|
|
164
|
+
|
|
165
|
+
When you select a user, you can see the information imported from Keycloak:
|
|
166
|
+
|
|
167
|
+

|
|
168
|
+
|
|
169
|
+
You can also select a group, view the list, and select or view the information imported from Keycloak for a group:
|
|
170
|
+
|
|
171
|
+

|
package/app-config.yaml
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
enabled:
|
|
2
|
+
keycloak: true
|
|
3
|
+
catalog:
|
|
4
|
+
providers:
|
|
5
|
+
keycloakOrg:
|
|
6
|
+
default:
|
|
7
|
+
baseUrl: '${KEYCLOAK_BASE_URL}'
|
|
8
|
+
loginRealm: '${KEYCLOAK_LOGIN_REALM}'
|
|
9
|
+
realm: '${KEYCLOAK_REALM}'
|
|
10
|
+
clientId: '${KEYCLOAK_CLIENT_ID}'
|
|
11
|
+
clientSecret: '${KEYCLOAK_CLIENT_SECRET}'
|
package/config.d.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
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
|
+
import type { SchedulerServiceTaskScheduleDefinitionConfig } from '@backstage/backend-plugin-api';
|
|
17
|
+
|
|
18
|
+
export interface Config {
|
|
19
|
+
catalog?: {
|
|
20
|
+
providers?: {
|
|
21
|
+
keycloakOrg?: {
|
|
22
|
+
[key: string]: {
|
|
23
|
+
/**
|
|
24
|
+
* KeycloakOrgConfig
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* Location of the Keycloak instance
|
|
28
|
+
*/
|
|
29
|
+
baseUrl: string;
|
|
30
|
+
/**
|
|
31
|
+
* Keycloak realm name. This realm is scraped and entities are
|
|
32
|
+
*/
|
|
33
|
+
realm?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Keycloak realm name. This realm is used for authentication using the credentials below.
|
|
36
|
+
*/
|
|
37
|
+
loginRealm?: string;
|
|
38
|
+
/**
|
|
39
|
+
* The number of users to query at a time.
|
|
40
|
+
* @defaultValue 100
|
|
41
|
+
* @remarks
|
|
42
|
+
* This is a performance optimization to avoid querying too many users at once.
|
|
43
|
+
* @see https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_users_resource
|
|
44
|
+
*/
|
|
45
|
+
userQuerySize?: number;
|
|
46
|
+
/**
|
|
47
|
+
* The number of groups to query at a time.
|
|
48
|
+
* @defaultValue 100
|
|
49
|
+
* @remarks
|
|
50
|
+
* This is a performance optimization to avoid querying too many groups at once.
|
|
51
|
+
* @see https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_groups_resource
|
|
52
|
+
*/
|
|
53
|
+
groupQuerySize?: number;
|
|
54
|
+
schedule?: SchedulerServiceTaskScheduleDefinitionConfig;
|
|
55
|
+
} & (
|
|
56
|
+
| {
|
|
57
|
+
/**
|
|
58
|
+
* KeycloakClientCredentials
|
|
59
|
+
*/
|
|
60
|
+
/**
|
|
61
|
+
* Keycloak credentials. Use together with "password".
|
|
62
|
+
*/
|
|
63
|
+
username: string;
|
|
64
|
+
/**
|
|
65
|
+
* Keycloak credentials. Use together with "username".
|
|
66
|
+
* @visibility secret
|
|
67
|
+
*/
|
|
68
|
+
password: string;
|
|
69
|
+
}
|
|
70
|
+
| {
|
|
71
|
+
/**
|
|
72
|
+
* KeycloakClientCredentials
|
|
73
|
+
*/
|
|
74
|
+
/**
|
|
75
|
+
* Keycloak credentials. Use together with "clientSecret".
|
|
76
|
+
*/
|
|
77
|
+
clientId: string;
|
|
78
|
+
/**
|
|
79
|
+
* Keycloak credentials. Use together with "clientId".
|
|
80
|
+
* @visibility secret
|
|
81
|
+
*/
|
|
82
|
+
clientSecret: string;
|
|
83
|
+
}
|
|
84
|
+
);
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var backendPluginApi = require('@backstage/backend-plugin-api');
|
|
4
|
+
|
|
5
|
+
const keycloakTransformerExtensionPoint = backendPluginApi.createExtensionPoint({
|
|
6
|
+
id: "keycloak.transformer"
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
exports.keycloakTransformerExtensionPoint = keycloakTransformerExtensionPoint;
|
|
10
|
+
//# sourceMappingURL=extensions.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extensions.cjs.js","sources":["../src/extensions.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 { createExtensionPoint } from '@backstage/backend-plugin-api';\n\nimport type { GroupTransformer, UserTransformer } from './lib/types';\n\n/**\n * An extension point that exposes the ability to implement user and group transformer functions for keycloak.\n *\n * @public\n */\nexport const keycloakTransformerExtensionPoint =\n createExtensionPoint<KeycloakTransformerExtensionPoint>({\n id: 'keycloak.transformer',\n });\n\n/**\n * The interface for {@link keycloakTransformerExtensionPoint}.\n *\n * @public\n */\nexport type KeycloakTransformerExtensionPoint = {\n setUserTransformer(userTransformer: UserTransformer): void;\n setGroupTransformer(groupTransformer: GroupTransformer): void;\n};\n"],"names":["createExtensionPoint"],"mappings":";;;;AAwBO,MAAM,oCACXA,qCAAwD,CAAA;AAAA,EACtD,EAAI,EAAA,sBAAA;AACN,CAAC;;;;"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var KeycloakOrgEntityProvider = require('./providers/KeycloakOrgEntityProvider.cjs.js');
|
|
6
|
+
var transformers = require('./lib/transformers.cjs.js');
|
|
7
|
+
var extensions = require('./extensions.cjs.js');
|
|
8
|
+
var catalogModuleKeycloakEntityProvider = require('./module/catalogModuleKeycloakEntityProvider.cjs.js');
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
exports.KeycloakOrgEntityProvider = KeycloakOrgEntityProvider.KeycloakOrgEntityProvider;
|
|
13
|
+
exports.noopGroupTransformer = transformers.noopGroupTransformer;
|
|
14
|
+
exports.noopUserTransformer = transformers.noopUserTransformer;
|
|
15
|
+
exports.sanitizeEmailTransformer = transformers.sanitizeEmailTransformer;
|
|
16
|
+
exports.keycloakTransformerExtensionPoint = extensions.keycloakTransformerExtensionPoint;
|
|
17
|
+
exports.default = catalogModuleKeycloakEntityProvider.catalogModuleKeycloakEntityProvider;
|
|
18
|
+
//# sourceMappingURL=index.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import * as _backstage_backend_plugin_api from '@backstage/backend-plugin-api';
|
|
2
|
+
import { SchedulerServiceTaskScheduleDefinition, SchedulerServiceTaskRunner, SchedulerService, LoggerService } from '@backstage/backend-plugin-api';
|
|
3
|
+
import { Config } from '@backstage/config';
|
|
4
|
+
import { EntityProvider, EntityProviderConnection } from '@backstage/plugin-catalog-node';
|
|
5
|
+
import { GroupEntity, UserEntity } from '@backstage/catalog-model';
|
|
6
|
+
import GroupRepresentation from '@keycloak/keycloak-admin-client/lib/defs/groupRepresentation';
|
|
7
|
+
import UserRepresentation from '@keycloak/keycloak-admin-client/lib/defs/userRepresentation';
|
|
8
|
+
|
|
9
|
+
interface GroupRepresentationWithParent extends GroupRepresentation {
|
|
10
|
+
parentId?: string;
|
|
11
|
+
parent?: string;
|
|
12
|
+
members?: string[];
|
|
13
|
+
}
|
|
14
|
+
interface GroupRepresentationWithParentAndEntity extends GroupRepresentationWithParent {
|
|
15
|
+
entity: GroupEntity;
|
|
16
|
+
}
|
|
17
|
+
interface UserRepresentationWithEntity extends UserRepresentation {
|
|
18
|
+
entity: UserEntity;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Customize the ingested User entity
|
|
22
|
+
*
|
|
23
|
+
* @public
|
|
24
|
+
*
|
|
25
|
+
* @param {UserEntity} entity The output of the default parser
|
|
26
|
+
* @param {UserRepresentation} user Keycloak user representation
|
|
27
|
+
* @param {string} realm Realm name
|
|
28
|
+
* @param {GroupRepresentationWithParentAndEntity[]} groups Data about available groups (can be used to create additional relationships)
|
|
29
|
+
*
|
|
30
|
+
* @returns {Promise<UserEntity | undefined>} Resolve to a modified `UserEntity` object that will be ingested into the catalog or resolve to `undefined` to reject the entity
|
|
31
|
+
*/
|
|
32
|
+
type UserTransformer = (entity: UserEntity, user: UserRepresentation, realm: string, groups: GroupRepresentationWithParentAndEntity[]) => Promise<UserEntity | undefined>;
|
|
33
|
+
/**
|
|
34
|
+
* Customize the ingested Group entity
|
|
35
|
+
*
|
|
36
|
+
* @public
|
|
37
|
+
*
|
|
38
|
+
* @param {GroupEntity} entity The output of the default parser
|
|
39
|
+
* @param {GroupRepresentation} group Keycloak group representation
|
|
40
|
+
* @param {string} realm Realm name
|
|
41
|
+
*
|
|
42
|
+
* @returns {Promise<GroupEntity | undefined>} Resolve to a modified `GroupEntity` object that will be ingested into the catalog or resolve to `undefined` to reject the entity
|
|
43
|
+
*/
|
|
44
|
+
type GroupTransformer = (entity: GroupEntity, group: GroupRepresentation, realm: string) => Promise<GroupEntity | undefined>;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The configuration parameters for a single Keycloak provider.
|
|
48
|
+
*
|
|
49
|
+
* @public
|
|
50
|
+
*/
|
|
51
|
+
type KeycloakProviderConfig = {
|
|
52
|
+
/**
|
|
53
|
+
* Identifier of the provider which will be used i.e. at the location key for ingested entities.
|
|
54
|
+
*/
|
|
55
|
+
id: string;
|
|
56
|
+
/**
|
|
57
|
+
* The Keycloak base URL
|
|
58
|
+
*/
|
|
59
|
+
baseUrl: string;
|
|
60
|
+
/**
|
|
61
|
+
* The username to use for authenticating requests
|
|
62
|
+
* If specified, password must also be specified
|
|
63
|
+
*/
|
|
64
|
+
username?: string;
|
|
65
|
+
/**
|
|
66
|
+
* The password to use for authenticating requests
|
|
67
|
+
* If specified, username must also be specified
|
|
68
|
+
*/
|
|
69
|
+
password?: string;
|
|
70
|
+
/**
|
|
71
|
+
* The clientId to use for authenticating requests
|
|
72
|
+
* If specified, clientSecret must also be specified
|
|
73
|
+
*/
|
|
74
|
+
clientId?: string;
|
|
75
|
+
/**
|
|
76
|
+
* The clientSecret to use for authenticating requests
|
|
77
|
+
* If specified, clientId must also be specified
|
|
78
|
+
*/
|
|
79
|
+
clientSecret?: string;
|
|
80
|
+
/**
|
|
81
|
+
* name of the Keycloak realm
|
|
82
|
+
*/
|
|
83
|
+
realm: string;
|
|
84
|
+
/**
|
|
85
|
+
* name of the Keycloak login realm
|
|
86
|
+
*/
|
|
87
|
+
loginRealm?: string;
|
|
88
|
+
/**
|
|
89
|
+
* Schedule configuration for refresh tasks.
|
|
90
|
+
*/
|
|
91
|
+
schedule?: SchedulerServiceTaskScheduleDefinition;
|
|
92
|
+
/**
|
|
93
|
+
* The number of users to query at a time.
|
|
94
|
+
* @defaultValue 100
|
|
95
|
+
* @remarks
|
|
96
|
+
* This is a performance optimization to avoid querying too many users at once.
|
|
97
|
+
* @see https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_users_resource
|
|
98
|
+
*/
|
|
99
|
+
userQuerySize?: number;
|
|
100
|
+
/**
|
|
101
|
+
* The number of groups to query at a time.
|
|
102
|
+
* @defaultValue 100
|
|
103
|
+
* @remarks
|
|
104
|
+
* This is a performance optimization to avoid querying too many groups at once.
|
|
105
|
+
* @see https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_groups_resource
|
|
106
|
+
*/
|
|
107
|
+
groupQuerySize?: number;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Options for {@link KeycloakOrgEntityProvider}.
|
|
112
|
+
*
|
|
113
|
+
* @public
|
|
114
|
+
*/
|
|
115
|
+
interface KeycloakOrgEntityProviderOptions {
|
|
116
|
+
/**
|
|
117
|
+
* A unique, stable identifier for this provider.
|
|
118
|
+
*
|
|
119
|
+
* @example "production"
|
|
120
|
+
*/
|
|
121
|
+
id: string;
|
|
122
|
+
/**
|
|
123
|
+
* The refresh schedule to use.
|
|
124
|
+
* @remarks
|
|
125
|
+
*
|
|
126
|
+
* You can pass in the result of
|
|
127
|
+
* {@link @backstage/backend-plugin-api#SchedulerService.createScheduledTaskRunner}
|
|
128
|
+
* to enable automatic scheduling of tasks.
|
|
129
|
+
*/
|
|
130
|
+
schedule?: SchedulerServiceTaskRunner;
|
|
131
|
+
/**
|
|
132
|
+
* Scheduler used to schedule refreshes based on
|
|
133
|
+
* the schedule config.
|
|
134
|
+
*/
|
|
135
|
+
scheduler?: SchedulerService;
|
|
136
|
+
/**
|
|
137
|
+
* The logger to use.
|
|
138
|
+
*/
|
|
139
|
+
logger: LoggerService;
|
|
140
|
+
/**
|
|
141
|
+
* The function that transforms a user entry in LDAP to an entity.
|
|
142
|
+
*/
|
|
143
|
+
userTransformer?: UserTransformer;
|
|
144
|
+
/**
|
|
145
|
+
* The function that transforms a group entry in LDAP to an entity.
|
|
146
|
+
*/
|
|
147
|
+
groupTransformer?: GroupTransformer;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Ingests org data (users and groups) from GitHub.
|
|
151
|
+
*
|
|
152
|
+
* @public
|
|
153
|
+
*/
|
|
154
|
+
declare class KeycloakOrgEntityProvider implements EntityProvider {
|
|
155
|
+
private options;
|
|
156
|
+
private connection?;
|
|
157
|
+
private scheduleFn?;
|
|
158
|
+
static fromConfig(deps: {
|
|
159
|
+
config: Config;
|
|
160
|
+
logger: LoggerService;
|
|
161
|
+
}, options: ({
|
|
162
|
+
schedule: SchedulerServiceTaskRunner;
|
|
163
|
+
} | {
|
|
164
|
+
scheduler: SchedulerService;
|
|
165
|
+
}) & {
|
|
166
|
+
userTransformer?: UserTransformer;
|
|
167
|
+
groupTransformer?: GroupTransformer;
|
|
168
|
+
}): KeycloakOrgEntityProvider[];
|
|
169
|
+
constructor(options: {
|
|
170
|
+
id: string;
|
|
171
|
+
provider: KeycloakProviderConfig;
|
|
172
|
+
logger: LoggerService;
|
|
173
|
+
taskRunner: SchedulerServiceTaskRunner;
|
|
174
|
+
userTransformer?: UserTransformer;
|
|
175
|
+
groupTransformer?: GroupTransformer;
|
|
176
|
+
});
|
|
177
|
+
getProviderName(): string;
|
|
178
|
+
connect(connection: EntityProviderConnection): Promise<void>;
|
|
179
|
+
/**
|
|
180
|
+
* Runs one complete ingestion loop. Call this method regularly at some
|
|
181
|
+
* appropriate cadence.
|
|
182
|
+
*/
|
|
183
|
+
read(options?: {
|
|
184
|
+
logger?: LoggerService;
|
|
185
|
+
}): Promise<void>;
|
|
186
|
+
schedule(taskRunner: SchedulerServiceTaskRunner): void;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
declare const noopGroupTransformer: GroupTransformer;
|
|
190
|
+
declare const noopUserTransformer: UserTransformer;
|
|
191
|
+
/**
|
|
192
|
+
* User transformer that sanitizes .metadata.name from email address to a valid name
|
|
193
|
+
*/
|
|
194
|
+
declare const sanitizeEmailTransformer: UserTransformer;
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* An extension point that exposes the ability to implement user and group transformer functions for keycloak.
|
|
198
|
+
*
|
|
199
|
+
* @public
|
|
200
|
+
*/
|
|
201
|
+
declare const keycloakTransformerExtensionPoint: _backstage_backend_plugin_api.ExtensionPoint<KeycloakTransformerExtensionPoint>;
|
|
202
|
+
/**
|
|
203
|
+
* The interface for {@link keycloakTransformerExtensionPoint}.
|
|
204
|
+
*
|
|
205
|
+
* @public
|
|
206
|
+
*/
|
|
207
|
+
type KeycloakTransformerExtensionPoint = {
|
|
208
|
+
setUserTransformer(userTransformer: UserTransformer): void;
|
|
209
|
+
setGroupTransformer(groupTransformer: GroupTransformer): void;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Registers the `KeycloakEntityProvider` with the catalog processing extension point.
|
|
214
|
+
*
|
|
215
|
+
* @alpha
|
|
216
|
+
*/
|
|
217
|
+
declare const catalogModuleKeycloakEntityProvider: _backstage_backend_plugin_api.BackendFeature;
|
|
218
|
+
|
|
219
|
+
export { type GroupRepresentationWithParent, type GroupRepresentationWithParentAndEntity, type GroupTransformer, KeycloakOrgEntityProvider, type KeycloakOrgEntityProviderOptions, type KeycloakTransformerExtensionPoint, type UserRepresentationWithEntity, type UserTransformer, catalogModuleKeycloakEntityProvider as default, keycloakTransformerExtensionPoint, noopGroupTransformer, noopUserTransformer, sanitizeEmailTransformer };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var backendPluginApi = require('@backstage/backend-plugin-api');
|
|
4
|
+
var errors = require('@backstage/errors');
|
|
5
|
+
|
|
6
|
+
const readProviderConfig = (id, providerConfigInstance) => {
|
|
7
|
+
const baseUrl = providerConfigInstance.getString("baseUrl");
|
|
8
|
+
const realm = providerConfigInstance.getOptionalString("realm") ?? "master";
|
|
9
|
+
const loginRealm = providerConfigInstance.getOptionalString("loginRealm") ?? "master";
|
|
10
|
+
const username = providerConfigInstance.getOptionalString("username");
|
|
11
|
+
const password = providerConfigInstance.getOptionalString("password");
|
|
12
|
+
const clientId = providerConfigInstance.getOptionalString("clientId");
|
|
13
|
+
const clientSecret = providerConfigInstance.getOptionalString("clientSecret");
|
|
14
|
+
const userQuerySize = providerConfigInstance.getOptionalNumber("userQuerySize");
|
|
15
|
+
const groupQuerySize = providerConfigInstance.getOptionalNumber("groupQuerySize");
|
|
16
|
+
if (clientId && !clientSecret) {
|
|
17
|
+
throw new errors.InputError(
|
|
18
|
+
`clientSecret must be provided when clientId is defined.`
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
if (clientSecret && !clientId) {
|
|
22
|
+
throw new errors.InputError(
|
|
23
|
+
`clientId must be provided when clientSecret is defined.`
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
if (username && !password) {
|
|
27
|
+
throw new errors.InputError(`password must be provided when username is defined.`);
|
|
28
|
+
}
|
|
29
|
+
if (password && !username) {
|
|
30
|
+
throw new errors.InputError(`username must be provided when password is defined.`);
|
|
31
|
+
}
|
|
32
|
+
const schedule = providerConfigInstance.has("schedule") ? backendPluginApi.readSchedulerServiceTaskScheduleDefinitionFromConfig(
|
|
33
|
+
providerConfigInstance.getConfig("schedule")
|
|
34
|
+
) : void 0;
|
|
35
|
+
return {
|
|
36
|
+
id,
|
|
37
|
+
baseUrl,
|
|
38
|
+
loginRealm,
|
|
39
|
+
realm,
|
|
40
|
+
username,
|
|
41
|
+
password,
|
|
42
|
+
clientId,
|
|
43
|
+
clientSecret,
|
|
44
|
+
schedule,
|
|
45
|
+
userQuerySize,
|
|
46
|
+
groupQuerySize
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
const readProviderConfigs = (config) => {
|
|
50
|
+
const providersConfig = config.getOptionalConfig(
|
|
51
|
+
"catalog.providers.keycloakOrg"
|
|
52
|
+
);
|
|
53
|
+
if (!providersConfig) {
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
return providersConfig.keys().map((id) => {
|
|
57
|
+
const providerConfigInstance = providersConfig.getConfig(id);
|
|
58
|
+
return readProviderConfig(id, providerConfigInstance);
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
exports.readProviderConfigs = readProviderConfigs;
|
|
63
|
+
//# sourceMappingURL=config.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.cjs.js","sources":["../../src/lib/config.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 */\n\nimport { readSchedulerServiceTaskScheduleDefinitionFromConfig } from '@backstage/backend-plugin-api';\nimport type { SchedulerServiceTaskScheduleDefinition } from '@backstage/backend-plugin-api';\nimport type { Config } from '@backstage/config';\nimport { InputError } from '@backstage/errors';\n\n/**\n * The configuration parameters for a single Keycloak provider.\n *\n * @public\n */\nexport type KeycloakProviderConfig = {\n /**\n * Identifier of the provider which will be used i.e. at the location key for ingested entities.\n */\n id: string;\n\n /**\n * The Keycloak base URL\n */\n baseUrl: string;\n\n /**\n * The username to use for authenticating requests\n * If specified, password must also be specified\n */\n username?: string;\n\n /**\n * The password to use for authenticating requests\n * If specified, username must also be specified\n */\n password?: string;\n\n /**\n * The clientId to use for authenticating requests\n * If specified, clientSecret must also be specified\n */\n clientId?: string;\n\n /**\n * The clientSecret to use for authenticating requests\n * If specified, clientId must also be specified\n */\n clientSecret?: string;\n\n /**\n * name of the Keycloak realm\n */\n realm: string;\n\n /**\n * name of the Keycloak login realm\n */\n loginRealm?: string;\n\n /**\n * Schedule configuration for refresh tasks.\n */\n schedule?: SchedulerServiceTaskScheduleDefinition;\n\n /**\n * The number of users to query at a time.\n * @defaultValue 100\n * @remarks\n * This is a performance optimization to avoid querying too many users at once.\n * @see https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_users_resource\n */\n userQuerySize?: number;\n\n /**\n * The number of groups to query at a time.\n * @defaultValue 100\n * @remarks\n * This is a performance optimization to avoid querying too many groups at once.\n * @see https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_groups_resource\n */\n groupQuerySize?: number;\n};\n\nconst readProviderConfig = (\n id: string,\n providerConfigInstance: Config,\n): KeycloakProviderConfig => {\n const baseUrl = providerConfigInstance.getString('baseUrl');\n const realm = providerConfigInstance.getOptionalString('realm') ?? 'master';\n const loginRealm =\n providerConfigInstance.getOptionalString('loginRealm') ?? 'master';\n const username = providerConfigInstance.getOptionalString('username');\n const password = providerConfigInstance.getOptionalString('password');\n const clientId = providerConfigInstance.getOptionalString('clientId');\n const clientSecret = providerConfigInstance.getOptionalString('clientSecret');\n const userQuerySize =\n providerConfigInstance.getOptionalNumber('userQuerySize');\n const groupQuerySize =\n providerConfigInstance.getOptionalNumber('groupQuerySize');\n\n if (clientId && !clientSecret) {\n throw new InputError(\n `clientSecret must be provided when clientId is defined.`,\n );\n }\n\n if (clientSecret && !clientId) {\n throw new InputError(\n `clientId must be provided when clientSecret is defined.`,\n );\n }\n\n if (username && !password) {\n throw new InputError(`password must be provided when username is defined.`);\n }\n\n if (password && !username) {\n throw new InputError(`username must be provided when password is defined.`);\n }\n\n const schedule = providerConfigInstance.has('schedule')\n ? readSchedulerServiceTaskScheduleDefinitionFromConfig(\n providerConfigInstance.getConfig('schedule'),\n )\n : undefined;\n\n return {\n id,\n baseUrl,\n loginRealm,\n realm,\n username,\n password,\n clientId,\n clientSecret,\n schedule,\n userQuerySize,\n groupQuerySize,\n };\n};\n\nexport const readProviderConfigs = (\n config: Config,\n): KeycloakProviderConfig[] => {\n const providersConfig = config.getOptionalConfig(\n 'catalog.providers.keycloakOrg',\n );\n if (!providersConfig) {\n return [];\n }\n return providersConfig.keys().map(id => {\n const providerConfigInstance = providersConfig.getConfig(id);\n return readProviderConfig(id, providerConfigInstance);\n });\n};\n"],"names":["InputError","readSchedulerServiceTaskScheduleDefinitionFromConfig"],"mappings":";;;;;AA+FA,MAAM,kBAAA,GAAqB,CACzB,EAAA,EACA,sBAC2B,KAAA;AAC3B,EAAM,MAAA,OAAA,GAAU,sBAAuB,CAAA,SAAA,CAAU,SAAS,CAAA,CAAA;AAC1D,EAAA,MAAM,KAAQ,GAAA,sBAAA,CAAuB,iBAAkB,CAAA,OAAO,CAAK,IAAA,QAAA,CAAA;AACnE,EAAA,MAAM,UACJ,GAAA,sBAAA,CAAuB,iBAAkB,CAAA,YAAY,CAAK,IAAA,QAAA,CAAA;AAC5D,EAAM,MAAA,QAAA,GAAW,sBAAuB,CAAA,iBAAA,CAAkB,UAAU,CAAA,CAAA;AACpE,EAAM,MAAA,QAAA,GAAW,sBAAuB,CAAA,iBAAA,CAAkB,UAAU,CAAA,CAAA;AACpE,EAAM,MAAA,QAAA,GAAW,sBAAuB,CAAA,iBAAA,CAAkB,UAAU,CAAA,CAAA;AACpE,EAAM,MAAA,YAAA,GAAe,sBAAuB,CAAA,iBAAA,CAAkB,cAAc,CAAA,CAAA;AAC5E,EAAM,MAAA,aAAA,GACJ,sBAAuB,CAAA,iBAAA,CAAkB,eAAe,CAAA,CAAA;AAC1D,EAAM,MAAA,cAAA,GACJ,sBAAuB,CAAA,iBAAA,CAAkB,gBAAgB,CAAA,CAAA;AAE3D,EAAI,IAAA,QAAA,IAAY,CAAC,YAAc,EAAA;AAC7B,IAAA,MAAM,IAAIA,iBAAA;AAAA,MACR,CAAA,uDAAA,CAAA;AAAA,KACF,CAAA;AAAA,GACF;AAEA,EAAI,IAAA,YAAA,IAAgB,CAAC,QAAU,EAAA;AAC7B,IAAA,MAAM,IAAIA,iBAAA;AAAA,MACR,CAAA,uDAAA,CAAA;AAAA,KACF,CAAA;AAAA,GACF;AAEA,EAAI,IAAA,QAAA,IAAY,CAAC,QAAU,EAAA;AACzB,IAAM,MAAA,IAAIA,kBAAW,CAAqD,mDAAA,CAAA,CAAA,CAAA;AAAA,GAC5E;AAEA,EAAI,IAAA,QAAA,IAAY,CAAC,QAAU,EAAA;AACzB,IAAM,MAAA,IAAIA,kBAAW,CAAqD,mDAAA,CAAA,CAAA,CAAA;AAAA,GAC5E;AAEA,EAAA,MAAM,QAAW,GAAA,sBAAA,CAAuB,GAAI,CAAA,UAAU,CAClD,GAAAC,qEAAA;AAAA,IACE,sBAAA,CAAuB,UAAU,UAAU,CAAA;AAAA,GAE7C,GAAA,KAAA,CAAA,CAAA;AAEJ,EAAO,OAAA;AAAA,IACL,EAAA;AAAA,IACA,OAAA;AAAA,IACA,UAAA;AAAA,IACA,KAAA;AAAA,IACA,QAAA;AAAA,IACA,QAAA;AAAA,IACA,QAAA;AAAA,IACA,YAAA;AAAA,IACA,QAAA;AAAA,IACA,aAAA;AAAA,IACA,cAAA;AAAA,GACF,CAAA;AACF,CAAA,CAAA;AAEa,MAAA,mBAAA,GAAsB,CACjC,MAC6B,KAAA;AAC7B,EAAA,MAAM,kBAAkB,MAAO,CAAA,iBAAA;AAAA,IAC7B,+BAAA;AAAA,GACF,CAAA;AACA,EAAA,IAAI,CAAC,eAAiB,EAAA;AACpB,IAAA,OAAO,EAAC,CAAA;AAAA,GACV;AACA,EAAA,OAAO,eAAgB,CAAA,IAAA,EAAO,CAAA,GAAA,CAAI,CAAM,EAAA,KAAA;AACtC,IAAM,MAAA,sBAAA,GAAyB,eAAgB,CAAA,SAAA,CAAU,EAAE,CAAA,CAAA;AAC3D,IAAO,OAAA,kBAAA,CAAmB,IAAI,sBAAsB,CAAA,CAAA;AAAA,GACrD,CAAA,CAAA;AACH;;;;"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const KEYCLOAK_ID_ANNOTATION = "keycloak.org/id";
|
|
4
|
+
const KEYCLOAK_REALM_ANNOTATION = "keycloak.org/realm";
|
|
5
|
+
const KEYCLOAK_ENTITY_QUERY_SIZE = 100;
|
|
6
|
+
|
|
7
|
+
exports.KEYCLOAK_ENTITY_QUERY_SIZE = KEYCLOAK_ENTITY_QUERY_SIZE;
|
|
8
|
+
exports.KEYCLOAK_ID_ANNOTATION = KEYCLOAK_ID_ANNOTATION;
|
|
9
|
+
exports.KEYCLOAK_REALM_ANNOTATION = KEYCLOAK_REALM_ANNOTATION;
|
|
10
|
+
//# sourceMappingURL=constants.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.cjs.js","sources":["../../src/lib/constants.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 */\n\nexport const KEYCLOAK_HOST_ANNOTATION = 'keycloak.org/host';\nexport const KEYCLOAK_ID_ANNOTATION = 'keycloak.org/id';\nexport const KEYCLOAK_REALM_ANNOTATION = 'keycloak.org/realm';\nexport const KEYCLOAK_ENTITY_QUERY_SIZE = 100;\n"],"names":[],"mappings":";;AAiBO,MAAM,sBAAyB,GAAA,kBAAA;AAC/B,MAAM,yBAA4B,GAAA,qBAAA;AAClC,MAAM,0BAA6B,GAAA;;;;;;"}
|