@continuoussecuritytooling/keycloak-reporter 0.1.1 → 0.2.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/.github/workflows/pipeline.yml +6 -3
- package/Dockerfile +3 -1
- package/README.md +16 -4
- package/cli.ts +15 -10
- package/config/schema.json +57 -0
- package/dist/cli.js +8 -9
- package/dist/cli.js.map +1 -1
- package/dist/config/schema.json +57 -0
- package/dist/index.js +3 -82
- package/dist/index.js.map +1 -1
- package/dist/lib/client.js +1 -1
- package/dist/lib/client.js.map +1 -1
- package/dist/src/config.js +57 -0
- package/dist/src/config.js.map +1 -0
- package/docker_entrypoint.sh +1 -1
- package/e2e/fixtures/config.json +5 -0
- package/e2e/spec/clients.js +5 -1
- package/e2e/spec/config.js +31 -0
- package/e2e/spec/users.js +5 -1
- package/e2e/spec/webhooks.js +8 -2
- package/index.ts +4 -125
- package/lib/client.ts +4 -1
- package/package.json +4 -3
- package/src/config.ts +69 -0
- package/dist/package.json +0 -56
|
@@ -41,8 +41,9 @@ jobs:
|
|
|
41
41
|
matrix:
|
|
42
42
|
node_version:
|
|
43
43
|
- 16
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
# TODO: Support Node 16+
|
|
45
|
+
#- 18
|
|
46
|
+
#- 20
|
|
46
47
|
os:
|
|
47
48
|
- ubuntu-latest
|
|
48
49
|
steps:
|
|
@@ -72,11 +73,13 @@ jobs:
|
|
|
72
73
|
name: Build Container Image
|
|
73
74
|
runs-on: ubuntu-latest
|
|
74
75
|
needs:
|
|
75
|
-
- build
|
|
76
76
|
- end2end
|
|
77
77
|
steps:
|
|
78
78
|
- uses: actions/checkout@v3
|
|
79
79
|
- uses: actions/setup-node@v3
|
|
80
|
+
# TODO: Support Node 16+
|
|
81
|
+
with:
|
|
82
|
+
node-version: "16"
|
|
80
83
|
- name: "Build Package"
|
|
81
84
|
run: |
|
|
82
85
|
npm run clean
|
package/Dockerfile
CHANGED
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
[](LICENSE)
|
|
5
5
|
[](https://github.com/ContinuousSecurityTooling/keycloak-reporter/actions/workflows/pipeline.yml)
|
|
6
|
-
[](https://
|
|
6
|
+
[](https://www.npmjs.com/package/@continuoussecuritytooling/keycloak-reporter)
|
|
7
7
|
[](https://www.npmjs.com/package/@continuoussecuritytooling/keycloak-reporter)
|
|
8
8
|
[](https://www.npmjs.com/package/@continuoussecuritytooling/keycloak-reporter)
|
|
9
9
|
[](https://hub.docker.com/r/continuoussecuritytooling/keycloak-reporting-cli/)
|
|
@@ -14,7 +14,12 @@
|
|
|
14
14
|
## Usage
|
|
15
15
|
|
|
16
16
|
```
|
|
17
|
-
|
|
17
|
+
npm i @continuoussecuritytooling/keycloak-reporter --location=global
|
|
18
|
+
kc-reporter help
|
|
19
|
+
```
|
|
20
|
+
For listing clients:
|
|
21
|
+
```
|
|
22
|
+
kc-reporter listClients <Keycloak_Root_URL> <Client_ID> <Client_Secret> --format=csv
|
|
18
23
|
```
|
|
19
24
|
|
|
20
25
|
The output looks for CSV, like that:
|
|
@@ -35,18 +40,25 @@ Valid commands are:
|
|
|
35
40
|
|
|
36
41
|
## Advanced
|
|
37
42
|
|
|
43
|
+
### Config file
|
|
44
|
+
|
|
45
|
+
You can also provider a config file via env var `CONFIG_FILE` and then just provide the commands, e.g.:
|
|
46
|
+
```
|
|
47
|
+
CONFIG_FILE=e2e/fixtures/config.json kc-reporter listClients
|
|
48
|
+
```
|
|
49
|
+
|
|
38
50
|
### Post to Slack or Teams
|
|
39
51
|
|
|
40
52
|
When using this command:
|
|
41
53
|
```
|
|
42
|
-
|
|
54
|
+
kc-reporter listUsers <Keycloak_Root_URL> <Client_ID> <Client_Secret> --format=json --output=webhook --webhookType=slack --webhookUrl=$WEBHOOK_TESTING_SLACK
|
|
43
55
|
```
|
|
44
56
|
the following entry in slack will be created:
|
|
45
57
|

|
|
46
58
|
|
|
47
59
|
And for Teams:
|
|
48
60
|
```
|
|
49
|
-
|
|
61
|
+
kc-reporter listUsers <Keycloak_Root_URL> <Client_ID> <Client_Secret> --format=json --output=webhook --webhookType=teams --webhookUrl=$WEBHOOK_TESTING_TEAMS
|
|
50
62
|
```
|
|
51
63
|
the following entry in slack will be created:
|
|
52
64
|

|
package/cli.ts
CHANGED
|
@@ -2,10 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
import yargs from 'yargs/yargs';
|
|
4
4
|
import { hideBin } from 'yargs/helpers';
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
import {
|
|
6
|
+
listUsers,
|
|
7
|
+
listClients,
|
|
8
|
+
Options,
|
|
9
|
+
convertJSON2CSV,
|
|
10
|
+
post2Webhook,
|
|
11
|
+
} from './index.js';
|
|
12
|
+
import config from './src/config.js';
|
|
13
|
+
|
|
9
14
|
|
|
10
15
|
class WebhookConfig {
|
|
11
16
|
type: string;
|
|
@@ -60,9 +65,9 @@ yargs(hideBin(process.argv))
|
|
|
60
65
|
() => {},
|
|
61
66
|
async (argv) => {
|
|
62
67
|
const users = await listUsers(<Options>{
|
|
63
|
-
clientId: argv.clientId as string,
|
|
64
|
-
clientSecret: argv.clientSecret as string,
|
|
65
|
-
rootUrl: argv.url as string,
|
|
68
|
+
clientId: argv.clientId ? argv.clientId as string: config.clientId,
|
|
69
|
+
clientSecret: argv.clientSecret ? argv.clientSecret as string: config.clientSecret,
|
|
70
|
+
rootUrl:argv.url ? argv.url as string: config.url,
|
|
66
71
|
});
|
|
67
72
|
await convert(
|
|
68
73
|
argv.format as string,
|
|
@@ -83,9 +88,9 @@ yargs(hideBin(process.argv))
|
|
|
83
88
|
() => {},
|
|
84
89
|
async (argv) => {
|
|
85
90
|
const clients = await listClients(<Options>{
|
|
86
|
-
clientId: argv.clientId as string,
|
|
87
|
-
clientSecret: argv.clientSecret as string,
|
|
88
|
-
rootUrl: argv.url as string,
|
|
91
|
+
clientId: argv.clientId ? argv.clientId as string: config.clientId,
|
|
92
|
+
clientSecret: argv.clientSecret ? argv.clientSecret as string: config.clientSecret,
|
|
93
|
+
rootUrl:argv.url ? argv.url as string: config.url,
|
|
89
94
|
});
|
|
90
95
|
await convert(
|
|
91
96
|
argv.format as string,
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$id": "https://github.com/ContinuousSecurityTooling/keycloak-reporter/blob/main/config/schema.json",
|
|
3
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
4
|
+
"title": "Keycloak Reporter Config",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"definitions": {},
|
|
7
|
+
"required": ["url", "clientId", "clientSecret"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"command": {
|
|
10
|
+
"type": "array",
|
|
11
|
+
"items": {
|
|
12
|
+
"type": "string",
|
|
13
|
+
"enum": ["listClients", "listUsers"]
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"url": {
|
|
17
|
+
"type": "string",
|
|
18
|
+
"description": "Keycloak Server URL"
|
|
19
|
+
},
|
|
20
|
+
"clientId": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"description": "Keycloak Client used for reporting"
|
|
23
|
+
},
|
|
24
|
+
"clientSecret": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"description": "Keycloak Client Secret used for reporting"
|
|
27
|
+
},
|
|
28
|
+
"output": {
|
|
29
|
+
"type": "array",
|
|
30
|
+
"items": {
|
|
31
|
+
"type": "string",
|
|
32
|
+
"enum": ["webhook", "stdout"]
|
|
33
|
+
},
|
|
34
|
+
"description": "Output channel to use"
|
|
35
|
+
},
|
|
36
|
+
"format": {
|
|
37
|
+
"type": "array",
|
|
38
|
+
"items": {
|
|
39
|
+
"type": "string",
|
|
40
|
+
"enum": ["json", "csv"]
|
|
41
|
+
},
|
|
42
|
+
"description": "Report format"
|
|
43
|
+
},
|
|
44
|
+
"webhookType": {
|
|
45
|
+
"type": "array",
|
|
46
|
+
"items": {
|
|
47
|
+
"type": "string",
|
|
48
|
+
"enum": ["slack", "teams"]
|
|
49
|
+
},
|
|
50
|
+
"description": "Type of webhook"
|
|
51
|
+
},
|
|
52
|
+
"webhookUrl": {
|
|
53
|
+
"type": "string",
|
|
54
|
+
"description": "URL of the webhook"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import yargs from 'yargs/yargs';
|
|
3
3
|
import { hideBin } from 'yargs/helpers';
|
|
4
|
-
import { listUsers, listClients } from './
|
|
5
|
-
import
|
|
6
|
-
import { post2Webhook } from './lib/output.js';
|
|
4
|
+
import { listUsers, listClients, convertJSON2CSV, post2Webhook, } from './index.js';
|
|
5
|
+
import config from './src/config.js';
|
|
7
6
|
class WebhookConfig {
|
|
8
7
|
constructor(type, url, title) {
|
|
9
8
|
this.type = type;
|
|
@@ -40,9 +39,9 @@ yargs(hideBin(process.argv))
|
|
|
40
39
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
41
40
|
() => { }, async (argv) => {
|
|
42
41
|
const users = await listUsers({
|
|
43
|
-
clientId: argv.clientId,
|
|
44
|
-
clientSecret: argv.clientSecret,
|
|
45
|
-
rootUrl: argv.url,
|
|
42
|
+
clientId: argv.clientId ? argv.clientId : config.clientId,
|
|
43
|
+
clientSecret: argv.clientSecret ? argv.clientSecret : config.clientSecret,
|
|
44
|
+
rootUrl: argv.url ? argv.url : config.url,
|
|
46
45
|
});
|
|
47
46
|
await convert(argv.format, argv.output, new WebhookConfig(argv.webhookType, argv.webhookUrl, 'User Listing'), users);
|
|
48
47
|
})
|
|
@@ -50,9 +49,9 @@ yargs(hideBin(process.argv))
|
|
|
50
49
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
51
50
|
() => { }, async (argv) => {
|
|
52
51
|
const clients = await listClients({
|
|
53
|
-
clientId: argv.clientId,
|
|
54
|
-
clientSecret: argv.clientSecret,
|
|
55
|
-
rootUrl: argv.url,
|
|
52
|
+
clientId: argv.clientId ? argv.clientId : config.clientId,
|
|
53
|
+
clientSecret: argv.clientSecret ? argv.clientSecret : config.clientSecret,
|
|
54
|
+
rootUrl: argv.url ? argv.url : config.url,
|
|
56
55
|
});
|
|
57
56
|
await convert(argv.format, argv.output, new WebhookConfig(argv.webhookType, argv.webhookUrl, 'Client Listing'), clients);
|
|
58
57
|
})
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../cli.ts"],"names":[],"mappings":";AAEA,OAAO,KAAK,MAAM,aAAa,CAAC;AAChC,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../cli.ts"],"names":[],"mappings":";AAEA,OAAO,KAAK,MAAM,aAAa,CAAC;AAChC,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EACL,SAAS,EACT,WAAW,EAEX,eAAe,EACf,YAAY,GACb,MAAM,YAAY,CAAC;AACpB,OAAO,MAAM,MAAM,iBAAiB,CAAC;AAGrC,MAAM,aAAa;IAIjB,YAAY,IAAY,EAAE,GAAW,EAAE,KAAa;QAClD,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;CACF;AAED,KAAK,UAAU,OAAO,CACpB,MAAc,EACd,MAAc,EACd,MAAqB,EACrB,IAAY;IAEZ,IAAI,aAAqB,CAAC;IAC1B,QAAQ,MAAM,EAAE;QACd,KAAK,KAAK;YACR,aAAa,GAAG,CAAC,MAAM,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;YACzD,MAAM;QACR,qBAAqB;QACrB;YACE,aAAa,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;KACxC;IACD,QAAQ,MAAM,EAAE;QACd,KAAK,SAAS;YACZ,IAAI;gBACF,MAAM,YAAY,CAChB,MAAM,CAAC,IAAI,EACX,MAAM,CAAC,GAAG,EACV,MAAM,CAAC,KAAK,EACZ,aAAa,CACd,CAAC;aACH;YAAC,OAAO,CAAC,EAAE;gBACV,OAAO,CAAC,KAAK,CAAC,gCAAgC,EAAE,CAAC,CAAC,CAAC;aACpD;YACD,MAAM;QACR,6BAA6B;QAC7B;YACE,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;KAC9B;AACH,CAAC;AAED,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;KACzB,OAAO,CACN,2CAA2C,EAC3C,kCAAkC;AAClC,gEAAgE;AAChE,GAAG,EAAE,GAAE,CAAC,EACR,KAAK,EAAE,IAAI,EAAE,EAAE;IACb,MAAM,KAAK,GAAG,MAAM,SAAS,CAAU;QACrC,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAkB,CAAA,CAAC,CAAC,MAAM,CAAC,QAAQ;QAClE,YAAY,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,YAAsB,CAAA,CAAC,CAAC,MAAM,CAAC,YAAY;QAClF,OAAO,EAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,GAAa,CAAA,CAAC,CAAC,MAAM,CAAC,GAAG;KAClD,CAAC,CAAC;IACH,MAAM,OAAO,CACX,IAAI,CAAC,MAAgB,EACrB,IAAI,CAAC,MAAgB,EACrB,IAAI,aAAa,CACf,IAAI,CAAC,WAAqB,EAC1B,IAAI,CAAC,UAAoB,EACzB,cAAc,CACf,EACD,KAAK,CACN,CAAC;AACJ,CAAC,CACF;KACA,OAAO,CACN,6CAA6C,EAC7C,oCAAoC;AACpC,gEAAgE;AAChE,GAAG,EAAE,GAAE,CAAC,EACR,KAAK,EAAE,IAAI,EAAE,EAAE;IACb,MAAM,OAAO,GAAG,MAAM,WAAW,CAAU;QACzC,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAkB,CAAA,CAAC,CAAC,MAAM,CAAC,QAAQ;QAClE,YAAY,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,YAAsB,CAAA,CAAC,CAAC,MAAM,CAAC,YAAY;QAClF,OAAO,EAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,GAAa,CAAA,CAAC,CAAC,MAAM,CAAC,GAAG;KAClD,CAAC,CAAC;IACH,MAAM,OAAO,CACX,IAAI,CAAC,MAAgB,EACrB,IAAI,CAAC,MAAgB,EACrB,IAAI,aAAa,CACf,IAAI,CAAC,WAAqB,EAC1B,IAAI,CAAC,UAAoB,EACzB,gBAAgB,CACjB,EACD,OAAO,CACR,CAAC;AACJ,CAAC,CACF;KACA,MAAM,CAAC,QAAQ,EAAE;IAChB,KAAK,EAAE,GAAG;IACV,IAAI,EAAE,QAAQ;IACd,OAAO,EAAE,MAAM;IACf,WAAW,EAAE,8BAA8B;CAC5C,CAAC;KACD,MAAM,CAAC,QAAQ,EAAE;IAChB,KAAK,EAAE,GAAG;IACV,IAAI,EAAE,QAAQ;IACd,OAAO,EAAE,QAAQ;IACjB,WAAW,EAAE,gBAAgB;CAC9B,CAAC;KACD,MAAM,CAAC,aAAa,EAAE;IACrB,KAAK,EAAE,GAAG;IACV,IAAI,EAAE,QAAQ;IACd,OAAO,EAAE,OAAO;IAChB,WAAW,EAAE,cAAc;CAC5B,CAAC;KACD,MAAM,CAAC,YAAY,EAAE;IACpB,KAAK,EAAE,GAAG;IACV,IAAI,EAAE,QAAQ;IACd,WAAW,EAAE,aAAa;CAC3B,CAAC;KACD,KAAK,EAAE,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$id": "https://github.com/ContinuousSecurityTooling/keycloak-reporter/blob/main/config/schema.json",
|
|
3
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
4
|
+
"title": "Keycloak Reporter Config",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"definitions": {},
|
|
7
|
+
"required": ["url", "clientId", "clientSecret"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"command": {
|
|
10
|
+
"type": "array",
|
|
11
|
+
"items": {
|
|
12
|
+
"type": "string",
|
|
13
|
+
"enum": ["listClients", "listUsers"]
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"url": {
|
|
17
|
+
"type": "string",
|
|
18
|
+
"description": "Keycloak Server URL"
|
|
19
|
+
},
|
|
20
|
+
"clientId": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"description": "Keycloak Client used for reporting"
|
|
23
|
+
},
|
|
24
|
+
"clientSecret": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"description": "Keycloak Client Secret used for reporting"
|
|
27
|
+
},
|
|
28
|
+
"output": {
|
|
29
|
+
"type": "array",
|
|
30
|
+
"items": {
|
|
31
|
+
"type": "string",
|
|
32
|
+
"enum": ["webhook", "stdout"]
|
|
33
|
+
},
|
|
34
|
+
"description": "Output channel to use"
|
|
35
|
+
},
|
|
36
|
+
"format": {
|
|
37
|
+
"type": "array",
|
|
38
|
+
"items": {
|
|
39
|
+
"type": "string",
|
|
40
|
+
"enum": ["json", "csv"]
|
|
41
|
+
},
|
|
42
|
+
"description": "Report format"
|
|
43
|
+
},
|
|
44
|
+
"webhookType": {
|
|
45
|
+
"type": "array",
|
|
46
|
+
"items": {
|
|
47
|
+
"type": "string",
|
|
48
|
+
"enum": ["slack", "teams"]
|
|
49
|
+
},
|
|
50
|
+
"description": "Type of webhook"
|
|
51
|
+
},
|
|
52
|
+
"webhookUrl": {
|
|
53
|
+
"type": "string",
|
|
54
|
+
"description": "URL of the webhook"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -1,83 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import { listUsers, listClients } from './src/cli.js';
|
|
5
|
-
import { convertJSON2CSV } from './lib/convert.js';
|
|
6
|
-
import { post2Webhook } from './lib/output.js';
|
|
7
|
-
class WebhookConfig {
|
|
8
|
-
constructor(type, url, title) {
|
|
9
|
-
this.type = type;
|
|
10
|
-
this.url = url;
|
|
11
|
-
this.title = title;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
async function convert(format, output, config, json) {
|
|
15
|
-
let outputContent;
|
|
16
|
-
switch (format) {
|
|
17
|
-
case 'csv':
|
|
18
|
-
outputContent = (await convertJSON2CSV(json)).toString();
|
|
19
|
-
break;
|
|
20
|
-
// defaulting to JSON
|
|
21
|
-
default:
|
|
22
|
-
outputContent = JSON.stringify(json);
|
|
23
|
-
}
|
|
24
|
-
switch (output) {
|
|
25
|
-
case 'webhook':
|
|
26
|
-
try {
|
|
27
|
-
await post2Webhook(config.type, config.url, config.title, outputContent);
|
|
28
|
-
}
|
|
29
|
-
catch (e) {
|
|
30
|
-
console.error('Error during sending webhook: ', e);
|
|
31
|
-
}
|
|
32
|
-
break;
|
|
33
|
-
// defaulting to standard out
|
|
34
|
-
default:
|
|
35
|
-
console.log(outputContent);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
yargs(hideBin(process.argv))
|
|
39
|
-
.command('listUsers [url] [clientId] [clientSecret]', 'fetches all users in the realms.',
|
|
40
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
41
|
-
() => { }, async (argv) => {
|
|
42
|
-
const users = await listUsers({
|
|
43
|
-
clientId: argv.clientId,
|
|
44
|
-
clientSecret: argv.clientSecret,
|
|
45
|
-
rootUrl: argv.url,
|
|
46
|
-
});
|
|
47
|
-
await convert(argv.format, argv.output, new WebhookConfig(argv.webhookType, argv.webhookUrl, 'User Listing'), users);
|
|
48
|
-
})
|
|
49
|
-
.command('listClients [url] [clientId] [clientSecret]', 'fetches all clients in the realms.',
|
|
50
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
51
|
-
() => { }, async (argv) => {
|
|
52
|
-
const clients = await listClients({
|
|
53
|
-
clientId: argv.clientId,
|
|
54
|
-
clientSecret: argv.clientSecret,
|
|
55
|
-
rootUrl: argv.url,
|
|
56
|
-
});
|
|
57
|
-
await convert(argv.format, argv.output, new WebhookConfig(argv.webhookType, argv.webhookUrl, 'Client Listing'), clients);
|
|
58
|
-
})
|
|
59
|
-
.option('format', {
|
|
60
|
-
alias: 'f',
|
|
61
|
-
type: 'string',
|
|
62
|
-
default: 'json',
|
|
63
|
-
description: 'output format, e.g. JSON|CSV',
|
|
64
|
-
})
|
|
65
|
-
.option('output', {
|
|
66
|
-
alias: 'o',
|
|
67
|
-
type: 'string',
|
|
68
|
-
default: 'stdout',
|
|
69
|
-
description: 'output channel',
|
|
70
|
-
})
|
|
71
|
-
.option('webhookType', {
|
|
72
|
-
alias: 'w',
|
|
73
|
-
type: 'string',
|
|
74
|
-
default: 'slack',
|
|
75
|
-
description: 'Webhook Type',
|
|
76
|
-
})
|
|
77
|
-
.option('webhookUrl', {
|
|
78
|
-
alias: 't',
|
|
79
|
-
type: 'string',
|
|
80
|
-
description: 'Webhook URL',
|
|
81
|
-
})
|
|
82
|
-
.parse();
|
|
1
|
+
export { listUsers, listClients } from './src/cli.js';
|
|
2
|
+
export { convertJSON2CSV } from './lib/convert.js';
|
|
3
|
+
export { post2Webhook } from './lib/output.js';
|
|
83
4
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEtD,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC"}
|
package/dist/lib/client.js
CHANGED
|
@@ -16,7 +16,7 @@ export async function createClient(options) {
|
|
|
16
16
|
});
|
|
17
17
|
}
|
|
18
18
|
catch (e) {
|
|
19
|
-
console.error('Check Client Config:', e.response.data.error_description);
|
|
19
|
+
console.error('Check Client Config:', e.response ? e.response.data.error_description : e);
|
|
20
20
|
return Promise.reject();
|
|
21
21
|
}
|
|
22
22
|
const keycloakIssuer = await Issuer.discover(`${options.rootUrl}/realms/master`);
|
package/dist/lib/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../lib/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACvC,OAAO,aAAa,MAAM,iCAAiC,CAAC;AAE5D,oCAAoC;AACpC,MAAM,aAAa,GAAG,EAAE,CAAC;AAQzB,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,OAAgB;IACjD,MAAM,aAAa,GAAG,IAAI,aAAa,CAAC;QACtC,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,SAAS,EAAE,QAAQ;KACpB,CAAC,CAAC;IAEH,IAAI;QACF,eAAe;QACf,MAAM,aAAa,CAAC,IAAI,CAAC;YACvB,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,YAAY,EAAE,OAAO,CAAC,YAAY;YAClC,SAAS,EAAE,oBAAoB;SAChC,CAAC,CAAC;KACJ;IAAC,OAAO,CAAC,EAAE;QACV,OAAO,CAAC,KAAK,
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../lib/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACvC,OAAO,aAAa,MAAM,iCAAiC,CAAC;AAE5D,oCAAoC;AACpC,MAAM,aAAa,GAAG,EAAE,CAAC;AAQzB,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,OAAgB;IACjD,MAAM,aAAa,GAAG,IAAI,aAAa,CAAC;QACtC,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,SAAS,EAAE,QAAQ;KACpB,CAAC,CAAC;IAEH,IAAI;QACF,eAAe;QACf,MAAM,aAAa,CAAC,IAAI,CAAC;YACvB,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,YAAY,EAAE,OAAO,CAAC,YAAY;YAClC,SAAS,EAAE,oBAAoB;SAChC,CAAC,CAAC;KACJ;IAAC,OAAO,CAAC,EAAE;QACV,OAAO,CAAC,KAAK,CACX,sBAAsB,EACtB,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CACnD,CAAC;QACF,OAAO,OAAO,CAAC,MAAM,EAAE,CAAC;KACzB;IAED,MAAM,cAAc,GAAG,MAAM,MAAM,CAAC,QAAQ,CAC1C,GAAG,OAAO,CAAC,OAAO,gBAAgB,CACnC,CAAC;IAEF,MAAM,MAAM,GAAG,IAAI,cAAc,CAAC,MAAM,CAAC;QACvC,SAAS,EAAE,OAAO,CAAC,QAAQ;QAC3B,0BAA0B,EAAE,MAAM,EAAE,uCAAuC;KAC5E,CAAC,CAAC;IAEH,gCAAgC;IAChC,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC;QAClC,SAAS,EAAE,OAAO,CAAC,QAAQ;QAC3B,aAAa,EAAE,OAAO,CAAC,YAAY;QACnC,UAAU,EAAE,oBAAoB;KACjC,CAAC,CAAC;IAEH;;;;;gCAK4B;IAE5B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC;AAC1D,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { assoc, pick, mergeAll, mergeDeepRight } from 'ramda';
|
|
2
|
+
import Ajv from 'ajv';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
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
|
+
// import the config file
|
|
10
|
+
function buildConfigFromFile(filePath) {
|
|
11
|
+
if (!filePath)
|
|
12
|
+
return {};
|
|
13
|
+
const isAbsolutePath = filePath.charAt(0) === '/';
|
|
14
|
+
return JSON.parse(isAbsolutePath
|
|
15
|
+
? fs.readFileSync(filePath, 'utf8')
|
|
16
|
+
: fs.readFileSync(fileURLToPath(path.join(import.meta.url, '../config', filePath)), 'utf8'));
|
|
17
|
+
}
|
|
18
|
+
// build an object using the defaults in the schema
|
|
19
|
+
function buildDefaults(schema, definitions) {
|
|
20
|
+
return Object.keys(schema.properties).reduce((acc, prop) => {
|
|
21
|
+
let spec = schema.properties[prop];
|
|
22
|
+
if (spec.$ref) {
|
|
23
|
+
spec = definitions[spec.$ref.replace('#/definitions/', '')];
|
|
24
|
+
if (spec && spec.type === 'object') {
|
|
25
|
+
return assoc(prop, buildDefaults(spec, definitions), acc);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return assoc(prop, spec.default, acc);
|
|
29
|
+
}, {});
|
|
30
|
+
}
|
|
31
|
+
// build an object of config values taken from process.env
|
|
32
|
+
function buildEnvironmentVariablesConfig(schema) {
|
|
33
|
+
const trueRx = /^true$/i;
|
|
34
|
+
const configKeys = Object.keys(schema.properties);
|
|
35
|
+
const env = pick(configKeys, process.env);
|
|
36
|
+
return Object.keys(env).reduce((acc, key) => {
|
|
37
|
+
const { type } = schema.properties[key];
|
|
38
|
+
switch (type) {
|
|
39
|
+
case 'integer':
|
|
40
|
+
return assoc(key, parseInt(env[key], 10), acc);
|
|
41
|
+
case 'boolean':
|
|
42
|
+
return assoc(key, trueRx.test(env[key]), acc);
|
|
43
|
+
default:
|
|
44
|
+
return assoc(key, env[key], acc);
|
|
45
|
+
}
|
|
46
|
+
}, {});
|
|
47
|
+
}
|
|
48
|
+
function validate(data) {
|
|
49
|
+
const valid = ajvValidate(data);
|
|
50
|
+
if (valid)
|
|
51
|
+
return true;
|
|
52
|
+
throw new Error(ajv.errorsText());
|
|
53
|
+
}
|
|
54
|
+
// merge the environment variables, config file values, and defaults
|
|
55
|
+
const config = mergeAll(mergeDeepRight(buildDefaults(schema, schema.definitions), buildConfigFromFile(process.env.CONFIG_FILE)), buildEnvironmentVariablesConfig(schema));
|
|
56
|
+
export default config;
|
|
57
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"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"}
|
package/docker_entrypoint.sh
CHANGED
package/e2e/spec/clients.js
CHANGED
|
@@ -4,7 +4,7 @@ import { test } from 'tape';
|
|
|
4
4
|
import { spawn } from 'node:child_process';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
|
|
7
|
-
test('Should list clients as JSON', (t) => {
|
|
7
|
+
test('Should list clients as JSON', { timeout: 3000 }, (t) => {
|
|
8
8
|
const cli = spawn(
|
|
9
9
|
path.join(path.dirname('.'), 'node'),
|
|
10
10
|
[
|
|
@@ -21,8 +21,12 @@ test('Should list clients as JSON', (t) => {
|
|
|
21
21
|
}
|
|
22
22
|
);
|
|
23
23
|
cli.stdout.on('data', (chunk) => {
|
|
24
|
+
console.log(chunk.toString())
|
|
24
25
|
t.equal(JSON.parse(chunk.toString()).length, 24);
|
|
25
26
|
});
|
|
27
|
+
cli.stderr.on('data', (msg) => {
|
|
28
|
+
t.fail(msg)
|
|
29
|
+
});
|
|
26
30
|
cli.stdout.on('end', () => {
|
|
27
31
|
t.end();
|
|
28
32
|
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import { test } from 'tape';
|
|
4
|
+
import { spawn } from 'node:child_process';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
|
|
7
|
+
test('Should use config file', { timeout: 3000 }, (t) => {
|
|
8
|
+
const cli = spawn(
|
|
9
|
+
path.join(path.dirname('.'), 'node'),
|
|
10
|
+
[
|
|
11
|
+
'dist/cli.js',
|
|
12
|
+
'listClients'
|
|
13
|
+
],
|
|
14
|
+
{
|
|
15
|
+
env: {
|
|
16
|
+
CONFIG_FILE: process.cwd() + '/e2e/fixtures/config.json',
|
|
17
|
+
...process.env,
|
|
18
|
+
},
|
|
19
|
+
}
|
|
20
|
+
);
|
|
21
|
+
cli.stdout.on('data', (chunk) => {
|
|
22
|
+
console.log(chunk.toString())
|
|
23
|
+
t.equal(JSON.parse(chunk.toString()).length, 24);
|
|
24
|
+
});
|
|
25
|
+
cli.stderr.on('data', (msg) => {
|
|
26
|
+
t.fail(msg)
|
|
27
|
+
});
|
|
28
|
+
cli.stdout.on('end', () => {
|
|
29
|
+
t.end();
|
|
30
|
+
});
|
|
31
|
+
});
|
package/e2e/spec/users.js
CHANGED
|
@@ -4,7 +4,7 @@ import { test } from 'tape';
|
|
|
4
4
|
import { spawn } from 'node:child_process';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
|
|
7
|
-
test('Should list users as JSON', (t) => {
|
|
7
|
+
test('Should list users as JSON', { timeout: 3000 }, (t) => {
|
|
8
8
|
const cli = spawn(
|
|
9
9
|
path.join(path.dirname('.'), 'node'),
|
|
10
10
|
[
|
|
@@ -21,8 +21,12 @@ test('Should list users as JSON', (t) => {
|
|
|
21
21
|
}
|
|
22
22
|
);
|
|
23
23
|
cli.stdout.on('data', (chunk) => {
|
|
24
|
+
console.log(chunk.toString())
|
|
24
25
|
t.equal(JSON.parse(chunk.toString()).length, 3);
|
|
25
26
|
});
|
|
27
|
+
cli.stderr.on('data', (msg) => {
|
|
28
|
+
t.fail(msg)
|
|
29
|
+
});
|
|
26
30
|
cli.stdout.on('end', () => {
|
|
27
31
|
t.end();
|
|
28
32
|
});
|
package/e2e/spec/webhooks.js
CHANGED
|
@@ -4,7 +4,7 @@ import { test } from 'tape';
|
|
|
4
4
|
import { spawn } from 'node:child_process';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
|
|
7
|
-
test('Should post message to Teams', (t) => {
|
|
7
|
+
test('Should post message to Teams', { timeout: 3000 }, (t) => {
|
|
8
8
|
const cli = spawn(
|
|
9
9
|
path.join(path.dirname('.'), 'node'),
|
|
10
10
|
[
|
|
@@ -26,12 +26,15 @@ test('Should post message to Teams', (t) => {
|
|
|
26
26
|
cli.stdout.on('data', (chunk) => {
|
|
27
27
|
console.log(chunk.toString())
|
|
28
28
|
});
|
|
29
|
+
cli.stderr.on('data', (msg) => {
|
|
30
|
+
t.fail(msg)
|
|
31
|
+
});
|
|
29
32
|
cli.stdout.on('end', () => {
|
|
30
33
|
t.end();
|
|
31
34
|
});
|
|
32
35
|
});
|
|
33
36
|
|
|
34
|
-
test('Should post message to Slack', (t) => {
|
|
37
|
+
test('Should post message to Slack', { timeout: 3000 }, (t) => {
|
|
35
38
|
const cli = spawn(
|
|
36
39
|
path.join(path.dirname('.'), 'node'),
|
|
37
40
|
[
|
|
@@ -53,6 +56,9 @@ test('Should post message to Slack', (t) => {
|
|
|
53
56
|
cli.stdout.on('data', (chunk) => {
|
|
54
57
|
console.log(chunk.toString())
|
|
55
58
|
});
|
|
59
|
+
cli.stderr.on('data', (msg) => {
|
|
60
|
+
t.fail(msg)
|
|
61
|
+
});
|
|
56
62
|
cli.stdout.on('end', () => {
|
|
57
63
|
t.end();
|
|
58
64
|
});
|
package/index.ts
CHANGED
|
@@ -1,125 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import { listUsers, listClients } from './src/cli.js';
|
|
6
|
-
import { Options } from './lib/client.js';
|
|
7
|
-
import { convertJSON2CSV } from './lib/convert.js';
|
|
8
|
-
import { post2Webhook } from './lib/output.js';
|
|
9
|
-
|
|
10
|
-
class WebhookConfig {
|
|
11
|
-
type: string;
|
|
12
|
-
url: string;
|
|
13
|
-
title: string;
|
|
14
|
-
constructor(type: string, url: string, title: string) {
|
|
15
|
-
this.type = type;
|
|
16
|
-
this.url = url;
|
|
17
|
-
this.title = title;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
async function convert(
|
|
22
|
-
format: string,
|
|
23
|
-
output: string,
|
|
24
|
-
config: WebhookConfig,
|
|
25
|
-
json: object
|
|
26
|
-
) {
|
|
27
|
-
let outputContent: string;
|
|
28
|
-
switch (format) {
|
|
29
|
-
case 'csv':
|
|
30
|
-
outputContent = (await convertJSON2CSV(json)).toString();
|
|
31
|
-
break;
|
|
32
|
-
// defaulting to JSON
|
|
33
|
-
default:
|
|
34
|
-
outputContent = JSON.stringify(json);
|
|
35
|
-
}
|
|
36
|
-
switch (output) {
|
|
37
|
-
case 'webhook':
|
|
38
|
-
try {
|
|
39
|
-
await post2Webhook(
|
|
40
|
-
config.type,
|
|
41
|
-
config.url,
|
|
42
|
-
config.title,
|
|
43
|
-
outputContent
|
|
44
|
-
);
|
|
45
|
-
} catch (e) {
|
|
46
|
-
console.error('Error during sending webhook: ', e);
|
|
47
|
-
}
|
|
48
|
-
break;
|
|
49
|
-
// defaulting to standard out
|
|
50
|
-
default:
|
|
51
|
-
console.log(outputContent);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
yargs(hideBin(process.argv))
|
|
56
|
-
.command(
|
|
57
|
-
'listUsers [url] [clientId] [clientSecret]',
|
|
58
|
-
'fetches all users in the realms.',
|
|
59
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
60
|
-
() => {},
|
|
61
|
-
async (argv) => {
|
|
62
|
-
const users = await listUsers(<Options>{
|
|
63
|
-
clientId: argv.clientId as string,
|
|
64
|
-
clientSecret: argv.clientSecret as string,
|
|
65
|
-
rootUrl: argv.url as string,
|
|
66
|
-
});
|
|
67
|
-
await convert(
|
|
68
|
-
argv.format as string,
|
|
69
|
-
argv.output as string,
|
|
70
|
-
new WebhookConfig(
|
|
71
|
-
argv.webhookType as string,
|
|
72
|
-
argv.webhookUrl as string,
|
|
73
|
-
'User Listing'
|
|
74
|
-
),
|
|
75
|
-
users
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
)
|
|
79
|
-
.command(
|
|
80
|
-
'listClients [url] [clientId] [clientSecret]',
|
|
81
|
-
'fetches all clients in the realms.',
|
|
82
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
83
|
-
() => {},
|
|
84
|
-
async (argv) => {
|
|
85
|
-
const clients = await listClients(<Options>{
|
|
86
|
-
clientId: argv.clientId as string,
|
|
87
|
-
clientSecret: argv.clientSecret as string,
|
|
88
|
-
rootUrl: argv.url as string,
|
|
89
|
-
});
|
|
90
|
-
await convert(
|
|
91
|
-
argv.format as string,
|
|
92
|
-
argv.output as string,
|
|
93
|
-
new WebhookConfig(
|
|
94
|
-
argv.webhookType as string,
|
|
95
|
-
argv.webhookUrl as string,
|
|
96
|
-
'Client Listing'
|
|
97
|
-
),
|
|
98
|
-
clients
|
|
99
|
-
);
|
|
100
|
-
}
|
|
101
|
-
)
|
|
102
|
-
.option('format', {
|
|
103
|
-
alias: 'f',
|
|
104
|
-
type: 'string',
|
|
105
|
-
default: 'json',
|
|
106
|
-
description: 'output format, e.g. JSON|CSV',
|
|
107
|
-
})
|
|
108
|
-
.option('output', {
|
|
109
|
-
alias: 'o',
|
|
110
|
-
type: 'string',
|
|
111
|
-
default: 'stdout',
|
|
112
|
-
description: 'output channel',
|
|
113
|
-
})
|
|
114
|
-
.option('webhookType', {
|
|
115
|
-
alias: 'w',
|
|
116
|
-
type: 'string',
|
|
117
|
-
default: 'slack',
|
|
118
|
-
description: 'Webhook Type',
|
|
119
|
-
})
|
|
120
|
-
.option('webhookUrl', {
|
|
121
|
-
alias: 't',
|
|
122
|
-
type: 'string',
|
|
123
|
-
description: 'Webhook URL',
|
|
124
|
-
})
|
|
125
|
-
.parse();
|
|
1
|
+
export { listUsers, listClients } from './src/cli.js';
|
|
2
|
+
export { Options } from './lib/client.js';
|
|
3
|
+
export { convertJSON2CSV } from './lib/convert.js';
|
|
4
|
+
export { post2Webhook } from './lib/output.js';
|
package/lib/client.ts
CHANGED
|
@@ -24,7 +24,10 @@ export async function createClient(options: Options): Promise<KcAdminClient> {
|
|
|
24
24
|
grantType: 'client_credentials',
|
|
25
25
|
});
|
|
26
26
|
} catch (e) {
|
|
27
|
-
console.error(
|
|
27
|
+
console.error(
|
|
28
|
+
'Check Client Config:',
|
|
29
|
+
e.response ? e.response.data.error_description : e
|
|
30
|
+
);
|
|
28
31
|
return Promise.reject();
|
|
29
32
|
}
|
|
30
33
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@continuoussecuritytooling/keycloak-reporter",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Reporting Tools for Keycloak",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"type": "module",
|
|
10
10
|
"scripts": {
|
|
11
11
|
"clean": "rm -rf dist/* && npm i",
|
|
12
|
-
"build": "tsc && chmod +x dist/cli.js && cp
|
|
12
|
+
"build": "tsc && chmod +x dist/cli.js && cp -rp config dist/",
|
|
13
13
|
"test": "eslint . && jest",
|
|
14
14
|
"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",
|
|
15
15
|
"end2end:test": "./e2e/run-tests.sh"
|
|
@@ -25,14 +25,15 @@
|
|
|
25
25
|
},
|
|
26
26
|
"homepage": "https://github.com/ContinuousSecurityTooling/keycloak-reporter#readme",
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@continuoussecuritytooling/keycloak-reporter": "^0.1.0",
|
|
29
28
|
"@json2csv/node": "^7.0.0",
|
|
30
29
|
"@keycloak/keycloak-admin-client": "^20.0.5",
|
|
31
30
|
"@slack/webhook": "^6.1.0",
|
|
31
|
+
"ajv": "^8.12.0",
|
|
32
32
|
"install": "^0.13.0",
|
|
33
33
|
"ms-teams-webhook": "^2.0.2",
|
|
34
34
|
"npm": "^9.6.7",
|
|
35
35
|
"openid-client": "^5.4.2",
|
|
36
|
+
"ramda": "^0.29.0",
|
|
36
37
|
"yargs": "^17.7.2"
|
|
37
38
|
},
|
|
38
39
|
"devDependencies": {
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { assoc, pick, mergeAll, mergeDeepRight } from 'ramda';
|
|
2
|
+
import Ajv from 'ajv';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
|
|
7
|
+
const schema = JSON.parse(
|
|
8
|
+
fs.readFileSync(fileURLToPath(path.join(import.meta.url, '../../config/schema.json')), 'utf8')
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
const ajv = new Ajv.default();
|
|
12
|
+
const ajvValidate = ajv.compile(schema);
|
|
13
|
+
|
|
14
|
+
// import the config file
|
|
15
|
+
function buildConfigFromFile(filePath) {
|
|
16
|
+
if (!filePath) return {};
|
|
17
|
+
const isAbsolutePath = filePath.charAt(0) === '/';
|
|
18
|
+
return JSON.parse(isAbsolutePath
|
|
19
|
+
? fs.readFileSync(filePath, 'utf8')
|
|
20
|
+
: fs.readFileSync(fileURLToPath(path.join(import.meta.url, '../config', filePath)), 'utf8'));
|
|
21
|
+
}
|
|
22
|
+
// build an object using the defaults in the schema
|
|
23
|
+
function buildDefaults(schema, definitions) {
|
|
24
|
+
return Object.keys(schema.properties).reduce((acc, prop) => {
|
|
25
|
+
let spec = schema.properties[prop];
|
|
26
|
+
if (spec.$ref) {
|
|
27
|
+
spec = definitions[spec.$ref.replace('#/definitions/', '')];
|
|
28
|
+
if (spec && spec.type === 'object') {
|
|
29
|
+
return assoc(prop, buildDefaults(spec, definitions), acc);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return assoc(prop, spec.default, acc);
|
|
33
|
+
}, {});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// build an object of config values taken from process.env
|
|
37
|
+
function buildEnvironmentVariablesConfig(schema) {
|
|
38
|
+
const trueRx = /^true$/i;
|
|
39
|
+
const configKeys = Object.keys(schema.properties);
|
|
40
|
+
const env = pick(configKeys, process.env);
|
|
41
|
+
return Object.keys(env).reduce((acc, key) => {
|
|
42
|
+
const { type } = schema.properties[key];
|
|
43
|
+
switch (type) {
|
|
44
|
+
case 'integer':
|
|
45
|
+
return assoc(key, parseInt(env[key], 10), acc);
|
|
46
|
+
case 'boolean':
|
|
47
|
+
return assoc(key, trueRx.test(env[key]), acc);
|
|
48
|
+
default:
|
|
49
|
+
return assoc(key, env[key], acc);
|
|
50
|
+
}
|
|
51
|
+
}, {});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function validate(data) {
|
|
55
|
+
const valid = ajvValidate(data);
|
|
56
|
+
if (valid) return true;
|
|
57
|
+
throw new Error(ajv.errorsText());
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// merge the environment variables, config file values, and defaults
|
|
61
|
+
const config = mergeAll(
|
|
62
|
+
mergeDeepRight(
|
|
63
|
+
buildDefaults(schema, schema.definitions),
|
|
64
|
+
buildConfigFromFile(process.env.CONFIG_FILE)
|
|
65
|
+
),
|
|
66
|
+
buildEnvironmentVariablesConfig(schema)
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
export default config;
|
package/dist/package.json
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@continuoussecuritytooling/keycloak-reporter",
|
|
3
|
-
"version": "0.1.1",
|
|
4
|
-
"description": "Reporting Tools for Keycloak",
|
|
5
|
-
"main": "dist/index.js",
|
|
6
|
-
"bin": {
|
|
7
|
-
"kc-reporter": "dist/cli.js"
|
|
8
|
-
},
|
|
9
|
-
"type": "module",
|
|
10
|
-
"scripts": {
|
|
11
|
-
"clean": "rm -rf dist/* && npm i",
|
|
12
|
-
"build": "tsc && chmod +x dist/cli.js && cp package.json dist/",
|
|
13
|
-
"test": "eslint . && jest",
|
|
14
|
-
"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",
|
|
15
|
-
"end2end:test": "./e2e/run-tests.sh"
|
|
16
|
-
},
|
|
17
|
-
"repository": {
|
|
18
|
-
"type": "git",
|
|
19
|
-
"url": "git+https://github.com/ContinuousSecurityTooling/keycloak-reporter.git"
|
|
20
|
-
},
|
|
21
|
-
"author": "Martin Reinhardt",
|
|
22
|
-
"license": "MIT",
|
|
23
|
-
"bugs": {
|
|
24
|
-
"url": "https://github.com/ContinuousSecurityTooling/keycloak-reporter/issues"
|
|
25
|
-
},
|
|
26
|
-
"homepage": "https://github.com/ContinuousSecurityTooling/keycloak-reporter#readme",
|
|
27
|
-
"dependencies": {
|
|
28
|
-
"@continuoussecuritytooling/keycloak-reporter": "^0.1.0",
|
|
29
|
-
"@json2csv/node": "^7.0.0",
|
|
30
|
-
"@keycloak/keycloak-admin-client": "^20.0.5",
|
|
31
|
-
"@slack/webhook": "^6.1.0",
|
|
32
|
-
"install": "^0.13.0",
|
|
33
|
-
"ms-teams-webhook": "^2.0.2",
|
|
34
|
-
"npm": "^9.6.7",
|
|
35
|
-
"openid-client": "^5.4.2",
|
|
36
|
-
"yargs": "^17.7.2"
|
|
37
|
-
},
|
|
38
|
-
"devDependencies": {
|
|
39
|
-
"@octokit/rest": "^19.0.11",
|
|
40
|
-
"@types/jest": "^29.5.1",
|
|
41
|
-
"@types/node": "^20.1.5",
|
|
42
|
-
"@types/yargs": "^17.0.24",
|
|
43
|
-
"@typescript-eslint/eslint-plugin": "^5.59.6",
|
|
44
|
-
"@typescript-eslint/parser": "^5.59.6",
|
|
45
|
-
"eslint": "^8.40.0",
|
|
46
|
-
"gunzip-maybe": "^1.4.2",
|
|
47
|
-
"jest": "^29.5.0",
|
|
48
|
-
"jest-extended": "^3.2.4",
|
|
49
|
-
"node-fetch": "^3.3.1",
|
|
50
|
-
"tap-xunit": "^2.4.1",
|
|
51
|
-
"tape": "^5.6.3",
|
|
52
|
-
"tar-fs": "^2.1.1",
|
|
53
|
-
"ts-jest": "^29.1.0",
|
|
54
|
-
"typescript": "^5.0.4"
|
|
55
|
-
}
|
|
56
|
-
}
|