@@ -158,10 +158,51 @@ func createUser(username string, meta ConnectionMetadata) error {
158158 } else {
159159 logger .Info ("auth-daemon: added %s to %s" , username , group )
160160 }
161+ if err := configurePasswordlessSudo (username ); err != nil {
162+ logger .Warn ("auth-daemon: configure passwordless sudo for %s: %v" , username , err )
163+ }
164+ }
165+ return nil
166+ }
167+
168+ // configurePasswordlessSudo creates a sudoers.d file to allow passwordless sudo for the user
169+ func configurePasswordlessSudo (username string ) error {
170+ sudoersFile := filepath .Join ("/etc/sudoers.d" , fmt .Sprintf ("90-pangolin-%s" , username ))
171+ content := fmt .Sprintf ("# Created by newt auth-daemon\n %s ALL=(ALL) NOPASSWD:ALL\n " , username )
172+
173+ // Write to temp file first
174+ tmpFile := sudoersFile + ".tmp"
175+ if err := os .WriteFile (tmpFile , []byte (content ), 0440 ); err != nil {
176+ return fmt .Errorf ("write temp sudoers file: %w" , err )
177+ }
178+
179+ // Validate with visudo
180+ cmd := exec .Command ("visudo" , "-c" , "-f" , tmpFile )
181+ if out , err := cmd .CombinedOutput (); err != nil {
182+ os .Remove (tmpFile )
183+ return fmt .Errorf ("visudo validation failed: %w (output: %s)" , err , string (out ))
184+ }
185+
186+ // Move to final location
187+ if err := os .Rename (tmpFile , sudoersFile ); err != nil {
188+ os .Remove (tmpFile )
189+ return fmt .Errorf ("move sudoers file: %w" , err )
161190 }
191+
192+ logger .Info ("auth-daemon: configured passwordless sudo for %s" , username )
162193 return nil
163194}
164195
196+ // removePasswordlessSudo removes the sudoers.d file for the user
197+ func removePasswordlessSudo (username string ) {
198+ sudoersFile := filepath .Join ("/etc/sudoers.d" , fmt .Sprintf ("90-newt-%s" , username ))
199+ if err := os .Remove (sudoersFile ); err != nil && ! os .IsNotExist (err ) {
200+ logger .Warn ("auth-daemon: remove passwordless sudo for %s: %v" , username , err )
201+ } else if err == nil {
202+ logger .Info ("auth-daemon: removed passwordless sudo for %s" , username )
203+ }
204+ }
205+
165206func mustAtoi (s string ) int {
166207 n , _ := strconv .Atoi (s )
167208 return n
@@ -189,6 +230,15 @@ func reconcileUser(u *user.User, meta ConnectionMetadata) error {
189230 logger .Info ("auth-daemon: removed %s from %s" , u .Username , group )
190231 }
191232 }
233+
234+ // Configure passwordless sudo
235+ if meta .Sudo {
236+ if err := configurePasswordlessSudo (u .Username ); err != nil {
237+ logger .Warn ("auth-daemon: configure passwordless sudo for %s: %v" , u .Username , err )
238+ }
239+ } else {
240+ removePasswordlessSudo (u .Username )
241+ }
192242 if meta .Homedir && u .HomeDir != "" {
193243 if st , err := os .Stat (u .HomeDir ); err != nil || ! st .IsDir () {
194244 if err := os .MkdirAll (u .HomeDir , 0755 ); err != nil {
0 commit comments