@continuoussecuritytooling/keycloak-reporter 0.1.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.
Files changed (45) hide show
  1. package/.bin/start-server.mjs +87 -0
  2. package/.bin/wait-for-server.sh +13 -0
  3. package/.docs/webhook-slack-sample.png +0 -0
  4. package/.docs/webhook-teams-sample.png +0 -0
  5. package/.editorconfig +23 -0
  6. package/.eslintignore +1 -0
  7. package/.eslintrc.cjs +14 -0
  8. package/.github/FUNDING.yml +2 -0
  9. package/.github/workflows/pipeline.yml +101 -0
  10. package/Dockerfile +9 -0
  11. package/LICENSE +21 -0
  12. package/README.md +43 -0
  13. package/cli.ts +125 -0
  14. package/dist/cli.js +83 -0
  15. package/dist/cli.js.map +1 -0
  16. package/dist/index.js +83 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/lib/client.js +41 -0
  19. package/dist/lib/client.js.map +1 -0
  20. package/dist/lib/convert.js +9 -0
  21. package/dist/lib/convert.js.map +1 -0
  22. package/dist/lib/output.js +98 -0
  23. package/dist/lib/output.js.map +1 -0
  24. package/dist/lib/user.js +75 -0
  25. package/dist/lib/user.js.map +1 -0
  26. package/dist/package.json +53 -0
  27. package/dist/src/cli.js +19 -0
  28. package/dist/src/cli.js.map +1 -0
  29. package/docker_entrypoint.sh +3 -0
  30. package/e2e/fixtures/auth-utils/test-realm.json +5095 -0
  31. package/e2e/run-tests.sh +45 -0
  32. package/e2e/spec/clients.js +29 -0
  33. package/e2e/spec/users.js +29 -0
  34. package/e2e/spec/webhooks.js +60 -0
  35. package/index.ts +125 -0
  36. package/jest.config.js +25 -0
  37. package/lib/client.ts +55 -0
  38. package/lib/convert.ts +9 -0
  39. package/lib/output.ts +115 -0
  40. package/lib/user.ts +99 -0
  41. package/package.json +53 -0
  42. package/renovate.json +14 -0
  43. package/src/cli.ts +26 -0
  44. package/test/convert.spec.ts +15 -0
  45. package/tsconfig.json +11 -0
@@ -0,0 +1,9 @@
1
+ import { AsyncParser } from '@json2csv/node';
2
+ export async function convertJSON2CSV(json) {
3
+ const opts = {};
4
+ const transformOpts = {};
5
+ const asyncOpts = {};
6
+ const parser = new AsyncParser(opts, transformOpts, asyncOpts);
7
+ return await parser.parse(json).promise();
8
+ }
9
+ //# sourceMappingURL=convert.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"convert.js","sourceRoot":"","sources":["../../lib/convert.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,IAAY;IAChD,MAAM,IAAI,GAAG,EAAE,CAAC;IAChB,MAAM,aAAa,GAAG,EAAE,CAAC;IACzB,MAAM,SAAS,GAAG,EAAE,CAAC;IACrB,MAAM,MAAM,GAAG,IAAI,WAAW,CAAC,IAAI,EAAE,aAAa,EAAE,SAAS,CAAC,CAAC;IAC/D,OAAO,MAAM,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;AAC5C,CAAC"}
@@ -0,0 +1,98 @@
1
+ import { IncomingWebhook as TeamsWebhook } from 'ms-teams-webhook';
2
+ import { IncomingWebhook as SlackWebhook } from '@slack/webhook';
3
+ var WebhookType;
4
+ (function (WebhookType) {
5
+ WebhookType["SLACK"] = "slack";
6
+ WebhookType["TEAMS"] = "teams";
7
+ })(WebhookType || (WebhookType = {}));
8
+ export async function post2Webhook(type, url, title, reportContent) {
9
+ //const title= 'Keycloak Reporting';
10
+ const date = new Date();
11
+ switch (type) {
12
+ case WebhookType.TEAMS.toString():
13
+ return new TeamsWebhook(url).send({
14
+ type: 'message',
15
+ attachments: [
16
+ {
17
+ contentType: 'application/vnd.microsoft.card.adaptive',
18
+ content: {
19
+ $schema: 'http://adaptivecards.io/schemas/adaptive-card.json',
20
+ type: 'AdaptiveCard',
21
+ version: '1.2',
22
+ body: [
23
+ {
24
+ type: 'FactSet',
25
+ facts: [
26
+ {
27
+ title: 'Type',
28
+ value: title,
29
+ },
30
+ {
31
+ title: 'Date',
32
+ value: `${date.getDate()}-${date.getMonth() + 1}-${date.getFullYear()}`,
33
+ },
34
+ ],
35
+ },
36
+ ],
37
+ actions: [
38
+ {
39
+ type: 'Action.ShowCard',
40
+ title: 'Show raw report data',
41
+ card: {
42
+ type: 'AdaptiveCard',
43
+ body: [
44
+ {
45
+ type: 'TextBlock',
46
+ text: reportContent,
47
+ wrap: true,
48
+ },
49
+ ],
50
+ $schema: 'http://adaptivecards.io/schemas/adaptive-card.json',
51
+ },
52
+ },
53
+ ],
54
+ },
55
+ },
56
+ ],
57
+ });
58
+ // defaulting to Slack
59
+ default:
60
+ return new SlackWebhook(url).send({
61
+ blocks: [
62
+ {
63
+ type: 'section',
64
+ fields: [
65
+ { type: 'mrkdwn', text: `*Type*: ${title}` },
66
+ {
67
+ type: 'mrkdwn',
68
+ text: `*Date*: ${date.getDate()}-${date.getMonth() + 1}-${date.getFullYear()}`,
69
+ },
70
+ ],
71
+ },
72
+ {
73
+ type: 'divider',
74
+ },
75
+ {
76
+ type: 'context',
77
+ elements: [
78
+ {
79
+ type: 'mrkdwn',
80
+ text: `
81
+ \`\`\`
82
+ ${reportContent}
83
+ \`\`\`
84
+ `
85
+ },
86
+ ],
87
+ },
88
+ {
89
+ type: 'context',
90
+ elements: [{ type: 'plain_text', text: 'Raw report data' }],
91
+ },
92
+ ],
93
+ });
94
+ }
95
+ }
96
+ /*
97
+ */
98
+ //# sourceMappingURL=output.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output.js","sourceRoot":"","sources":["../../lib/output.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,IAAI,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAEnE,OAAO,EAAE,eAAe,IAAI,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEjE,IAAK,WAGJ;AAHD,WAAK,WAAW;IACd,8BAAe,CAAA;IACf,8BAAe,CAAA;AACjB,CAAC,EAHI,WAAW,KAAX,WAAW,QAGf;AAOD,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,IAAY,EACZ,GAAW,EACX,KAAa,EACb,aAAqB;IAErB,oCAAoC;IACpC,MAAM,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC;IACxB,QAAQ,IAAI,EAAE;QACZ,KAAK,WAAW,CAAC,KAAK,CAAC,QAAQ,EAAE;YAC/B,OAAO,IAAI,YAAY,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;gBAChC,IAAI,EAAE,SAAS;gBACf,WAAW,EAAE;oBACX;wBACE,WAAW,EAAE,yCAAyC;wBACtD,OAAO,EAAE;4BACP,OAAO,EAAE,oDAAoD;4BAC7D,IAAI,EAAE,cAAc;4BACpB,OAAO,EAAE,KAAK;4BACd,IAAI,EAAE;gCACJ;oCACE,IAAI,EAAE,SAAS;oCACf,KAAK,EAAE;wCACL;4CACE,KAAK,EAAE,MAAM;4CACb,KAAK,EAAE,KAAK;yCACb;wCACD;4CACE,KAAK,EAAE,MAAM;4CACb,KAAK,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,IACtB,IAAI,CAAC,QAAQ,EAAE,GAAG,CACpB,IAAI,IAAI,CAAC,WAAW,EAAE,EAAE;yCACzB;qCACF;iCACF;6BACF;4BACD,OAAO,EAAE;gCACP;oCACE,IAAI,EAAE,iBAAiB;oCACvB,KAAK,EAAE,sBAAsB;oCAC7B,IAAI,EAAE;wCACJ,IAAI,EAAE,cAAc;wCACpB,IAAI,EAAE;4CACJ;gDACE,IAAI,EAAE,WAAW;gDACjB,IAAI,EAAE,aAAa;gDACnB,IAAI,EAAE,IAAI;6CACX;yCACF;wCACD,OAAO,EACL,oDAAoD;qCACvD;iCACF;6BACF;yBACF;qBACF;iBACF;aACF,CAAC,CAAC;QACL,sBAAsB;QACtB;YACE,OAAO,IAAI,YAAY,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;gBAChC,MAAM,EAAE;oBACN;wBACE,IAAI,EAAE,SAAS;wBACf,MAAM,EAAE;4BACN,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,KAAK,EAAE,EAAE;4BAC5C;gCACE,IAAI,EAAE,QAAQ;gCACd,IAAI,EAAE,WAAW,IAAI,CAAC,OAAO,EAAE,IAC7B,IAAI,CAAC,QAAQ,EAAE,GAAG,CACpB,IAAI,IAAI,CAAC,WAAW,EAAE,EAAE;6BACzB;yBACF;qBACF;oBACD;wBACE,IAAI,EAAE,SAAS;qBAChB;oBACD;wBACE,IAAI,EAAE,SAAS;wBACf,QAAQ,EAAE;4BACR;gCACE,IAAI,EAAE,QAAQ;gCACd,IAAI,EAAE;;EAEpB,aAAa;;CAEd;6BACc;yBACF;qBACF;oBACD;wBACE,IAAI,EAAE,SAAS;wBACf,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,iBAAiB,EAAE,CAAC;qBAC5D;iBACF;aACF,CAAC,CAAC;KACN;AACH,CAAC;AAED;GACG"}
@@ -0,0 +1,75 @@
1
+ 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
+ let allClients = new Array();
13
+ for (const realm of realms) {
14
+ // switch realm
15
+ client.setConfig({
16
+ realmName: realm.realm,
17
+ });
18
+ const realmClients = new Array();
19
+ for (const user of await client.clients.find()) {
20
+ realmClients.push({
21
+ client: user.clientId,
22
+ id: user.id,
23
+ description: user.description,
24
+ realm: realm.realm,
25
+ enabled: user.enabled,
26
+ public: user.publicClient,
27
+ allowedOrigins: JSON.stringify(user.webOrigins),
28
+ });
29
+ }
30
+ allClients = [...allClients, ...realmClients];
31
+ }
32
+ // switch back to realm
33
+ client.setConfig({
34
+ realmName: currentRealm,
35
+ });
36
+ return new Promise((resolve) => resolve(allClients));
37
+ }
38
+ 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
+ let allUsers = new Array();
50
+ for (const realm of realms) {
51
+ // switch realm
52
+ client.setConfig({
53
+ realmName: realm.realm,
54
+ });
55
+ const realmUsers = new Array();
56
+ for (const user of await client.users.find()) {
57
+ realmUsers.push({
58
+ username: user.username,
59
+ id: user.id,
60
+ firstName: user.firstName,
61
+ lastName: user.lastName,
62
+ email: user.email,
63
+ realm: realm.realm,
64
+ enabled: user.enabled,
65
+ });
66
+ }
67
+ allUsers = [...allUsers, ...realmUsers];
68
+ }
69
+ // switch back to realm
70
+ client.setConfig({
71
+ realmName: currentRealm,
72
+ });
73
+ return new Promise((resolve) => resolve(allUsers));
74
+ }
75
+ //# sourceMappingURL=user.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"user.js","sourceRoot":"","sources":["../../lib/user.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@continuoussecuritytooling/keycloak-reporter",
3
+ "version": "0.1.0",
4
+ "description": "Reporting Tools for Keycloak",
5
+ "main": "dist/index.js",
6
+ "bin": "dist/cli.js",
7
+ "type": "module",
8
+ "scripts": {
9
+ "clean": "rm -rf dist/* && npm i",
10
+ "build": "tsc && chmod +x dist/index.js && cp package.json dist/",
11
+ "test": "eslint . && jest",
12
+ "end2end:start-server": ".bin/start-server.mjs -Dkeycloak.profile.feature.account_api=disabled -Dkeycloak.profile.feature.account2=disabled -Dkeycloak.migration.action=import -Dkeycloak.migration.provider=singleFile -Dkeycloak.migration.file=e2e/fixtures/auth-utils/test-realm.json -Dkeycloak.migration.strategy=OVERWRITE_EXISTING",
13
+ "end2end:test": "./e2e/run-tests.sh"
14
+ },
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/ContinuousSecurityTooling/keycloak-reporter.git"
18
+ },
19
+ "author": "Martin Reinhardt",
20
+ "license": "MIT",
21
+ "bugs": {
22
+ "url": "https://github.com/ContinuousSecurityTooling/keycloak-reporter/issues"
23
+ },
24
+ "homepage": "https://github.com/ContinuousSecurityTooling/keycloak-reporter#readme",
25
+ "dependencies": {
26
+ "@json2csv/node": "^7.0.0",
27
+ "@keycloak/keycloak-admin-client": "^20.0.5",
28
+ "@slack/webhook": "^6.1.0",
29
+ "install": "^0.13.0",
30
+ "ms-teams-webhook": "^2.0.2",
31
+ "npm": "^9.6.7",
32
+ "openid-client": "^5.4.2",
33
+ "yargs": "^17.7.2"
34
+ },
35
+ "devDependencies": {
36
+ "@octokit/rest": "^19.0.11",
37
+ "@types/jest": "^29.5.1",
38
+ "@types/node": "^20.1.5",
39
+ "@types/yargs": "^17.0.24",
40
+ "@typescript-eslint/eslint-plugin": "^5.59.6",
41
+ "@typescript-eslint/parser": "^5.59.6",
42
+ "eslint": "^8.40.0",
43
+ "gunzip-maybe": "^1.4.2",
44
+ "jest": "^29.5.0",
45
+ "jest-extended": "^3.2.4",
46
+ "node-fetch": "^3.3.1",
47
+ "tap-xunit": "^2.4.1",
48
+ "tape": "^5.6.3",
49
+ "tar-fs": "^2.1.1",
50
+ "ts-jest": "^29.1.0",
51
+ "typescript": "^5.0.4"
52
+ }
53
+ }
@@ -0,0 +1,19 @@
1
+ import { createClient } from '../lib/client.js';
2
+ import { userListing, clientListing } from '../lib/user.js';
3
+ export async function listUsers(options) {
4
+ const users = await userListing(await createClient({
5
+ clientId: options.clientId,
6
+ clientSecret: options.clientSecret,
7
+ rootUrl: options.rootUrl,
8
+ }));
9
+ return new Promise((resolve) => resolve(users));
10
+ }
11
+ export async function listClients(options) {
12
+ const clients = await clientListing(await createClient({
13
+ clientId: options.clientId,
14
+ clientSecret: options.clientSecret,
15
+ rootUrl: options.rootUrl,
16
+ }));
17
+ return new Promise((resolve) => resolve(clients));
18
+ }
19
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAQ,WAAW,EAAE,aAAa,EAAU,MAAM,gBAAgB,CAAC;AAE1E,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,OAAgB;IAC9C,MAAM,KAAK,GAAG,MAAM,WAAW,CAC7B,MAAM,YAAY,CAAC;QACjB,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,YAAY,EAAE,OAAO,CAAC,YAAY;QAClC,OAAO,EAAE,OAAO,CAAC,OAAO;KACzB,CAAC,CACH,CAAC;IAEF,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,CACjC,MAAM,YAAY,CAAC;QACjB,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,YAAY,EAAE,OAAO,CAAC,YAAY;QAClC,OAAO,EAAE,OAAO,CAAC,OAAO;KACzB,CAAC,CACH,CAAC;IAEF,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;AACpD,CAAC"}
@@ -0,0 +1,3 @@
1
+ #!/bin/bash
2
+
3
+ node index.js "$@"