mirror of
https://github.com/fosrl/newt.git
synced 2026-02-28 07:46:37 +00:00
@@ -46,11 +46,12 @@ func startAuthDaemon(ctx context.Context) error {
|
||||
|
||||
// Create auth daemon server
|
||||
cfg := authdaemon.Config{
|
||||
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
|
||||
PrincipalsFilePath: principalsFile,
|
||||
CACertPath: caCertPath,
|
||||
Force: true,
|
||||
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
|
||||
PrincipalsFilePath: principalsFile,
|
||||
CACertPath: caCertPath,
|
||||
Force: true,
|
||||
GenerateRandomPassword: authDaemonGenerateRandomPassword,
|
||||
}
|
||||
|
||||
srv, err := authdaemon.NewServer(cfg)
|
||||
@@ -72,8 +73,6 @@ func startAuthDaemon(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
|
||||
// runPrincipalsCmd executes the principals subcommand logic
|
||||
func runPrincipalsCmd(args []string) {
|
||||
opts := struct {
|
||||
@@ -148,4 +147,4 @@ Example:
|
||||
newt principals --username alice
|
||||
|
||||
`, defaultPrincipalsPath)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ func (s *Server) ProcessConnection(req ConnectionRequest) {
|
||||
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)
|
||||
}
|
||||
if cfg.PrincipalsFilePath != "" {
|
||||
|
||||
@@ -4,6 +4,8 @@ package authdaemon
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
@@ -122,6 +124,22 @@ func sudoGroup() string {
|
||||
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"
|
||||
|
||||
// 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.
|
||||
func ensureUser(username string, meta ConnectionMetadata) error {
|
||||
func ensureUser(username string, meta ConnectionMetadata, generateRandomPassword bool) error {
|
||||
if username == "" {
|
||||
return nil
|
||||
}
|
||||
@@ -181,7 +199,7 @@ func ensureUser(username string, meta ConnectionMetadata) error {
|
||||
if _, ok := err.(user.UnknownUserError); !ok {
|
||||
return fmt.Errorf("lookup user %s: %w", username, err)
|
||||
}
|
||||
return createUser(username, meta)
|
||||
return createUser(username, meta, generateRandomPassword)
|
||||
}
|
||||
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"}
|
||||
if meta.Homedir {
|
||||
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))
|
||||
}
|
||||
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 u, err := user.Lookup(username); err == nil && u.HomeDir != "" {
|
||||
uid, gid := mustAtoi(u.Uid), mustAtoi(u.Gid)
|
||||
|
||||
@@ -12,7 +12,7 @@ func writeCACertIfNotExists(path, contents string, force bool) error {
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
|
||||
@@ -27,8 +27,9 @@ type Config struct {
|
||||
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.
|
||||
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.
|
||||
PrincipalsFilePath string // Required. Path to the principals data file (JSON: username -> array of principals). No default.
|
||||
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.
|
||||
GenerateRandomPassword bool // If true, set a random password on users when they are provisioned (for SSH PermitEmptyPasswords no).
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
|
||||
9
main.go
9
main.go
@@ -137,6 +137,7 @@ var (
|
||||
authDaemonPrincipalsFile string
|
||||
authDaemonCACertPath string
|
||||
authDaemonEnabled bool
|
||||
authDaemonGenerateRandomPassword bool
|
||||
// Build/version (can be overridden via -ldflags "-X main.newtVersion=...")
|
||||
newtVersion = "version_replaceme"
|
||||
|
||||
@@ -216,6 +217,7 @@ func runNewtMain(ctx context.Context) {
|
||||
authDaemonPrincipalsFile = os.Getenv("AD_PRINCIPALS_FILE")
|
||||
authDaemonCACertPath = os.Getenv("AD_CA_CERT_PATH")
|
||||
authDaemonEnabledEnv := os.Getenv("AUTH_DAEMON_ENABLED")
|
||||
authDaemonGenerateRandomPasswordEnv := os.Getenv("AD_GENERATE_RANDOM_PASSWORD")
|
||||
|
||||
// Metrics/observability env mirrors
|
||||
metricsEnabledEnv := os.Getenv("NEWT_METRICS_PROMETHEUS_ENABLED")
|
||||
@@ -421,6 +423,13 @@ func runNewtMain(ctx context.Context) {
|
||||
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
|
||||
version := flag.Bool("version", false, "Print the version")
|
||||
|
||||
Reference in New Issue
Block a user