Skip to content

Commit d448667

Browse files
committed
feat: adds support for randomly generated admin password on first run.
For a long time, we have used G@ppm0ym as a standard static default password. This PR removes the static password. The goal has been for users to always integrate their own authentication with role based auth. That said, we understand this doesn't always happen. As such, randomly generating a password on first run when no static password is provided will adhere to better security practices. Existing users should have the `adminPassword` and therefore be unaffected by this change.
1 parent e568fca commit d448667

File tree

5 files changed

+451
-16
lines changed

5 files changed

+451
-16
lines changed

cmd/app/main.go

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package main
22

33
import (
44
"context"
5+
"crypto/rand"
6+
"encoding/base64"
57
"errors"
68
"fmt"
79
"log"
@@ -26,6 +28,9 @@ var (
2628
ErrSecretStoreTokenNotConfigured = errors.New("secret store token not configured")
2729
)
2830

31+
// adminPasswordLength is the length of generated admin passwords.
32+
const adminPasswordLength = 16
33+
2934
// Function pointers for better testability.
3035
var (
3136
initializeConfigFunc = config.NewConfig
@@ -64,6 +69,7 @@ func main() {
6469
}
6570

6671
handleEncryptionKey(cfg)
72+
handleAdminPassword(cfg)
6773
handleDebugMode(cfg)
6874
runAppFunc(cfg)
6975
}
@@ -300,6 +306,137 @@ func handleKeyNotFound(toolkitCrypto security.Crypto, _, _ security.Storager) st
300306
return toolkitCrypto.GenerateKey()
301307
}
302308

309+
// generateRandomPassword creates a cryptographically secure random password.
310+
func generateRandomPassword(length int) (string, error) {
311+
bytes := make([]byte, length)
312+
313+
_, err := rand.Read(bytes)
314+
if err != nil {
315+
return "", err
316+
}
317+
318+
return base64.URLEncoding.EncodeToString(bytes)[:length], nil
319+
}
320+
321+
// handleAdminPassword manages the admin password - loading from keyring or generating a new one.
322+
func handleAdminPassword(cfg *config.Config) {
323+
// If admin password is already provided via config/env, just use it
324+
if cfg.AdminPassword != "" {
325+
log.Println("Admin password loaded from configuration")
326+
327+
return
328+
}
329+
330+
// Try to initialize secret store client for password retrieval
331+
remoteStorage, err := handleSecretsConfig(cfg)
332+
if err != nil {
333+
remoteStorage = nil
334+
}
335+
336+
// Try remote storage first
337+
if done := tryRemoteAdminPassword(cfg, remoteStorage); done {
338+
return
339+
}
340+
341+
// Try local keyring storage
342+
localStorage := security.NewKeyRingStorage("device-management-toolkit")
343+
344+
if done := tryLocalAdminPassword(cfg, localStorage, remoteStorage); done {
345+
return
346+
}
347+
348+
// Password not found anywhere, generate a new one
349+
password, err := generateRandomPassword(adminPasswordLength)
350+
if err != nil {
351+
log.Fatalf("Failed to generate admin password: %v", err)
352+
}
353+
354+
cfg.AdminPassword = password
355+
356+
if err := saveAdminPassword(password, remoteStorage, localStorage); err != nil {
357+
log.Printf("Warning: Failed to save admin password: %v", err)
358+
}
359+
360+
// Output the generated password so the user knows what to use
361+
log.Printf("\033[33m========================================\033[0m")
362+
log.Printf("\033[33mGenerated Admin Password: %s\033[0m", password)
363+
log.Printf("\033[33mThis password has been saved to your system keyring.\033[0m")
364+
log.Printf("\033[33m========================================\033[0m")
365+
}
366+
367+
// tryRemoteAdminPassword attempts to retrieve the admin password from remote storage.
368+
func tryRemoteAdminPassword(cfg *config.Config, remoteStorage security.Storager) bool {
369+
if remoteStorage == nil {
370+
return false
371+
}
372+
373+
password, err := remoteStorage.GetKeyValue("admin-password")
374+
if err == nil && password != "" {
375+
cfg.AdminPassword = password
376+
377+
log.Println("Admin password loaded from secret store")
378+
379+
return true
380+
}
381+
382+
return false
383+
}
384+
385+
// tryLocalAdminPassword attempts to retrieve the admin password from local keyring.
386+
func tryLocalAdminPassword(cfg *config.Config, localStorage, remoteStorage security.Storager) bool {
387+
password, err := localStorage.GetKeyValue("admin-password")
388+
if err == nil && password != "" {
389+
cfg.AdminPassword = password
390+
391+
log.Println("Admin password loaded from local keyring")
392+
syncAdminPasswordToRemote(password, remoteStorage)
393+
394+
return true
395+
}
396+
397+
// Check for unexpected errors
398+
if err != nil && !errors.Is(err, security.ErrKeyNotFound) {
399+
log.Printf("Warning: Failed to read admin password from keyring: %v", err)
400+
}
401+
402+
return false
403+
}
404+
405+
// syncAdminPasswordToRemote syncs the admin password to remote storage if available.
406+
func syncAdminPasswordToRemote(password string, remoteStorage security.Storager) {
407+
if remoteStorage == nil {
408+
return
409+
}
410+
411+
if err := remoteStorage.SetKeyValue("admin-password", password); err != nil {
412+
log.Printf("Warning: Failed to sync admin password to secret store: %v", err)
413+
} else {
414+
log.Println("Admin password synced to secret store")
415+
}
416+
}
417+
418+
func saveAdminPassword(password string, remoteStorage, localStorage security.Storager) error {
419+
if remoteStorage != nil {
420+
err := remoteStorage.SetKeyValue("admin-password", password)
421+
if err == nil {
422+
log.Println("Admin password saved to secret store")
423+
424+
return nil
425+
}
426+
427+
return err
428+
}
429+
430+
err := localStorage.SetKeyValue("admin-password", password)
431+
if err == nil {
432+
log.Println("Admin password saved to local keyring")
433+
434+
return nil
435+
}
436+
437+
return err
438+
}
439+
303440
// CommandExecutor is an interface to allow for mocking exec.Command in tests.
304441
type CommandExecutor interface {
305442
Execute(name string, arg ...string) error

0 commit comments

Comments
 (0)