Skip to content

Commit 97528fe

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 97528fe

File tree

4 files changed

+427
-1
lines changed

4 files changed

+427
-1
lines changed

cmd/app/main.go

Lines changed: 134 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"
@@ -64,6 +66,7 @@ func main() {
6466
}
6567

6668
handleEncryptionKey(cfg)
69+
handleAdminPassword(cfg)
6770
handleDebugMode(cfg)
6871
runAppFunc(cfg)
6972
}
@@ -300,6 +303,137 @@ func handleKeyNotFound(toolkitCrypto security.Crypto, _, _ security.Storager) st
300303
return toolkitCrypto.GenerateKey()
301304
}
302305

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

0 commit comments

Comments
 (0)