Skip to content

Commit 9eedbab

Browse files
ferruhcihansrodenhuisj-zimnowoda
authored
feat: user management (#1740)
Co-authored-by: Sander Rodenhuis <[email protected]> Co-authored-by: Jehoszafat Zimnowoda <[email protected]>
1 parent 73ed921 commit 9eedbab

File tree

15 files changed

+143
-12
lines changed

15 files changed

+143
-12
lines changed

chart/apl/values.yaml

+3-1
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@ otomi: {}
111111
# clientSecret: ''
112112
# issuer: ''
113113
# # IDP group id used to identify global admin
114-
# adminGroupID: ''
114+
# platformAdminGroupID: ''
115+
# # IDP group id used to identify all teams admin
116+
# allTeamsAdminGroupID: ''
115117
# # IDP group id used to identify team admin
116118
# teamAdminGroupID: ''

helmfile.d/snippets/defaults.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -1265,6 +1265,7 @@ environments:
12651265
hasExternalIDP: false
12661266
isMultitenant: true
12671267
nodeSelector: {}
1268+
users: []
12681269
e2e:
12691270
enabled: false
12701271
upgrade:

helmfile.d/snippets/env.gotmpl

+3
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ environments:
5050
{{- end }}
5151
{{- end }}
5252
{{- end }}
53+
{{- if eq (exec "bash" (list "-c" "( test -f $ENV_DIR/env/secrets.users.yaml && echo 'true' ) || echo 'false'") | trim) "true" }}
54+
- {{ $ENV_DIR }}/env/secrets.users.yaml{{ $ext }}
55+
{{- end }}
5356
{{- if eq (exec "bash" (list "-c" "( test -f $ENV_DIR/env/secrets.settings.yaml && echo 'true' ) || echo 'false'") | trim) "true" }}
5457
- {{ $ENV_DIR }}/env/secrets.settings.yaml{{ $ext }}
5558
{{- end }}

package-lock.json

+11
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"envalid": "7.3.1",
2424
"express": "4.18.1",
2525
"fs-extra": "9.1.0",
26+
"generate-password": "^1.7.1",
2627
"ignore-walk": "3.0.4",
2728
"lodash": "4.17.21",
2829
"node-fetch": "2.6.7",

src/cmd/bootstrap.test.ts

+14-2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ describe('Bootstrapping values', () => {
2222
apps: { 'cert-manager': { issuer: 'custom-ca' } },
2323
cluster: { name: 'bla', provider: 'dida' },
2424
}
25+
const users = [{ id: 'user1', initialPassword: 'existing-password' }, { id: 'user2' }]
2526
const secrets = { secret: 'true', deep: { nested: 'secret' } }
2627
const ageKeys = { publicKey: 'agePublicKey', privateKey: 'agePrivateKey' }
2728
const kmsValues = {
@@ -241,6 +242,11 @@ describe('Bootstrapping values', () => {
241242
})
242243
describe('processing values', () => {
243244
const generatedSecrets = { gen: 'x' }
245+
const generatedPassword = 'generated-password'
246+
const usersWithPasswords = [
247+
{ id: 'user1', initialPassword: 'existing-password' },
248+
{ id: 'user2', initialPassword: generatedPassword },
249+
]
244250
const ca = { a: 'cert' }
245251
const mergedValues = merge(cloneDeep(values), cloneDeep(secrets))
246252
const mergedSecretsWithCa = merge(cloneDeep(secrets), cloneDeep(ca))
@@ -262,6 +268,7 @@ describe('Bootstrapping values', () => {
262268
terminal,
263269
validateValues: jest.fn().mockReturnValue(true),
264270
writeValues: jest.fn(),
271+
generatePassword: jest.fn().mockReturnValue(generatedPassword),
265272
}
266273
})
267274
describe('Creating CA', () => {
@@ -335,10 +342,15 @@ describe('Bootstrapping values', () => {
335342
expect(res).toEqual(mergedValues)
336343
})
337344
it('should merge original with generated values and write them to env dir', async () => {
338-
const writtenValues = merge(cloneDeep(values), cloneDeep(mergedSecretsWithGenAndCa))
339-
deps.loadYaml.mockReturnValue(values)
345+
const writtenValues = merge(
346+
cloneDeep(values),
347+
cloneDeep(mergedSecretsWithGenAndCa),
348+
cloneDeep({ users: usersWithPasswords }),
349+
)
350+
deps.loadYaml.mockReturnValue({ ...values, users })
340351
deps.getStoredClusterSecrets.mockReturnValue(secrets)
341352
deps.generateSecrets.mockReturnValue(generatedSecrets)
353+
deps.generatePassword.mockReturnValue(generatedPassword)
342354
await processValues(deps)
343355
expect(deps.writeValues).toHaveBeenNthCalledWith(2, writtenValues)
344356
})

src/cmd/bootstrap.ts

+16-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { copy, pathExists } from 'fs-extra'
22
import { copyFile, mkdir, readFile, writeFile } from 'fs/promises'
3+
import { generate as generatePassword } from 'generate-password'
34
import { cloneDeep, get, merge } from 'lodash'
45
import { pki } from 'node-forge'
56
import path from 'path'
@@ -252,6 +253,7 @@ export const processValues = async (
252253
generateSecrets,
253254
createK8sSecret,
254255
createCustomCA,
256+
generatePassword,
255257
},
256258
): Promise<Record<string, any> | undefined> => {
257259
const d = deps.terminal(`cmd:${cmdName}:processValues`)
@@ -287,8 +289,21 @@ export const processValues = async (
287289
}
288290
// merge existing secrets over newly generated ones to keep them
289291
const allSecrets = merge(cloneDeep(caSecrets), cloneDeep(storedSecrets), cloneDeep(generatedSecrets))
292+
// generate initial passwords for users if they don't have one
293+
const users = get(originalInput, 'users', [])
294+
for (const user of users) {
295+
if (!user.initialPassword) {
296+
user.initialPassword = deps.generatePassword({
297+
length: 16,
298+
numbers: true,
299+
symbols: true,
300+
lowercase: true,
301+
uppercase: true,
302+
})
303+
}
304+
}
290305
// we have generated all we need, now store everything by merging the original values over all the secrets
291-
await deps.writeValues(merge(cloneDeep(allSecrets), cloneDeep(originalInput)))
306+
await deps.writeValues(merge(cloneDeep(allSecrets), cloneDeep(originalInput), cloneDeep({ users })))
292307
// and do some context dependent post processing:
293308
if (deps.isChart) {
294309
// to support potential failing chart install we store secrets on cluster

src/common/values.ts

+3
Original file line numberDiff line numberDiff line change
@@ -191,14 +191,17 @@ export const writeValues = async (inValues: Record<string, any>, overwrite = fal
191191
'databases',
192192
'files',
193193
'bootstrap',
194+
'users',
194195
]
195196
const secretSettings = omit(secrets, fieldsToOmit)
196197
const license = { license: values?.license }
197198
const settings = omit(plainValues, fieldsToOmit)
199+
const users = { users: values?.users }
198200
// and write to their files
199201
const promises: Promise<void>[] = []
200202
if (settings) promises.push(writeValuesToFile(`${env.ENV_DIR}/env/settings.yaml`, settings, overwrite))
201203
if (license) promises.push(writeValuesToFile(`${env.ENV_DIR}/env/secrets.license.yaml`, license, overwrite))
204+
if (users) promises.push(writeValuesToFile(`${env.ENV_DIR}/env/secrets.users.yaml`, users, overwrite))
202205
if (secretSettings || overwrite)
203206
promises.push(writeValuesToFile(`${env.ENV_DIR}/env/secrets.settings.yaml`, secretSettings, overwrite))
204207
if (plainValues.cluster || overwrite)

tests/bootstrap/input.yaml

+7
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,10 @@ otomi:
77
apps:
88
metrics-server:
99
enabled: false
10+
users:
11+
12+
firstName: platform
13+
lastName: admin
14+
isPlatformAdmin: true
15+
isTeamAdmin: false
16+
teams: []

tests/fixtures/env/secrets.users.yaml

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
users:
2+
3+
firstName: platform
4+
lastName: admin
5+
isPlatformAdmin: true
6+
isTeamAdmin: true
7+
teams: ['demo']
8+
initialPassword: 'platform-admin-password'
9+
10+
firstName: team
11+
lastName: admin
12+
isPlatformAdmin: false
13+
isTeamAdmin: true
14+
teams: ['demo']
15+
initialPassword: 'team-admin-password'
16+
17+
firstName: team
18+
lastName: member
19+
isPlatformAdmin: false
20+
isTeamAdmin: false
21+
teams: ['demo']
22+
initialPassword: 'team-member-password'

tests/fixtures/env/settings.yaml

+2-1
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,11 @@ obj:
5656
thanos: my-clusterid-thanos
5757
type: linode
5858
oidc:
59-
adminGroupID: someAdminGroupID
59+
platformAdminGroupID: someAdminGroupID
6060
clientID: someClientID
6161
issuer: https://login.microsoftonline.com/xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
6262
subClaimMapper: oid
63+
allTeamsAdminGroupID: xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
6364
teamAdminGroupID: xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
6465
otomi:
6566
additionalClusters:

values-changes.yaml

+4
Original file line numberDiff line numberDiff line change
@@ -302,3 +302,7 @@ changes:
302302
deletions:
303303
- 'teamConfig.{team}.managedMonitoring.prometheus'
304304
- 'apps.grafana.resources.downloadDashboards'
305+
- version: 30
306+
relocations:
307+
- 'oidc.adminGroupID': 'oidc.platformAdminGroupID'
308+
- 'oidc.teamAdminGroupID': 'oidc.allTeamsAdminGroupID'

values-schema.yaml

+38-3
Original file line numberDiff line numberDiff line change
@@ -1307,7 +1307,6 @@ definitions:
13071307
- value
13081308
required:
13091309
- name
1310-
13111310
sealedsecret:
13121311
type: object
13131312
description: Define location of code to build
@@ -1341,7 +1340,6 @@ definitions:
13411340
- type
13421341
- encryptedData
13431342
- name
1344-
13451343
workload:
13461344
type: object
13471345
description: Define location of the application's manifests or chart
@@ -1435,6 +1433,37 @@ definitions:
14351433
required:
14361434
- name
14371435
- url
1436+
user:
1437+
type: object
1438+
description: A user in keycloak, who can be a platform admin, a team admin, or a team member.
1439+
properties:
1440+
email:
1441+
$ref: '#/definitions/email'
1442+
x-secret: ''
1443+
firstName:
1444+
type: string
1445+
x-secret: ''
1446+
lastName:
1447+
type: string
1448+
x-secret: ''
1449+
isPlatformAdmin:
1450+
type: boolean
1451+
x-secret: ''
1452+
isTeamAdmin:
1453+
type: boolean
1454+
x-secret: ''
1455+
teams:
1456+
type: array
1457+
items:
1458+
type: string
1459+
x-secret: ''
1460+
initialPassword:
1461+
type: string
1462+
x-secret: ''
1463+
required:
1464+
- email
1465+
- firstName
1466+
- lastName
14381467

14391468
properties:
14401469
alerts:
@@ -2896,7 +2925,9 @@ properties:
28962925
clientSecret:
28972926
type: string
28982927
x-secret: ''
2899-
adminGroupID:
2928+
platformAdminGroupID:
2929+
$ref: '#/definitions/wordCharacterPattern'
2930+
allTeamsAdminGroupID:
29002931
$ref: '#/definitions/wordCharacterPattern'
29012932
teamAdminGroupID:
29022933
$ref: '#/definitions/wordCharacterPattern'
@@ -3221,5 +3252,9 @@ properties:
32213252
type: string
32223253
helm:
32233254
type: object
3255+
users:
3256+
type: array
3257+
items:
3258+
$ref: '#/definitions/user'
32243259
required:
32253260
- cluster

values/apl-keycloak-operator/apl-keycloak-operator-raw.gotmpl

+14-2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,15 @@
1818
{{- $k := $c | get "keycloak" }}
1919
{{- $doms := tpl (readFile "../../helmfile.d/snippets/domains.gotmpl") $v | fromYaml }}
2020
{{- $joinTpl := readFile "../../helmfile.d/utils/joinListWithSep.gotmpl" }}
21+
{{ $users := list }}
22+
{{- range $user := $v.users }}
23+
{{ $groups := list }}
24+
{{- if $user.isPlatformAdmin }}{{ $groups = append $groups "platform-admin" }}{{ end }}
25+
{{- if $user.isTeamAdmin }}{{ $groups = append $groups "team-admin" }}{{ end }}
26+
{{- range $team := $user.teams }}{{ $groups = append $groups (print "team-" $team) }}{{ end }}
27+
{{- $users = append $users (dict "email" $user.email "firstName" $user.firstName "lastName" $user.lastName "initialPassword" $user.initialPassword "groups" $groups) }}
28+
{{- end }}
29+
{{- $users := $users | toJson }}
2130

2231
resources:
2332
- apiVersion: v1
@@ -29,6 +38,7 @@ resources:
2938
KEYCLOAK_ADMIN: {{ .Values.apps.keycloak.adminUsername | b64enc }}
3039
KEYCLOAK_ADMIN_PASSWORD: {{ $k.adminPassword | b64enc }}
3140
KEYCLOAK_CLIENT_SECRET: {{ $k.idp.clientSecret | b64enc }}
41+
USERS: {{ $users | b64enc }}
3242
{{- if $v.otomi.hasExternalIDP }}
3343
IDP_CLIENT_ID: {{ $oi.clientID | b64enc}}
3444
IDP_CLIENT_SECRET: {{ $oi.clientSecret | b64enc }}
@@ -56,8 +66,10 @@ resources:
5666
{{- with $oi | get "subClaimMapper" nil }}
5767
IDP_SUB_CLAIM_MAPPER: {{ . }}{{ end }}
5868
IDP_GROUP_MAPPINGS_TEAMS: '{{ $teamsMapping | toJson }}'
59-
{{- with $oi | get "adminGroupID" nil }}
60-
IDP_GROUP_apl_ADMIN: {{ . }}{{ end }}
69+
{{- with $oi | get "platformAdminGroupID" nil }}
70+
IDP_GROUP_PLATFORM_ADMIN: {{ . }}{{ end }}
71+
{{- with $oi | get "allTeamsAdminGroupID" nil }}
72+
IDP_GROUP_ALL_TEAMS_ADMIN: {{ . }}{{ end }}
6173
{{- with $oi | get "teamAdminGroupID" nil }}
6274
IDP_GROUP_TEAM_ADMIN: {{ . }}{{ end }}
6375
IDP_OIDC_URL: {{ $oi.issuer }}

values/jobs/keycloak.gotmpl

+4-2
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,10 @@ env:
4242
{{- with $o | get "subClaimMapper" nil }}
4343
IDP_SUB_CLAIM_MAPPER: {{ . }}{{ end }}
4444
IDP_GROUP_MAPPINGS_TEAMS: '{{ $teamsMapping | toJson }}'
45-
{{- with $o | get "adminGroupID" nil }}
46-
IDP_GROUP_OTOMI_ADMIN: {{ . }}{{ end }}
45+
{{- with $o | get "platformAdminGroupID" nil }}
46+
IDP_GROUP_PLATFORM_ADMIN: {{ . }}{{ end }}
47+
{{- with $o | get "allTeamsAdminGroupID" nil }}
48+
IDP_GROUP_ALL_TEAMS_ADMIN: {{ . }}{{ end }}
4749
{{- with $o | get "teamAdminGroupID" nil }}
4850
IDP_GROUP_TEAM_ADMIN: {{ . }}{{ end }}
4951
IDP_OIDC_URL: {{ $o.issuer }}

0 commit comments

Comments
 (0)