@continuoussecuritytooling/keycloak-reporter 0.5.1 → 0.7.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/.eslintrc.cjs +4 -3
- package/.github/workflows/pipeline.yml +37 -10
- package/.github/workflows/release.yml +1 -1
- package/.prettierrc +2 -2
- package/Dockerfile +19 -2
- package/README.md +4 -3
- package/artifacthub-repo.yml +6 -0
- package/charts/keycloak-reporter/Chart.yaml +9 -3
- package/charts/keycloak-reporter/README.md +7 -21
- package/charts/keycloak-reporter/templates/_helpers.tpl +8 -8
- package/charts/keycloak-reporter/templates/cronjob.yaml +21 -16
- package/charts/keycloak-reporter/templates/secret.yaml +6 -8
- package/charts/keycloak-reporter/values.yaml +42 -39
- package/cli.ts +54 -87
- package/config/schema.json +6 -1
- package/index.ts +1 -1
- 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 +27 -0
- package/src/config.ts +6 -18
- package/config.json +0 -9
- package/dist/cli.js +0 -130
- package/dist/cli.js.map +0 -1
- package/dist/config/schema.json +0 -65
- package/dist/index.js +0 -4
- package/dist/index.js.map +0 -1
- package/dist/lib/client.js +0 -41
- package/dist/lib/client.js.map +0 -1
- package/dist/lib/convert.js +0 -9
- package/dist/lib/convert.js.map +0 -1
- package/dist/lib/output.js +0 -113
- package/dist/lib/output.js.map +0 -1
- package/dist/lib/user.js +0 -75
- package/dist/lib/user.js.map +0 -1
- package/dist/src/cli.js +0 -19
- package/dist/src/cli.js.map +0 -1
- package/dist/src/config.js +0 -57
- package/dist/src/config.js.map +0 -1
- package/k8s.yaml +0 -51
- package/keycloak-reporter-0.5.0.tgz +0 -0
- package/src/cli.ts +0 -26
- package/test.values.yaml +0 -8
package/cli.ts
CHANGED
|
@@ -4,15 +4,8 @@ import { writeFileSync } from 'node:fs';
|
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import yargs from 'yargs/yargs';
|
|
6
6
|
import { hideBin } from 'yargs/helpers';
|
|
7
|
-
import {
|
|
8
|
-
listUsers,
|
|
9
|
-
listClients,
|
|
10
|
-
Options,
|
|
11
|
-
convertJSON2CSV,
|
|
12
|
-
post2Webhook
|
|
13
|
-
} from './index.js';
|
|
7
|
+
import { listUsers, listClients, Options, convertJSON2CSV, post2Webhook } from './index.js';
|
|
14
8
|
import config from './src/config.js';
|
|
15
|
-
|
|
16
9
|
class WebhookConfig {
|
|
17
10
|
type: string;
|
|
18
11
|
url: string;
|
|
@@ -31,13 +24,34 @@ class ReportConfig {
|
|
|
31
24
|
directory: string;
|
|
32
25
|
}
|
|
33
26
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
27
|
+
function getKeycloakConfig(config, argv): Options {
|
|
28
|
+
return {
|
|
29
|
+
clientId: config.clientId ? config.clientId : (argv.clientId as string),
|
|
30
|
+
clientSecret: config.clientSecret ? config.clientSecret : (argv.clientSecret as string),
|
|
31
|
+
rootUrl: config.url ? config.url : (argv.url as string),
|
|
32
|
+
useAuditingEndpoint: argv.useAuditingEndpoint == true || config.useAuditingEndpoint.toLowerCase() == 'true',
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function convertData(config, argv, name: string, title: string, json: object) {
|
|
37
|
+
convert(
|
|
38
|
+
config.format ? config.format : (argv.format as string),
|
|
39
|
+
config.output ? config.output : (argv.output as string),
|
|
40
|
+
{
|
|
41
|
+
name,
|
|
42
|
+
directory: argv.reports ? (argv.reports as string) : config.reports,
|
|
43
|
+
},
|
|
44
|
+
new WebhookConfig(
|
|
45
|
+
config.webhookType ? config.webhookType : (argv.webhookType as string),
|
|
46
|
+
config.webhookUrl ? config.webhookUrl : (argv.webhookUrl as string),
|
|
47
|
+
title,
|
|
48
|
+
config.webhookMessage ? config.webhookMessage : (argv.webhookMessage as string)
|
|
49
|
+
),
|
|
50
|
+
json
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function convert(format: string, output: string, reports: ReportConfig, config: WebhookConfig, json: object) {
|
|
41
55
|
let outputContent: string;
|
|
42
56
|
switch (format) {
|
|
43
57
|
case 'csv':
|
|
@@ -52,23 +66,21 @@ async function convert(
|
|
|
52
66
|
writeFileSync(
|
|
53
67
|
path.join(
|
|
54
68
|
`${reports.directory}`,
|
|
55
|
-
`${reports.name}_${date.getFullYear()}-${
|
|
56
|
-
date.getMonth() + 1
|
|
57
|
-
}-${date.getDate()}.${format.toLowerCase()}`
|
|
69
|
+
`${reports.name}_${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}.${format.toLowerCase()}`
|
|
58
70
|
),
|
|
59
71
|
outputContent
|
|
60
72
|
);
|
|
61
73
|
}
|
|
62
74
|
switch (output) {
|
|
63
75
|
case 'webhook':
|
|
76
|
+
if (!config.url) {
|
|
77
|
+
console.error('No valid Webhook URL given');
|
|
78
|
+
throw new Error('Please provide a valid --webhookUrl parameter');
|
|
79
|
+
}
|
|
64
80
|
try {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
config.title,
|
|
69
|
-
outputContent,
|
|
70
|
-
config.message
|
|
71
|
-
);
|
|
81
|
+
console.log(`Sending report via webhook to ${config.type} ....`);
|
|
82
|
+
await post2Webhook(config.type, config.url, config.title, outputContent, config.message);
|
|
83
|
+
console.log('Done sending.');
|
|
72
84
|
} catch (e) {
|
|
73
85
|
switch (e.code || e.message) {
|
|
74
86
|
case 'Request failed with status code 400':
|
|
@@ -78,10 +90,7 @@ async function convert(
|
|
|
78
90
|
console.error('Invalid Slack Webhook Payload. Check your params.');
|
|
79
91
|
throw new Error('Invalid Slack Payload');
|
|
80
92
|
default:
|
|
81
|
-
console.error(
|
|
82
|
-
`Error during sending webhook.(${e?.code})`,
|
|
83
|
-
e?.original
|
|
84
|
-
);
|
|
93
|
+
console.error(`Error during sending webhook.(${e?.code})`, e?.original);
|
|
85
94
|
throw e;
|
|
86
95
|
}
|
|
87
96
|
}
|
|
@@ -99,32 +108,8 @@ yargs(hideBin(process.argv))
|
|
|
99
108
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
100
109
|
() => {},
|
|
101
110
|
async (argv) => {
|
|
102
|
-
const users = await listUsers(
|
|
103
|
-
|
|
104
|
-
clientSecret: config.clientSecret
|
|
105
|
-
? config.clientSecret
|
|
106
|
-
: (argv.clientSecret as string),
|
|
107
|
-
rootUrl: config.url ? config.url : (argv.url as string)
|
|
108
|
-
});
|
|
109
|
-
await convert(
|
|
110
|
-
config.format ? config.format : (argv.format as string),
|
|
111
|
-
config.output ? config.output : (argv.output as string),
|
|
112
|
-
{
|
|
113
|
-
name: 'user_listing',
|
|
114
|
-
directory: argv.reports ? (argv.reports as string) : config.reports
|
|
115
|
-
},
|
|
116
|
-
new WebhookConfig(
|
|
117
|
-
config.webhookType
|
|
118
|
-
? config.webhookType
|
|
119
|
-
: (argv.webhookType as string),
|
|
120
|
-
config.webhookUrl ? config.webhookUrl : (argv.webhookUrl as string),
|
|
121
|
-
'User Listing',
|
|
122
|
-
config.webhookMessage
|
|
123
|
-
? config.webhookMessage
|
|
124
|
-
: (argv.webhookMessage as string)
|
|
125
|
-
),
|
|
126
|
-
users
|
|
127
|
-
);
|
|
111
|
+
const users = await listUsers(getKeycloakConfig(config, argv));
|
|
112
|
+
convertData(config, argv, 'user_listing', 'User Listing', users);
|
|
128
113
|
}
|
|
129
114
|
)
|
|
130
115
|
.command(
|
|
@@ -133,65 +118,47 @@ yargs(hideBin(process.argv))
|
|
|
133
118
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
134
119
|
() => {},
|
|
135
120
|
async (argv) => {
|
|
136
|
-
const clients = await listClients(
|
|
137
|
-
|
|
138
|
-
clientSecret: config.clientSecret
|
|
139
|
-
? config.clientSecret
|
|
140
|
-
: (argv.clientSecret as string),
|
|
141
|
-
rootUrl: config.url ? config.url : (argv.url as string)
|
|
142
|
-
});
|
|
143
|
-
await convert(
|
|
144
|
-
config.format ? config.format : (argv.format as string),
|
|
145
|
-
config.output ? config.output : (argv.output as string),
|
|
146
|
-
{
|
|
147
|
-
name: 'client_listing',
|
|
148
|
-
directory: argv.reports ? (argv.reports as string) : config.reports
|
|
149
|
-
},
|
|
150
|
-
new WebhookConfig(
|
|
151
|
-
config.webhookType
|
|
152
|
-
? config.webhookType
|
|
153
|
-
: (argv.webhookType as string),
|
|
154
|
-
config.webhookUrl ? config.webhookUrl : (argv.webhookUrl as string),
|
|
155
|
-
'Client Listing',
|
|
156
|
-
config.webhookMessage
|
|
157
|
-
? config.webhookMessage
|
|
158
|
-
: (argv.webhookMessage as string)
|
|
159
|
-
),
|
|
160
|
-
clients
|
|
161
|
-
);
|
|
121
|
+
const clients = await listClients(getKeycloakConfig(config, argv));
|
|
122
|
+
convertData(config, argv, 'client_listing', 'Client Listing', clients);
|
|
162
123
|
}
|
|
163
124
|
)
|
|
164
125
|
.option('format', {
|
|
165
126
|
alias: 'f',
|
|
166
127
|
type: 'string',
|
|
167
128
|
default: 'json',
|
|
168
|
-
description: 'output format, e.g. JSON|CSV'
|
|
129
|
+
description: 'output format, e.g. JSON|CSV',
|
|
169
130
|
})
|
|
170
131
|
.option('output', {
|
|
171
132
|
alias: 'o',
|
|
172
133
|
type: 'string',
|
|
173
134
|
default: 'stdout',
|
|
174
|
-
description: 'output channel'
|
|
135
|
+
description: 'output channel',
|
|
175
136
|
})
|
|
176
137
|
.option('webhookType', {
|
|
177
138
|
alias: 'w',
|
|
178
139
|
type: 'string',
|
|
179
140
|
default: 'slack',
|
|
180
|
-
description: 'Webhook Type'
|
|
141
|
+
description: 'Webhook Type',
|
|
181
142
|
})
|
|
182
143
|
.option('webhookMessage', {
|
|
183
144
|
alias: 'm',
|
|
184
145
|
type: 'string',
|
|
185
|
-
description: 'Webhook Message'
|
|
146
|
+
description: 'Webhook Message',
|
|
186
147
|
})
|
|
187
148
|
.option('webhookUrl', {
|
|
188
149
|
alias: 't',
|
|
189
150
|
type: 'string',
|
|
190
|
-
description: 'Webhook URL'
|
|
151
|
+
description: 'Webhook URL',
|
|
191
152
|
})
|
|
192
153
|
.option('reports', {
|
|
193
154
|
alias: 'r',
|
|
194
155
|
type: 'string',
|
|
195
|
-
description: 'Reports directory'
|
|
156
|
+
description: 'Reports directory',
|
|
157
|
+
})
|
|
158
|
+
.option('useAuditingEndpoint', {
|
|
159
|
+
alias: 'a',
|
|
160
|
+
type: 'boolean',
|
|
161
|
+
default: false,
|
|
162
|
+
description: 'use auditior rest endpoint',
|
|
196
163
|
})
|
|
197
164
|
.parse();
|
package/config/schema.json
CHANGED
package/index.ts
CHANGED
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.7.0",
|
|
4
4
|
"description": "Reporting Tools for Keycloak",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -25,9 +25,10 @@
|
|
|
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
30
|
"@keycloak/keycloak-admin-client": "^22.0.0",
|
|
30
|
-
"@slack/webhook": "^
|
|
31
|
+
"@slack/webhook": "^7.0.0",
|
|
31
32
|
"ajv": "^8.12.0",
|
|
32
33
|
"install": "^0.13.0",
|
|
33
34
|
"ms-teams-webhook": "^2.0.2",
|
|
@@ -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
|
package/src/commands.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import KcAdminClient from '@keycloak/keycloak-admin-client';
|
|
2
|
+
import {
|
|
3
|
+
AuditClient,
|
|
4
|
+
AuditedClientRepresentation,
|
|
5
|
+
AuditedUserRepresentation,
|
|
6
|
+
} from '@continuoussecuritytooling/keycloak-auditor';
|
|
7
|
+
import { Options, createClient } from '../lib/client.js';
|
|
8
|
+
import { User, userListing, clientListing, Client } from '../lib/user.js';
|
|
9
|
+
|
|
10
|
+
function kcClient(options: Options): Promise<KcAdminClient | AuditClient> {
|
|
11
|
+
return createClient({
|
|
12
|
+
clientId: options.clientId,
|
|
13
|
+
clientSecret: options.clientSecret,
|
|
14
|
+
rootUrl: options.rootUrl,
|
|
15
|
+
useAuditingEndpoint: options.useAuditingEndpoint,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function listUsers(options: Options): Promise<Array<User | AuditedUserRepresentation>> {
|
|
20
|
+
const users = await userListing(await kcClient(options));
|
|
21
|
+
return new Promise((resolve) => resolve(users));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function listClients(options: Options): Promise<Array<Client | AuditedClientRepresentation>> {
|
|
25
|
+
const clients = await clientListing(await kcClient(options));
|
|
26
|
+
return new Promise((resolve) => resolve(clients));
|
|
27
|
+
}
|
package/src/config.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
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';
|
|
@@ -8,16 +7,15 @@ const schema = JSON.parse(
|
|
|
8
7
|
fs.readFileSync(fileURLToPath(path.join(import.meta.url, '../../config/schema.json')), 'utf8')
|
|
9
8
|
);
|
|
10
9
|
|
|
11
|
-
const ajv = new Ajv.default();
|
|
12
|
-
const ajvValidate = ajv.compile(schema);
|
|
13
|
-
|
|
14
10
|
// import the config file
|
|
15
11
|
function buildConfigFromFile(filePath) {
|
|
16
12
|
if (!filePath) return {};
|
|
17
13
|
const isAbsolutePath = filePath.charAt(0) === '/';
|
|
18
|
-
return JSON.parse(
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
return JSON.parse(
|
|
15
|
+
isAbsolutePath
|
|
16
|
+
? fs.readFileSync(filePath, 'utf8')
|
|
17
|
+
: fs.readFileSync(fileURLToPath(path.join(import.meta.url, '../config', filePath)), 'utf8')
|
|
18
|
+
);
|
|
21
19
|
}
|
|
22
20
|
// build an object using the defaults in the schema
|
|
23
21
|
function buildDefaults(schema, definitions) {
|
|
@@ -50,19 +48,9 @@ function buildEnvironmentVariablesConfig(schema) {
|
|
|
50
48
|
}
|
|
51
49
|
}, {});
|
|
52
50
|
}
|
|
53
|
-
|
|
54
|
-
function validate(data) {
|
|
55
|
-
const valid = ajvValidate(data);
|
|
56
|
-
if (valid) return true;
|
|
57
|
-
throw new Error(ajv.errorsText());
|
|
58
|
-
}
|
|
59
|
-
|
|
60
51
|
// merge the environment variables, config file values, and defaults
|
|
61
52
|
const config = mergeAll(
|
|
62
|
-
mergeDeepRight(
|
|
63
|
-
buildDefaults(schema, schema.definitions),
|
|
64
|
-
buildConfigFromFile(process.env.CONFIG_FILE)
|
|
65
|
-
),
|
|
53
|
+
mergeDeepRight(buildDefaults(schema, schema.definitions), buildConfigFromFile(process.env.CONFIG_FILE)),
|
|
66
54
|
buildEnvironmentVariablesConfig(schema)
|
|
67
55
|
);
|
|
68
56
|
|
package/config.json
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"url": "https://id.m13t.de",
|
|
3
|
-
"clientId": "admin-cli",
|
|
4
|
-
"clientSecret": "PWkJ98Atq36QFP5Z25YXJDWs4tvsGvkI",
|
|
5
|
-
"output": "webhook",
|
|
6
|
-
"webhookType": "teams",
|
|
7
|
-
"webhookUrl": "https://m13t4mgmt.webhook.office.com/webhookb2/02950819-c8ca-4c83-9751-808d801e8810@09f6f098-3af9-474c-a398-d17786fff1bf/IncomingWebhook/b06222e267a04255aaa32a341acb1749/a4f92b5b-01c7-40a8-91ff-0695e08d76ff",
|
|
8
|
-
"webhookMessage": "TEST"
|
|
9
|
-
}
|