@continuoussecuritytooling/keycloak-reporter 0.6.0 → 0.8.0
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/.docs/realm-config.json +0 -0
- package/.eslintrc.cjs +4 -3
- package/.github/FUNDING.yml +2 -1
- package/.github/workflows/pipeline.yml +61 -16
- package/.github/workflows/release.yml +6 -6
- package/.prettierrc +2 -2
- package/CHANGELOG.md +92 -11
- package/Dockerfile +16 -2
- package/README.md +8 -4
- package/artifacthub-repo.yml +6 -0
- package/charts/keycloak-reporter/Chart.yaml +10 -4
- package/charts/keycloak-reporter/README.md +7 -16
- package/charts/keycloak-reporter/ci.values.yaml +13 -0
- package/charts/keycloak-reporter/templates/cronjob.yaml +4 -5
- package/charts/keycloak-reporter/templates/tests/test-connection.yaml +57 -0
- package/charts/keycloak-reporter/values.yaml +2 -1
- package/cli.ts +59 -87
- package/config/schema.json +6 -1
- package/dist/cli.js +38 -37
- package/dist/cli.js.map +1 -1
- package/dist/config/schema.json +6 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/client.js +7 -24
- package/dist/lib/client.js.map +1 -1
- package/dist/lib/convert.js +0 -0
- package/dist/lib/convert.js.map +0 -0
- package/dist/lib/output.js +2 -2
- package/dist/lib/output.js.map +1 -1
- package/dist/lib/user.js +79 -44
- package/dist/lib/user.js.map +1 -1
- package/dist/src/commands.js +30 -0
- package/dist/src/commands.js.map +1 -0
- package/dist/src/config.js +0 -9
- package/dist/src/config.js.map +1 -1
- package/e2e/spec/clients.js +1 -1
- package/e2e/spec/config.js +25 -1
- package/e2e/spec/users.js +1 -1
- package/index.ts +2 -2
- package/keycloak-reporter-1.2.1.tgz +0 -0
- package/lib/client.ts +10 -37
- package/lib/output.ts +2 -2
- package/lib/user.ts +86 -49
- package/package.json +5 -4
- package/renovate.json +12 -5
- package/src/commands.ts +37 -0
- package/src/config.ts +6 -17
- package/dist/src/cli.js +0 -19
- package/dist/src/cli.js.map +0 -1
- package/src/cli.ts +0 -26
package/dist/lib/user.js
CHANGED
|
@@ -1,75 +1,110 @@
|
|
|
1
|
+
import KcAdminClient from '@keycloak/keycloak-admin-client';
|
|
1
2
|
export async function clientListing(client) {
|
|
2
|
-
const currentRealm = client.realmName;
|
|
3
|
-
let realms;
|
|
4
|
-
try {
|
|
5
|
-
// iterate over realms
|
|
6
|
-
realms = await client.realms.find();
|
|
7
|
-
}
|
|
8
|
-
catch (e) {
|
|
9
|
-
console.error('Check Client role:', e.response.statusText);
|
|
10
|
-
return Promise.reject();
|
|
11
|
-
}
|
|
12
3
|
let allClients = new Array();
|
|
13
|
-
|
|
14
|
-
|
|
4
|
+
if (client instanceof KcAdminClient) {
|
|
5
|
+
const currentRealm = client.realmName;
|
|
6
|
+
let realms;
|
|
7
|
+
try {
|
|
8
|
+
// iterate over realms
|
|
9
|
+
realms = await client.realms.find();
|
|
10
|
+
}
|
|
11
|
+
catch (e) {
|
|
12
|
+
console.error('Check Client role:', e.response.statusText);
|
|
13
|
+
return Promise.reject();
|
|
14
|
+
}
|
|
15
|
+
for (const realm of realms) {
|
|
16
|
+
// switch realm
|
|
17
|
+
client.setConfig({
|
|
18
|
+
realmName: realm.realm,
|
|
19
|
+
});
|
|
20
|
+
const realmClients = new Array();
|
|
21
|
+
for (const user of await client.clients.find()) {
|
|
22
|
+
realmClients.push({
|
|
23
|
+
client: user.clientId,
|
|
24
|
+
id: user.id,
|
|
25
|
+
description: user.description,
|
|
26
|
+
realm: realm.realm,
|
|
27
|
+
enabled: user.enabled,
|
|
28
|
+
public: user.publicClient,
|
|
29
|
+
allowedOrigins: JSON.stringify(user.webOrigins),
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
allClients = [...allClients, ...realmClients];
|
|
33
|
+
}
|
|
34
|
+
// switch back to realm
|
|
15
35
|
client.setConfig({
|
|
16
|
-
realmName:
|
|
36
|
+
realmName: currentRealm,
|
|
17
37
|
});
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
const clients = await client.clientListing();
|
|
41
|
+
for (const user of clients) {
|
|
42
|
+
allClients.push({
|
|
21
43
|
client: user.clientId,
|
|
22
44
|
id: user.id,
|
|
23
45
|
description: user.description,
|
|
24
|
-
realm:
|
|
46
|
+
realm: user.realm,
|
|
25
47
|
enabled: user.enabled,
|
|
26
48
|
public: user.publicClient,
|
|
49
|
+
lastLogin: user.lastLogin,
|
|
27
50
|
allowedOrigins: JSON.stringify(user.webOrigins),
|
|
28
51
|
});
|
|
29
52
|
}
|
|
30
|
-
allClients = [...allClients, ...realmClients];
|
|
31
53
|
}
|
|
32
|
-
// switch back to realm
|
|
33
|
-
client.setConfig({
|
|
34
|
-
realmName: currentRealm,
|
|
35
|
-
});
|
|
36
54
|
return new Promise((resolve) => resolve(allClients));
|
|
37
55
|
}
|
|
38
56
|
export async function userListing(client) {
|
|
39
|
-
const currentRealm = client.realmName;
|
|
40
|
-
let realms;
|
|
41
|
-
// iterate over realms
|
|
42
|
-
try {
|
|
43
|
-
realms = await client.realms.find();
|
|
44
|
-
}
|
|
45
|
-
catch (e) {
|
|
46
|
-
console.error('Check Client role:', e.response.statusText);
|
|
47
|
-
return Promise.reject();
|
|
48
|
-
}
|
|
49
57
|
let allUsers = new Array();
|
|
50
|
-
|
|
51
|
-
|
|
58
|
+
if (client instanceof KcAdminClient) {
|
|
59
|
+
const currentRealm = client.realmName;
|
|
60
|
+
let realms;
|
|
61
|
+
// iterate over realms
|
|
62
|
+
try {
|
|
63
|
+
realms = await client.realms.find();
|
|
64
|
+
}
|
|
65
|
+
catch (e) {
|
|
66
|
+
console.error('Check Client role:', e.response.statusText);
|
|
67
|
+
return Promise.reject();
|
|
68
|
+
}
|
|
69
|
+
for (const realm of realms) {
|
|
70
|
+
// switch realm
|
|
71
|
+
client.setConfig({
|
|
72
|
+
realmName: realm.realm,
|
|
73
|
+
});
|
|
74
|
+
const realmUsers = new Array();
|
|
75
|
+
for (const user of await client.users.find()) {
|
|
76
|
+
realmUsers.push({
|
|
77
|
+
username: user.username,
|
|
78
|
+
id: user.id,
|
|
79
|
+
firstName: user.firstName,
|
|
80
|
+
lastName: user.lastName,
|
|
81
|
+
email: user.email,
|
|
82
|
+
realm: realm.realm,
|
|
83
|
+
enabled: user.enabled,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
allUsers = [...allUsers, ...realmUsers];
|
|
87
|
+
}
|
|
88
|
+
// switch back to realm
|
|
52
89
|
client.setConfig({
|
|
53
|
-
realmName:
|
|
90
|
+
realmName: currentRealm,
|
|
54
91
|
});
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
const users = await client.userListing();
|
|
95
|
+
for (const user of users) {
|
|
96
|
+
allUsers.push({
|
|
58
97
|
username: user.username,
|
|
59
98
|
id: user.id,
|
|
60
99
|
firstName: user.firstName,
|
|
61
100
|
lastName: user.lastName,
|
|
62
101
|
email: user.email,
|
|
63
|
-
realm:
|
|
102
|
+
realm: user.realm,
|
|
103
|
+
lastLogin: user.lastLogin,
|
|
64
104
|
enabled: user.enabled,
|
|
65
105
|
});
|
|
66
106
|
}
|
|
67
|
-
allUsers = [...allUsers, ...realmUsers];
|
|
68
107
|
}
|
|
69
|
-
// switch back to realm
|
|
70
|
-
client.setConfig({
|
|
71
|
-
realmName: currentRealm,
|
|
72
|
-
});
|
|
73
108
|
return new Promise((resolve) => resolve(allUsers));
|
|
74
109
|
}
|
|
75
110
|
//# sourceMappingURL=user.js.map
|
package/dist/lib/user.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user.js","sourceRoot":"","sources":["../../lib/user.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"user.js","sourceRoot":"","sources":["../../lib/user.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { createClient } from '../lib/client.js';
|
|
2
|
+
import { userListing, clientListing } from '../lib/user.js';
|
|
3
|
+
function kcClient(options) {
|
|
4
|
+
return createClient({
|
|
5
|
+
clientId: options.clientId,
|
|
6
|
+
clientSecret: options.clientSecret,
|
|
7
|
+
rootUrl: options.rootUrl,
|
|
8
|
+
useAuditingEndpoint: options.useAuditingEndpoint,
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
export async function listUsers(options) {
|
|
12
|
+
const users = await userListing(await kcClient(options));
|
|
13
|
+
return new Promise((resolve) => resolve(users));
|
|
14
|
+
}
|
|
15
|
+
export async function listClients(options) {
|
|
16
|
+
const clients = await clientListing(await kcClient(options));
|
|
17
|
+
return new Promise((resolve) => resolve(clients));
|
|
18
|
+
}
|
|
19
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
20
|
+
export async function configTest(options) {
|
|
21
|
+
try {
|
|
22
|
+
await userListing(await kcClient(options));
|
|
23
|
+
console.log(`Connection to ${options.rootUrl} was successfull`);
|
|
24
|
+
}
|
|
25
|
+
catch (e) {
|
|
26
|
+
console.error(`Connection to ${options.rootUrl} was not: successfull`, e.response);
|
|
27
|
+
return Promise.reject(e.response.statusText);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=commands.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands.js","sourceRoot":"","sources":["../../src/commands.ts"],"names":[],"mappings":"AAMA,OAAO,EAAW,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAQ,WAAW,EAAE,aAAa,EAAU,MAAM,gBAAgB,CAAC;AAE1E,SAAS,QAAQ,CAAC,OAAgB;IAChC,OAAO,YAAY,CAAC;QAClB,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,YAAY,EAAE,OAAO,CAAC,YAAY;QAClC,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,mBAAmB,EAAE,OAAO,CAAC,mBAAmB;KACjD,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,OAAgB;IAC9C,MAAM,KAAK,GAAG,MAAM,WAAW,CAAC,MAAM,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;IACzD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;AAClD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,OAAgB;IAChD,MAAM,OAAO,GAAG,MAAM,aAAa,CAAC,MAAM,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;IAC7D,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;AACpD,CAAC;AACD,uDAAuD;AACvD,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,OAAgB;IAC/C,IAAI,CAAC;QACH,MAAM,WAAW,CAAC,MAAM,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;QAC3C,OAAO,CAAC,GAAG,CAAC,iBAAiB,OAAO,CAAC,OAAO,kBAAkB,CAAC,CAAC;IAClE,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,CAAC,KAAK,CAAC,iBAAiB,OAAO,CAAC,OAAO,uBAAuB,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC;QACnF,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IAC/C,CAAC;AACH,CAAC"}
|
package/dist/src/config.js
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import { assoc, pick, mergeAll, mergeDeepRight } from 'ramda';
|
|
2
|
-
import Ajv from 'ajv';
|
|
3
2
|
import path from 'path';
|
|
4
3
|
import { fileURLToPath } from 'url';
|
|
5
4
|
import fs from 'fs';
|
|
6
5
|
const schema = JSON.parse(fs.readFileSync(fileURLToPath(path.join(import.meta.url, '../../config/schema.json')), 'utf8'));
|
|
7
|
-
const ajv = new Ajv.default();
|
|
8
|
-
const ajvValidate = ajv.compile(schema);
|
|
9
6
|
// import the config file
|
|
10
7
|
function buildConfigFromFile(filePath) {
|
|
11
8
|
if (!filePath)
|
|
@@ -45,12 +42,6 @@ function buildEnvironmentVariablesConfig(schema) {
|
|
|
45
42
|
}
|
|
46
43
|
}, {});
|
|
47
44
|
}
|
|
48
|
-
function validate(data) {
|
|
49
|
-
const valid = ajvValidate(data);
|
|
50
|
-
if (valid)
|
|
51
|
-
return true;
|
|
52
|
-
throw new Error(ajv.errorsText());
|
|
53
|
-
}
|
|
54
45
|
// merge the environment variables, config file values, and defaults
|
|
55
46
|
const config = mergeAll(mergeDeepRight(buildDefaults(schema, schema.definitions), buildConfigFromFile(process.env.CONFIG_FILE)), buildEnvironmentVariablesConfig(schema));
|
|
56
47
|
export default config;
|
package/dist/src/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AAC9D,OAAO,
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"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"}
|
package/e2e/spec/clients.js
CHANGED
|
@@ -21,7 +21,7 @@ test('Should list clients as JSON', { timeout: 3000 }, (t) => {
|
|
|
21
21
|
}
|
|
22
22
|
);
|
|
23
23
|
cli.stdout.on('data', (chunk) => {
|
|
24
|
-
console.log(chunk.toString())
|
|
24
|
+
console.log('Response', JSON.parse(chunk.toString()));
|
|
25
25
|
t.equal(JSON.parse(chunk.toString()).length, 24);
|
|
26
26
|
t.end();
|
|
27
27
|
});
|
package/e2e/spec/config.js
CHANGED
|
@@ -19,7 +19,7 @@ test('Should use config file', { timeout: 3000 }, (t) => {
|
|
|
19
19
|
}
|
|
20
20
|
);
|
|
21
21
|
cli.stdout.on('data', (chunk) => {
|
|
22
|
-
console.log(chunk.toString())
|
|
22
|
+
console.log('Response', JSON.parse(chunk.toString()));
|
|
23
23
|
t.equal(JSON.parse(chunk.toString()).length, 24);
|
|
24
24
|
t.end();
|
|
25
25
|
});
|
|
@@ -27,3 +27,27 @@ test('Should use config file', { timeout: 3000 }, (t) => {
|
|
|
27
27
|
t.fail(msg)
|
|
28
28
|
});
|
|
29
29
|
});
|
|
30
|
+
|
|
31
|
+
test('Should validate config', { timeout: 3000 }, (t) => {
|
|
32
|
+
const cli = spawn(
|
|
33
|
+
path.join(path.dirname('.'), 'node'),
|
|
34
|
+
[
|
|
35
|
+
'dist/cli.js',
|
|
36
|
+
'configTest'
|
|
37
|
+
],
|
|
38
|
+
{
|
|
39
|
+
env: {
|
|
40
|
+
CONFIG_FILE: process.cwd() + '/e2e/fixtures/config.json',
|
|
41
|
+
...process.env,
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
cli.stdout.on('data', (chunk) => {
|
|
46
|
+
console.log(chunk.toString())
|
|
47
|
+
t.equal(chunk.toString(), 'Connection to http://localhost:8080 was successfull\n');
|
|
48
|
+
t.end();
|
|
49
|
+
});
|
|
50
|
+
cli.stderr.on('data', (msg) => {
|
|
51
|
+
t.fail(msg)
|
|
52
|
+
});
|
|
53
|
+
});
|
package/e2e/spec/users.js
CHANGED
|
@@ -21,7 +21,7 @@ test('Should list users as JSON', { timeout: 3000 }, (t) => {
|
|
|
21
21
|
}
|
|
22
22
|
);
|
|
23
23
|
cli.stdout.on('data', (chunk) => {
|
|
24
|
-
console.log(chunk.toString())
|
|
24
|
+
console.log('Response', JSON.parse(chunk.toString()));
|
|
25
25
|
t.equal(JSON.parse(chunk.toString()).length, 3);
|
|
26
26
|
t.end();
|
|
27
27
|
});
|
package/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { listUsers, listClients } from './src/
|
|
1
|
+
export { configTest, listUsers, listClients } from './src/commands.js';
|
|
2
2
|
export { Options } from './lib/client.js';
|
|
3
3
|
export { convertJSON2CSV } from './lib/convert.js';
|
|
4
|
-
export { post2Webhook } from './lib/output.js';
|
|
4
|
+
export { post2Webhook } from './lib/output.js';
|
|
Binary file
|
package/lib/client.ts
CHANGED
|
@@ -1,21 +1,20 @@
|
|
|
1
|
-
import { Issuer } from 'openid-client';
|
|
2
1
|
import KcAdminClient from '@keycloak/keycloak-admin-client';
|
|
3
|
-
|
|
4
|
-
// Token refresh interval 60 seconds
|
|
5
|
-
const TOKEN_REFRESH = 60;
|
|
2
|
+
import { AuditClient } from '@continuoussecuritytooling/keycloak-auditor';
|
|
6
3
|
|
|
7
4
|
export interface Options {
|
|
8
5
|
clientId: string;
|
|
9
6
|
clientSecret: string;
|
|
10
7
|
rootUrl: string;
|
|
8
|
+
useAuditingEndpoint: boolean;
|
|
11
9
|
}
|
|
12
10
|
|
|
13
|
-
export async function createClient(options: Options): Promise<KcAdminClient> {
|
|
14
|
-
const kcAdminClient =
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
11
|
+
export async function createClient(options: Options): Promise<KcAdminClient | AuditClient> {
|
|
12
|
+
const kcAdminClient = options.useAuditingEndpoint
|
|
13
|
+
? new AuditClient(options.rootUrl, 'master')
|
|
14
|
+
: new KcAdminClient({
|
|
15
|
+
baseUrl: options.rootUrl,
|
|
16
|
+
realmName: 'master',
|
|
17
|
+
});
|
|
19
18
|
try {
|
|
20
19
|
// client login
|
|
21
20
|
await kcAdminClient.auth({
|
|
@@ -24,35 +23,9 @@ export async function createClient(options: Options): Promise<KcAdminClient> {
|
|
|
24
23
|
grantType: 'client_credentials',
|
|
25
24
|
});
|
|
26
25
|
} catch (e) {
|
|
27
|
-
console.error(
|
|
28
|
-
'Check Client Config:',
|
|
29
|
-
e.response ? e.response.data.error_description : e
|
|
30
|
-
);
|
|
26
|
+
console.error('Check Client Config:', e.response ? e.response.data.error_description : e);
|
|
31
27
|
return Promise.reject();
|
|
32
28
|
}
|
|
33
29
|
|
|
34
|
-
const keycloakIssuer = await Issuer.discover(
|
|
35
|
-
`${options.rootUrl}/realms/master`
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
const client = new keycloakIssuer.Client({
|
|
39
|
-
client_id: options.clientId,
|
|
40
|
-
token_endpoint_auth_method: 'none', // to send only client_id in the header
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
// Use the grant type 'password'
|
|
44
|
-
const tokenSet = await client.grant({
|
|
45
|
-
client_id: options.clientId,
|
|
46
|
-
client_secret: options.clientSecret,
|
|
47
|
-
grant_type: 'client_credentials',
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
/*
|
|
51
|
-
// TODO: FIXME - Periodically using refresh_token grant flow to get new access token here
|
|
52
|
-
setInterval(async () => {
|
|
53
|
-
const refreshToken = tokenSet.refresh_token;
|
|
54
|
-
kcAdminClient.setAccessToken((await client.refresh(refreshToken)).access_token);
|
|
55
|
-
}, TOKEN_REFRESH * 1000); */
|
|
56
|
-
|
|
57
30
|
return new Promise((resolve) => resolve(kcAdminClient));
|
|
58
31
|
}
|
package/lib/output.ts
CHANGED
|
@@ -29,7 +29,7 @@ export async function post2Webhook(
|
|
|
29
29
|
{
|
|
30
30
|
contentType: 'application/vnd.microsoft.card.adaptive',
|
|
31
31
|
content: {
|
|
32
|
-
$schema: '
|
|
32
|
+
$schema: 'https://adaptivecards.io/schemas/adaptive-card.json',
|
|
33
33
|
type: 'AdaptiveCard',
|
|
34
34
|
version: '1.2',
|
|
35
35
|
body: [
|
|
@@ -68,7 +68,7 @@ export async function post2Webhook(
|
|
|
68
68
|
}
|
|
69
69
|
],
|
|
70
70
|
$schema:
|
|
71
|
-
'
|
|
71
|
+
'https://adaptivecards.io/schemas/adaptive-card.json'
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
]
|
package/lib/user.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import KcAdminClient from '@keycloak/keycloak-admin-client';
|
|
2
|
+
import {
|
|
3
|
+
AuditClient,
|
|
4
|
+
AuditedClientRepresentation,
|
|
5
|
+
AuditedUserRepresentation,
|
|
6
|
+
} from '@continuoussecuritytooling/keycloak-auditor';
|
|
2
7
|
|
|
3
8
|
export interface User {
|
|
4
9
|
username: string;
|
|
@@ -21,79 +26,111 @@ export interface Client {
|
|
|
21
26
|
}
|
|
22
27
|
|
|
23
28
|
export async function clientListing(
|
|
24
|
-
client: KcAdminClient
|
|
25
|
-
): Promise<Array<Client>> {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
29
|
+
client: KcAdminClient | AuditClient
|
|
30
|
+
): Promise<Array<Client | AuditedClientRepresentation>> {
|
|
31
|
+
let allClients = new Array<Client | AuditedClientRepresentation>();
|
|
32
|
+
if (client instanceof KcAdminClient) {
|
|
33
|
+
const currentRealm = client.realmName;
|
|
34
|
+
let realms;
|
|
35
|
+
try {
|
|
36
|
+
// iterate over realms
|
|
37
|
+
realms = await client.realms.find();
|
|
38
|
+
} catch (e) {
|
|
39
|
+
console.error('Check Client role:', e.response.statusText);
|
|
40
|
+
return Promise.reject();
|
|
41
|
+
}
|
|
42
|
+
for (const realm of realms) {
|
|
43
|
+
// switch realm
|
|
44
|
+
client.setConfig({
|
|
45
|
+
realmName: realm.realm,
|
|
46
|
+
});
|
|
47
|
+
const realmClients = new Array<Client>();
|
|
48
|
+
for (const user of await client.clients.find()) {
|
|
49
|
+
realmClients.push({
|
|
50
|
+
client: user.clientId,
|
|
51
|
+
id: user.id,
|
|
52
|
+
description: user.description,
|
|
53
|
+
realm: realm.realm,
|
|
54
|
+
enabled: user.enabled,
|
|
55
|
+
public: user.publicClient,
|
|
56
|
+
allowedOrigins: JSON.stringify(user.webOrigins),
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
allClients = [...allClients, ...realmClients];
|
|
60
|
+
}
|
|
61
|
+
// switch back to realm
|
|
38
62
|
client.setConfig({
|
|
39
|
-
realmName:
|
|
63
|
+
realmName: currentRealm,
|
|
40
64
|
});
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
65
|
+
} else {
|
|
66
|
+
const clients = await client.clientListing();
|
|
67
|
+
for (const user of clients) {
|
|
68
|
+
allClients.push({
|
|
44
69
|
client: user.clientId,
|
|
45
70
|
id: user.id,
|
|
46
71
|
description: user.description,
|
|
47
|
-
realm:
|
|
72
|
+
realm: user.realm,
|
|
48
73
|
enabled: user.enabled,
|
|
49
74
|
public: user.publicClient,
|
|
75
|
+
lastLogin: user.lastLogin,
|
|
50
76
|
allowedOrigins: JSON.stringify(user.webOrigins),
|
|
51
77
|
});
|
|
52
78
|
}
|
|
53
|
-
allClients = [...allClients, ...realmClients];
|
|
54
79
|
}
|
|
55
|
-
// switch back to realm
|
|
56
|
-
client.setConfig({
|
|
57
|
-
realmName: currentRealm,
|
|
58
|
-
});
|
|
59
|
-
|
|
60
80
|
return new Promise((resolve) => resolve(allClients));
|
|
61
81
|
}
|
|
62
82
|
|
|
63
|
-
export async function userListing(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
83
|
+
export async function userListing(
|
|
84
|
+
client: KcAdminClient | AuditClient
|
|
85
|
+
): Promise<Array<User | AuditedUserRepresentation>> {
|
|
86
|
+
let allUsers = new Array<User | AuditedUserRepresentation>();
|
|
87
|
+
if (client instanceof KcAdminClient) {
|
|
88
|
+
const currentRealm = client.realmName;
|
|
89
|
+
let realms;
|
|
90
|
+
// iterate over realms
|
|
91
|
+
try {
|
|
92
|
+
realms = await client.realms.find();
|
|
93
|
+
} catch (e) {
|
|
94
|
+
console.error('Check Client role:', e.response.statusText);
|
|
95
|
+
return Promise.reject();
|
|
96
|
+
}
|
|
97
|
+
for (const realm of realms) {
|
|
98
|
+
// switch realm
|
|
99
|
+
client.setConfig({
|
|
100
|
+
realmName: realm.realm,
|
|
101
|
+
});
|
|
102
|
+
const realmUsers = new Array<User>();
|
|
103
|
+
for (const user of await client.users.find()) {
|
|
104
|
+
realmUsers.push({
|
|
105
|
+
username: user.username,
|
|
106
|
+
id: user.id,
|
|
107
|
+
firstName: user.firstName,
|
|
108
|
+
lastName: user.lastName,
|
|
109
|
+
email: user.email,
|
|
110
|
+
realm: realm.realm,
|
|
111
|
+
enabled: user.enabled,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
allUsers = [...allUsers, ...realmUsers];
|
|
115
|
+
}
|
|
116
|
+
// switch back to realm
|
|
76
117
|
client.setConfig({
|
|
77
|
-
realmName:
|
|
118
|
+
realmName: currentRealm,
|
|
78
119
|
});
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
120
|
+
} else {
|
|
121
|
+
const users = await client.userListing();
|
|
122
|
+
for (const user of users) {
|
|
123
|
+
allUsers.push({
|
|
82
124
|
username: user.username,
|
|
83
125
|
id: user.id,
|
|
84
126
|
firstName: user.firstName,
|
|
85
127
|
lastName: user.lastName,
|
|
86
128
|
email: user.email,
|
|
87
|
-
realm:
|
|
129
|
+
realm: user.realm,
|
|
130
|
+
lastLogin: user.lastLogin,
|
|
88
131
|
enabled: user.enabled,
|
|
89
132
|
});
|
|
90
133
|
}
|
|
91
|
-
allUsers = [...allUsers, ...realmUsers];
|
|
92
134
|
}
|
|
93
|
-
// switch back to realm
|
|
94
|
-
client.setConfig({
|
|
95
|
-
realmName: currentRealm,
|
|
96
|
-
});
|
|
97
|
-
|
|
98
135
|
return new Promise((resolve) => resolve(allUsers));
|
|
99
136
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@continuoussecuritytooling/keycloak-reporter",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Reporting Tools for Keycloak",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -25,8 +25,9 @@
|
|
|
25
25
|
},
|
|
26
26
|
"homepage": "https://github.com/ContinuousSecurityTooling/keycloak-reporter#readme",
|
|
27
27
|
"dependencies": {
|
|
28
|
+
"@continuoussecuritytooling/keycloak-auditor": "^1.1.0",
|
|
28
29
|
"@json2csv/node": "^7.0.0",
|
|
29
|
-
"@keycloak/keycloak-admin-client": "^
|
|
30
|
+
"@keycloak/keycloak-admin-client": "^23.0.0",
|
|
30
31
|
"@slack/webhook": "^7.0.0",
|
|
31
32
|
"ajv": "^8.12.0",
|
|
32
33
|
"install": "^0.13.0",
|
|
@@ -41,8 +42,8 @@
|
|
|
41
42
|
"@types/jest": "^29.5.1",
|
|
42
43
|
"@types/node": "^20.1.5",
|
|
43
44
|
"@types/yargs": "^17.0.24",
|
|
44
|
-
"@typescript-eslint/eslint-plugin": "^
|
|
45
|
-
"@typescript-eslint/parser": "^
|
|
45
|
+
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
|
46
|
+
"@typescript-eslint/parser": "^6.0.0",
|
|
46
47
|
"eslint": "^8.40.0",
|
|
47
48
|
"eslint-config-prettier": "^9.0.0",
|
|
48
49
|
"eslint-plugin-prettier": "^5.0.0",
|
package/renovate.json
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
|
3
|
-
"
|
|
3
|
+
"labels": ["dependencies"],
|
|
4
|
+
"extends": ["config:base", ":dependencyDashboard", ":rebaseStalePrs"],
|
|
5
|
+
"automergeSchedule": ["after 5am and before 5pm every weekday"],
|
|
6
|
+
"separateMinorPatch": true,
|
|
4
7
|
"platformAutomerge": true,
|
|
5
8
|
"packageRules": [
|
|
6
9
|
{
|
|
@@ -12,12 +15,16 @@
|
|
|
12
15
|
],
|
|
13
16
|
"regexManagers": [
|
|
14
17
|
{
|
|
15
|
-
"description": "Update
|
|
16
|
-
"fileMatch": ["
|
|
18
|
+
"description": "Update image variables in YAML files",
|
|
19
|
+
"fileMatch": ["\\.y[a]?ml$", "\\.y[a]?ml\\.tpl$"],
|
|
17
20
|
"matchStrings": [
|
|
18
|
-
"# renovate: datasource=(?<datasource>[a-z-]+?)(?: lookupName=(?<
|
|
21
|
+
"# renovate: datasource=(?<datasource>[a-z-]+?) depName=(?<depName>[^\\s]+?)(?: (lookupName|packageName)=(?<packageName>[^\\s]+?))?(?: versioning=(?<versioning>[^\\s]+?))?\\s.+?_version: (?<currentValue>.+?)\\s",
|
|
22
|
+
"# renovate: datasource=(?<datasource>[a-z-]+?) depName=(?<depName>[^\\s]+?)(?: (lookupName|packageName)=(?<packageName>[^\\s]+?))?(?: versioning=(?<versioning>[^\\s]+?))?\\s.+?-version: (?<currentValue>.+?)\\s",
|
|
23
|
+
"# renovate: datasource=(?<datasource>[a-z-]+?) depName=(?<depName>[^\\s]+?)(?: (lookupName|packageName)=(?<packageName>[^\\s]+?))?(?: versioning=(?<versioning>[^\\s]+?))?\\simage: (?<currentValue>.+?)\\s",
|
|
24
|
+
"# renovate: datasource=(?<datasource>[a-z-]+?) depName=(?<depName>.+?) versioning=(?<versioning>.+?)\\s\\s*image: (?<currentValue>.*)",
|
|
25
|
+
"# renovate: datasource=(?<datasource>[a-z-]+?) depName=(?<depName>.+?) \\s\\s*image: (?<currentValue>.*)"
|
|
19
26
|
],
|
|
20
|
-
"
|
|
27
|
+
"extractVersionTemplate": "{{#if extractVersion}}{{{extractVersion}}}{{else}}^v?(?<version>.+)${{/if}}"
|
|
21
28
|
}
|
|
22
29
|
],
|
|
23
30
|
"prHourlyLimit": 10
|