@@ -2,6 +2,8 @@ package main
22
33import (
44 "context"
5+ "crypto/rand"
6+ "encoding/base64"
57 "errors"
68 "fmt"
79 "log"
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.
3035var (
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.
304441type CommandExecutor interface {
305442 Execute (name string , arg ... string ) error
0 commit comments