generate random user password

This commit is contained in:
miloschwartz
2026-02-26 10:29:00 -08:00
parent 555e1ddc7c
commit 1bf89a2cc9
6 changed files with 49 additions and 15 deletions

View File

@@ -46,11 +46,12 @@ func startAuthDaemon(ctx context.Context) error {
// Create auth daemon server // Create auth daemon server
cfg := authdaemon.Config{ cfg := authdaemon.Config{
DisableHTTPS: true, // We run without HTTP server in newt DisableHTTPS: true, // We run without HTTP server in newt
PresharedKey: "this-key-is-not-used", // Not used in embedded mode, but set to non-empty to satisfy validation PresharedKey: "this-key-is-not-used", // Not used in embedded mode, but set to non-empty to satisfy validation
PrincipalsFilePath: principalsFile, PrincipalsFilePath: principalsFile,
CACertPath: caCertPath, CACertPath: caCertPath,
Force: true, Force: true,
GenerateRandomPassword: authDaemonGenerateRandomPassword,
} }
srv, err := authdaemon.NewServer(cfg) srv, err := authdaemon.NewServer(cfg)
@@ -72,8 +73,6 @@ func startAuthDaemon(ctx context.Context) error {
return nil return nil
} }
// runPrincipalsCmd executes the principals subcommand logic // runPrincipalsCmd executes the principals subcommand logic
func runPrincipalsCmd(args []string) { func runPrincipalsCmd(args []string) {
opts := struct { opts := struct {

View File

@@ -16,7 +16,7 @@ func (s *Server) ProcessConnection(req ConnectionRequest) {
logger.Warn("auth-daemon: write CA cert: %v", err) logger.Warn("auth-daemon: write CA cert: %v", err)
} }
} }
if err := ensureUser(req.Username, req.Metadata); err != nil { if err := ensureUser(req.Username, req.Metadata, s.cfg.GenerateRandomPassword); err != nil {
logger.Warn("auth-daemon: ensure user: %v", err) logger.Warn("auth-daemon: ensure user: %v", err)
} }
if cfg.PrincipalsFilePath != "" { if cfg.PrincipalsFilePath != "" {

View File

@@ -4,6 +4,8 @@ package authdaemon
import ( import (
"bufio" "bufio"
"crypto/rand"
"encoding/hex"
"encoding/json" "encoding/json"
"fmt" "fmt"
"os" "os"
@@ -122,6 +124,22 @@ func sudoGroup() string {
return "sudo" return "sudo"
} }
// setRandomPassword generates a random password and sets it for username via chpasswd.
// Used when GenerateRandomPassword is true so SSH with PermitEmptyPasswords no can accept the user.
func setRandomPassword(username string) error {
b := make([]byte, 16)
if _, err := rand.Read(b); err != nil {
return fmt.Errorf("generate password: %w", err)
}
password := hex.EncodeToString(b)
cmd := exec.Command("chpasswd")
cmd.Stdin = strings.NewReader(username + ":" + password)
if out, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("chpasswd: %w (output: %s)", err, string(out))
}
return nil
}
const skelDir = "/etc/skel" const skelDir = "/etc/skel"
// copySkelInto copies files from srcDir (e.g. /etc/skel) into dstDir (e.g. user's home). // copySkelInto copies files from srcDir (e.g. /etc/skel) into dstDir (e.g. user's home).
@@ -172,7 +190,7 @@ func copySkelInto(srcDir, dstDir string, uid, gid int) {
} }
// ensureUser creates the system user if missing, or reconciles sudo and homedir to match meta. // ensureUser creates the system user if missing, or reconciles sudo and homedir to match meta.
func ensureUser(username string, meta ConnectionMetadata) error { func ensureUser(username string, meta ConnectionMetadata, generateRandomPassword bool) error {
if username == "" { if username == "" {
return nil return nil
} }
@@ -181,7 +199,7 @@ func ensureUser(username string, meta ConnectionMetadata) error {
if _, ok := err.(user.UnknownUserError); !ok { if _, ok := err.(user.UnknownUserError); !ok {
return fmt.Errorf("lookup user %s: %w", username, err) return fmt.Errorf("lookup user %s: %w", username, err)
} }
return createUser(username, meta) return createUser(username, meta, generateRandomPassword)
} }
return reconcileUser(u, meta) return reconcileUser(u, meta)
} }
@@ -223,7 +241,7 @@ func setUserGroups(username string, groups []string) {
} }
} }
func createUser(username string, meta ConnectionMetadata) error { func createUser(username string, meta ConnectionMetadata, generateRandomPassword bool) error {
args := []string{"-s", "/bin/bash"} args := []string{"-s", "/bin/bash"}
if meta.Homedir { if meta.Homedir {
args = append(args, "-m") args = append(args, "-m")
@@ -236,6 +254,13 @@ func createUser(username string, meta ConnectionMetadata) error {
return fmt.Errorf("useradd %s: %w (output: %s)", username, err, string(out)) return fmt.Errorf("useradd %s: %w (output: %s)", username, err, string(out))
} }
logger.Info("auth-daemon: created user %s (homedir=%v)", username, meta.Homedir) logger.Info("auth-daemon: created user %s (homedir=%v)", username, meta.Homedir)
if generateRandomPassword {
if err := setRandomPassword(username); err != nil {
logger.Warn("auth-daemon: set random password for %s: %v", username, err)
} else {
logger.Info("auth-daemon: set random password for %s (PermitEmptyPasswords no)", username)
}
}
if meta.Homedir { if meta.Homedir {
if u, err := user.Lookup(username); err == nil && u.HomeDir != "" { if u, err := user.Lookup(username); err == nil && u.HomeDir != "" {
uid, gid := mustAtoi(u.Uid), mustAtoi(u.Gid) uid, gid := mustAtoi(u.Uid), mustAtoi(u.Gid)

View File

@@ -12,7 +12,7 @@ func writeCACertIfNotExists(path, contents string, force bool) error {
} }
// ensureUser returns an error on non-Linux. // ensureUser returns an error on non-Linux.
func ensureUser(username string, meta ConnectionMetadata) error { func ensureUser(username string, meta ConnectionMetadata, generateRandomPassword bool) error {
return errLinuxOnly return errLinuxOnly
} }

View File

@@ -27,8 +27,9 @@ type Config struct {
Port int // Required when DisableHTTPS is false. Listen port for the HTTPS server. No default. Port int // Required when DisableHTTPS is false. Listen port for the HTTPS server. No default.
PresharedKey string // Required when DisableHTTPS is false. HTTP auth (Authorization: Bearer <key> or X-Preshared-Key: <key>). No default. PresharedKey string // Required when DisableHTTPS is false. HTTP auth (Authorization: Bearer <key> or X-Preshared-Key: <key>). No default.
CACertPath string // Required. Where to write the CA cert (e.g. /etc/ssh/ca.pem). No default. CACertPath string // Required. Where to write the CA cert (e.g. /etc/ssh/ca.pem). No default.
Force bool // If true, overwrite existing CA cert (and other items) when content differs. Default false. Force bool // If true, overwrite existing CA cert (and other items) when content differs. Default false.
PrincipalsFilePath string // Required. Path to the principals data file (JSON: username -> array of principals). No default. PrincipalsFilePath string // Required. Path to the principals data file (JSON: username -> array of principals). No default.
GenerateRandomPassword bool // If true, set a random password on users when they are provisioned (for SSH PermitEmptyPasswords no).
} }
type Server struct { type Server struct {

View File

@@ -137,6 +137,7 @@ var (
authDaemonPrincipalsFile string authDaemonPrincipalsFile string
authDaemonCACertPath string authDaemonCACertPath string
authDaemonEnabled bool authDaemonEnabled bool
authDaemonGenerateRandomPassword bool
// Build/version (can be overridden via -ldflags "-X main.newtVersion=...") // Build/version (can be overridden via -ldflags "-X main.newtVersion=...")
newtVersion = "version_replaceme" newtVersion = "version_replaceme"
@@ -216,6 +217,7 @@ func runNewtMain(ctx context.Context) {
authDaemonPrincipalsFile = os.Getenv("AD_PRINCIPALS_FILE") authDaemonPrincipalsFile = os.Getenv("AD_PRINCIPALS_FILE")
authDaemonCACertPath = os.Getenv("AD_CA_CERT_PATH") authDaemonCACertPath = os.Getenv("AD_CA_CERT_PATH")
authDaemonEnabledEnv := os.Getenv("AUTH_DAEMON_ENABLED") authDaemonEnabledEnv := os.Getenv("AUTH_DAEMON_ENABLED")
authDaemonGenerateRandomPasswordEnv := os.Getenv("AD_GENERATE_RANDOM_PASSWORD")
// Metrics/observability env mirrors // Metrics/observability env mirrors
metricsEnabledEnv := os.Getenv("NEWT_METRICS_PROMETHEUS_ENABLED") metricsEnabledEnv := os.Getenv("NEWT_METRICS_PROMETHEUS_ENABLED")
@@ -421,6 +423,13 @@ func runNewtMain(ctx context.Context) {
authDaemonEnabled = v authDaemonEnabled = v
} }
} }
if authDaemonGenerateRandomPasswordEnv == "" {
flag.BoolVar(&authDaemonGenerateRandomPassword, "ad-generate-random-password", false, "Generate a random password for authenticated users")
} else {
if v, err := strconv.ParseBool(authDaemonGenerateRandomPasswordEnv); err == nil {
authDaemonGenerateRandomPassword = v
}
}
// do a --version check // do a --version check
version := flag.Bool("version", false, "Print the version") version := flag.Bool("version", false, "Print the version")