Skip to content

Commit 7f1f6d3

Browse files
ferruhcihanj-zimnowodasrodenhuis
authored
feat: default platform admin user (#1770)
Co-authored-by: jeho <[email protected]> Co-authored-by: Sander Rodenhuis <[email protected]>
1 parent 26f2c9a commit 7f1f6d3

File tree

7 files changed

+83
-40
lines changed

7 files changed

+83
-40
lines changed

helmfile.d/snippets/env.gotmpl

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ 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" }}
53+
{{- if eq (exec "bash" (list "-c" (printf "( test -f $ENV_DIR/env/secrets.users.yaml%s && echo 'true' ) || echo 'false'" (default "" $ext))) | trim) "true" }}
5454
- {{ $ENV_DIR }}/env/secrets.users.yaml{{ $ext }}
5555
{{- end }}
5656
{{- if eq (exec "bash" (list "-c" "( test -f $ENV_DIR/env/secrets.settings.yaml && echo 'true' ) || echo 'false'") | trim) "true" }}

src/cmd/bootstrap.test.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,10 @@ describe('Bootstrapping values', () => {
279279
terminal,
280280
validateValues: jest.fn().mockReturnValue(true),
281281
writeValues: jest.fn(),
282+
getUsers: jest.fn().mockReturnValue(usersWithPasswords),
282283
generatePassword: jest.fn().mockReturnValue(generatedPassword),
284+
addInitialPasswords: jest.fn().mockReturnValue(usersWithPasswords),
285+
addPlatformAdmin: jest.fn().mockReturnValue(usersWithPasswords),
283286
}
284287
})
285288
describe('Creating CA', () => {
@@ -361,7 +364,7 @@ describe('Bootstrapping values', () => {
361364
deps.loadYaml.mockReturnValue({ ...values, users })
362365
deps.getStoredClusterSecrets.mockReturnValue(secrets)
363366
deps.generateSecrets.mockReturnValue(generatedSecrets)
364-
deps.generatePassword.mockReturnValue(generatedPassword)
367+
deps.getUsers.mockReturnValue(usersWithPasswords)
365368
await processValues(deps)
366369
expect(deps.writeValues).toHaveBeenNthCalledWith(2, writtenValues)
367370
})

src/cmd/bootstrap.ts

+46-13
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,47 @@ export const getKmsValues = async (deps = { generateAgeKeys, hfValues }) => {
196196
return { kms: { sops: { provider: 'age', age: ageKeys } } }
197197
}
198198

199+
export const addPlatformAdmin = (users: any[], domainSuffix: string) => {
200+
const defaultPlatformAdminEmail = `platform-admin@${domainSuffix}`
201+
const platformAdminExists = users.find((user) => user.email === defaultPlatformAdminEmail)
202+
if (platformAdminExists) return
203+
const platformAdmin = {
204+
email: defaultPlatformAdminEmail,
205+
firstName: 'platform',
206+
lastName: 'admin',
207+
isPlatformAdmin: true,
208+
isTeamAdmin: false,
209+
teams: [],
210+
}
211+
users.push(platformAdmin)
212+
}
213+
214+
export const addInitialPasswords = (users: any[], deps = { generatePassword }) => {
215+
for (const user of users) {
216+
if (!user.initialPassword) {
217+
user.initialPassword = deps.generatePassword({
218+
length: 20,
219+
numbers: true,
220+
symbols: true,
221+
lowercase: true,
222+
uppercase: true,
223+
exclude: String(':,;"/=|%\\\''),
224+
})
225+
}
226+
}
227+
}
228+
229+
export const getUsers = (originalInput: any, deps = { generatePassword, addInitialPasswords, addPlatformAdmin }) => {
230+
const users = get(originalInput, 'users', []) as any[]
231+
const { hasExternalIDP } = get(originalInput, 'otomi', {})
232+
if (!hasExternalIDP) {
233+
const { domainSuffix }: { domainSuffix: string } = get(originalInput, 'cluster', {})
234+
deps.addPlatformAdmin(users, domainSuffix)
235+
}
236+
deps.addInitialPasswords(users)
237+
return users
238+
}
239+
199240
export const copyBasicFiles = async (
200241
deps = { copy, copyFile, copySchema, mkdir, pathExists, terminal },
201242
): Promise<void> => {
@@ -254,7 +295,10 @@ export const processValues = async (
254295
generateSecrets,
255296
createK8sSecret,
256297
createCustomCA,
298+
getUsers,
257299
generatePassword,
300+
addInitialPasswords,
301+
addPlatformAdmin,
258302
},
259303
): Promise<Record<string, any> | undefined> => {
260304
const d = deps.terminal(`cmd:${cmdName}:processValues`)
@@ -295,19 +339,8 @@ export const processValues = async (
295339
cloneDeep(generatedSecrets),
296340
cloneDeep(kmsValues),
297341
)
298-
// generate initial passwords for users if they don't have one
299-
const users = get(originalInput, 'users', [])
300-
for (const user of users) {
301-
if (!user.initialPassword) {
302-
user.initialPassword = deps.generatePassword({
303-
length: 16,
304-
numbers: true,
305-
symbols: true,
306-
lowercase: true,
307-
uppercase: true,
308-
})
309-
}
310-
}
342+
// add default platform admin & generate initial passwords for users if they don't have one
343+
const users = deps.getUsers(originalInput)
311344
// we have generated all we need, now store everything by merging the original values over all the secrets
312345
await deps.writeValues(merge(cloneDeep(allSecrets), cloneDeep(originalInput), cloneDeep({ users })))
313346
// and do some context dependent post processing:

src/cmd/commit.ts

+21-16
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { CoreV1Api, CustomObjectsApi, KubeConfig } from '@kubernetes/client-node'
2+
import retry from 'async-retry'
13
import { bootstrapGit, setIdentity } from 'src/common/bootstrap'
24
import { prepareEnvironment } from 'src/common/cli'
35
import { encrypt } from 'src/common/crypt'
@@ -12,8 +14,6 @@ import { Argv } from 'yargs'
1214
import { $, cd } from 'zx'
1315
import { Arguments as DroneArgs } from './gen-drone'
1416
import { validateValues } from './validate-values'
15-
import { CoreV1Api, CustomObjectsApi, KubeConfig } from '@kubernetes/client-node'
16-
import retry from 'async-retry'
1717

1818
const cmdName = getFilename(__filename)
1919

@@ -184,31 +184,36 @@ export async function checkIfPipelineRunExists(): Promise<void> {
184184
d.info(`There is a Tekton PipelineRuns continuing...`)
185185
}
186186

187-
async function createRootCredentialsSecret(credentials: { adminUsername: string; adminPassword: string }) {
188-
const secretData = {
189-
username: credentials.adminUsername,
190-
password: credentials.adminPassword,
191-
}
187+
async function createCredentialsSecret(secretName: string, username: string, password: string): Promise<void> {
188+
const secretData = { username, password }
192189
const kc = new KubeConfig()
193190
kc.loadFromDefault()
194191
const coreV1Api = kc.makeApiClient(CoreV1Api)
195-
await createGenericSecret(coreV1Api, 'root-credentials', 'default', secretData)
192+
await createGenericSecret(coreV1Api, secretName, 'keycloak', secretData)
196193
}
197194

198195
export const printWelcomeMessage = async (): Promise<void> => {
199196
const d = terminal(`cmd:${cmdName}:commit`)
200197
const values = (await hfValues()) as Record<string, any>
201-
const credentials = values.apps.keycloak
202-
await createRootCredentialsSecret({
203-
adminUsername: credentials.adminUsername,
204-
adminPassword: credentials.adminPassword,
205-
})
198+
const { adminUsername, adminPassword }: { adminUsername: string; adminPassword: string } = values.apps.keycloak
199+
await createCredentialsSecret('root-credentials', adminUsername, adminPassword)
200+
const { hasExternalIDP } = values.otomi
201+
const { domainSuffix } = values.cluster
202+
const defaultPlatformAdminEmail = `platform-admin@${domainSuffix}`
203+
const platformAdmin = values.users.find((user: any) => user.email === defaultPlatformAdminEmail)
204+
if (platformAdmin && !hasExternalIDP) {
205+
const { email, initialPassword }: { email: string; initialPassword: string } = platformAdmin
206+
await createCredentialsSecret('platform-admin-initial-credentials', email, initialPassword)
207+
}
208+
const secretName = hasExternalIDP ? 'root-credentials' : 'platform-admin-initial-credentials'
206209
const message = `
207210
########################################################################################################################################
208211
#
209-
# Visit the console at: https://console.${values.cluster.domainSuffix}
210-
# Perform: kubectl get secret root-credentials -n default -o yaml
211-
# To obtain access credentials in base64 encoded format
212+
# The Application Platform console is available at https://console.${domainSuffix}
213+
#
214+
# Obtain login credentials by using the below commands:
215+
# kubectl get secret ${secretName} -n keycloak -o jsonpath='{.data.username}' | base64 -d
216+
# kubectl get secret ${secretName} -n keycloak -o jsonpath='{.data.password}' | base64 -d
212217
#
213218
########################################################################################################################################`
214219
d.info(message)

tests/bootstrap/input.yaml

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
# Minimal values file with defaults
22
cluster:
33
k8sContext: CONTEXT_PLACEHOLDER
4-
domainSuffix: my.localhost
4+
domainSuffix: local.host
55
otomi:
66
version: 'main'
77
apps:
88
metrics-server:
99
enabled: false
1010
users:
11-
- email: platform@admin.local
12-
firstName: platform
13-
lastName: admin
14-
isPlatformAdmin: true
11+
- email: team-member@local.host
12+
firstName: team-member
13+
lastName: localhost
14+
isPlatformAdmin: false
1515
isTeamAdmin: false
16-
teams: []
16+
teams: ['demo']

values/otomi-api/otomi-api.gotmpl

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
{{- $sops := $v | get "kms.sops" dict }}
88
{{- $giteaValuesUrl := printf "gitea.%s/otomi/values" $v.cluster.domainSuffix }}
99
{{- $helmChartCatalog := printf "https://gitea.%s/otomi/charts.git" $v.cluster.domainSuffix }}
10+
{{- $defaultPlatformAdminEmail := printf "platform-admin@%s" $v.cluster.domainSuffix }}
1011
{{- $sopsEnv := tpl (readFile "../../helmfile.d/snippets/sops-env.gotmpl") $sops }}
1112

1213
replicaCount: 1
@@ -24,6 +25,7 @@ secrets:
2425

2526
env:
2627
HELM_CHART_CATALOG: {{ $helmChartCatalog }}
28+
DEFAULT_PLATFORM_ADMIN_EMAIL: {{ $defaultPlatformAdminEmail }}
2729
DEBUG: 'otomi:*,-otomi:authz,-otomi:repo'
2830
VERBOSITY: '1'
2931
GIT_REPO_URL: {{ $o | get "git.repoUrl" $giteaValuesUrl }}

versions.yaml

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
api: 3.0.0
2-
console: 3.0.0
1+
api: main
2+
console: main
33
consoleLogin: v3.0.0
4-
tasks: 3.3.0
4+
tasks: main
55
tools: 2.7.0

0 commit comments

Comments
 (0)