[Pkg-shadow-devel] [Git][debian/adduser][debian/latest] 3 commits: adduser: add --add-authorized-key option

Marc Haber (@zugschlus) gitlab at salsa.debian.org
Fri Aug 28 17:07:03 BST 2026



Marc Haber pushed to branch debian/latest at Debian / adduser


Commits:
2e5dd268 by Juanes at 2026-08-18T11:52:30-05:00
adduser: add --add-authorized-key option

Install SSH public keys from a file or from the command line into the
new user's ~/.ssh/authorized_keys.

Closes: #446362

- - - - -
65c9c768 by Juanes at 2026-08-18T11:52:30-05:00
adduser: document --add-authorized-key in the manpage

- - - - -
b1a5696c by Juanes at 2026-08-18T17:00:55-05:00
tests: cover --add-authorized-key

- - - - -


4 changed files:

- AdduserCreateHomedir.pm
- adduser
- + debian/tests/f/authorized_keys.t
- doc/adduser.8


Changes:

=====================================
AdduserCreateHomedir.pm
=====================================
@@ -30,6 +30,7 @@ sub create_homedir {
     my $primary_gid = $params{gid};
     my $copy_skeleton = $params{copy_skeleton};
     my $force_home = $params{force_home};
+    my $authorized_keys = $params{authorized_keys} // [];
     my $system_user = $params{system_user};
     my $no_create_home = $params{no_create_home};
     my $encrypt_home = $params{encrypt_home};
@@ -49,11 +50,13 @@ sub create_homedir {
 
     if ($home_dir =~ /^\/+nonexistent(\/|$)/) {
         log_info(mtx("Not creating `%s'."), $home_dir);
+        warn_no_keys($home_dir, $authorized_keys);
         return 0;
     }
 
     if ($no_create_home) {
         log_info(mtx("Not creating home directory `%s' as requested."), $home_dir);
+        warn_no_keys($home_dir, $authorized_keys);
         return 0;
     }
 
@@ -65,6 +68,7 @@ sub create_homedir {
             if (($homedir_stat[4] != $new_uid) || ($homedir_stat[5] != $primary_gid)) {
                 log_warn(mtx("Warning: The home directory `%s' does not belong to the user you are currently creating."), $home_dir);
             }
+            warn_no_keys($home_dir, $authorized_keys);
             return 0;
         }
     }
@@ -125,6 +129,12 @@ sub create_homedir {
         ) and return RET_FILE_ERROR;
     }
 
+    if (@$authorized_keys) {
+        my $ret = install_authorized_keys($home_dir, $authorized_keys,
+            $new_uid, $primary_gid, $dir_mode);
+        return $ret if ($ret);
+    }
+
     if (defined($encrypt_home)) {
         &systemcall("/bin/umount", $home_dir);
     }
@@ -291,6 +301,82 @@ sub set_perms {
     return 1;
 }
 
+sub warn_no_keys {
+    my ($home_dir, $authorized_keys) = @_;
+    return if (!@$authorized_keys);
+    log_warn(mtx("Not installing authorized key(s) into `%s'."), $home_dir);
+}
+
+sub collect_authorized_keys {
+    my ($authorized_keys) = @_;
+    my @lines;
+
+    foreach my $arg (@$authorized_keys) {
+        if (! -f $arg) {
+            push @lines, $arg;
+            next;
+        }
+        my $fh;
+        if (!open($fh, '<', $arg)) {
+            log_warn(mtx("Cannot open `%s': %s. Not installing this key."),
+                sanitize_string($arg, '[-_./a-zA-Z0-9]+', 1), $!);
+            next;
+        }
+        while (my $line = <$fh>) {
+            chomp $line;
+            next if ($line =~ /^\s*$/);
+            push @lines, $line;
+        }
+        close($fh);
+    }
+
+    return @lines;
+}
+
+sub install_authorized_keys {
+    my ($home_dir, $authorized_keys, $new_uid, $primary_gid, $dir_mode) = @_;
+
+    my @keys = collect_authorized_keys($authorized_keys);
+    return RET_OK if (!@keys);
+
+    my $sshdir = "$home_dir/.ssh";
+    my $keyfile = "$sshdir/authorized_keys";
+    log_info(mtx("Adding %d authorized key(s) to `%s' ..."), scalar(@keys), $keyfile);
+
+    if (! -d $sshdir) {
+        mkdir($sshdir, 0700) or do {
+            log_err("mkdir %s: %s", $sshdir, $!);
+            return RET_FILE_ERROR;
+        };
+    }
+
+    my $fh;
+    open($fh, '>>', $keyfile) or do {
+        log_err("open %s: %s", $keyfile, $!);
+        return RET_FILE_ERROR;
+    };
+    print {$fh} "$_\n" foreach (@keys);
+    close($fh) or do {
+        log_err("close %s: %s", $keyfile, $!);
+        return RET_FILE_ERROR;
+    };
+
+    chown($new_uid, $primary_gid, $sshdir, $keyfile) == 2 or do {
+        log_err("chown %s:%s %s: %s", $new_uid, $primary_gid, $sshdir, $!);
+        return RET_WRONG_OBJECT_PROPERTIES;
+    };
+    chmod($dir_mode, $sshdir) or do {
+        log_err("chmod %s %s: %s", $dir_mode, $sshdir, $!);
+        return RET_WRONG_OBJECT_PROPERTIES;
+    };
+    chmod(0600, $keyfile) or do {
+        log_err("chmod 0600 %s: %s", $keyfile, $!);
+        return RET_WRONG_OBJECT_PROPERTIES;
+    };
+
+    return RET_OK;
+}
+
 1;
 
 # Local Variables:


=====================================
adduser
=====================================
@@ -114,6 +114,7 @@ my $disabled_login = 0;		# leave the new account disabled?
 
 our @configfiles;
 our @defaults = undef;
+our @authorized_keys = ();
 our $encrypt_home = undef;
 our $found_group_opt = undef;
 our $found_sys_opt = undef;
@@ -160,6 +161,7 @@ our @names;
 
 log_trace("ARGV %s", join(@ARGV,"-"));
 GetOptions(
+    'add-authorized-key=s' => \@authorized_keys,
     'add-extra-groups' => \$add_extra_groups,
     'add_extra_groups' => \$add_extra_groups_old,
     'allow-all-names' => sub { $name_check_level = 2 },
@@ -343,6 +345,11 @@ if ($found_unlock_opt) {
     $action = "unlockuser";
 }
 
+if (@authorized_keys && $action ne "adduser") {
+    log_warn( mtx("The --add-authorized-key option is only supported when adding a regular user. Not installing any keys.") );
+    @authorized_keys = ();
+}
+
 # $new_firstuid = $new_firstuid || $config{"first_uid"} || 1000;
 # $new_lastuid = $new_lastuid || $config{"last_uid"} || 59999;
 # $new_firstgid = $new_firstgid || $config{"first_gid"} || 1000;
@@ -1017,6 +1024,7 @@ if ($action eq "adduser") {
         gid => $primary_gid,
         copy_skeleton => $no_copy_skel ? 0 : 1,
         force_home => $force_home ? 1 : 0,
+        authorized_keys => \@authorized_keys,
         system_user => 0,
         no_create_home => $no_create_home,
         encrypt_home => $encrypt_home,


=====================================
debian/tests/f/authorized_keys.t
=====================================
@@ -0,0 +1,134 @@
+#! /usr/bin/perl -Idebian/tests/lib
+
+# tests for adduser --add-authorized-key
+
+use diagnostics;
+use strict;
+use warnings;
+
+use AdduserTestsCommon;
+use Test::More;
+
+my $key_one = 'ssh-ed25519 AAAAoneoneone one at example.org';
+my $key_two = 'ssh-rsa AAAAtwotwotwo two at example.org';
+my $key_three = 'ssh-ed25519 AAAAthreethree three at example.org';
+my $keyfile = '/tmp/adduser-test-keys.pub';
+
+my @users = qw(ausakliteral ausakfile ausakrepeat ausaknohome ausaknonexist
+               ausakexisting ausaksystem);
+
+END {
+    cleanup_user($_) foreach (@users);
+    unlink($keyfile);
+    done_testing();
+}
+
+sub keyfile_lines {
+    my ($path) = @_;
+    open(my $fh, '<', $path) or return ();
+    my @lines = <$fh>;
+    close($fh);
+    chomp(@lines);
+    return @lines;
+}
+
+sub add_user_with_keys {
+    my ($name, @args) = @_;
+    assert_command_success(
+        '/usr/sbin/adduser',
+        '--stdoutmsglevel=error', '--stderrmsglevel=error',
+        '--comment', '""',
+        '--disabled-password',
+        @args,
+        $name
+    );
+    assert_user_exists($name);
+}
+
+# a literal key is installed with the expected ownership and modes
+my $name = 'ausakliteral';
+assert_user_does_not_exist($name);
+add_user_with_keys($name, '--add-authorized-key', $key_one);
+assert_path_is_a_directory("/home/$name/.ssh");
+assert_path_is_a_file("/home/$name/.ssh/authorized_keys");
+assert_path_has_ownership("/home/$name/.ssh", "$name:$name");
+assert_path_has_ownership("/home/$name/.ssh/authorized_keys", "$name:$name");
+assert_path_has_mode("/home/$name/.ssh/authorized_keys", "600");
+is_deeply([keyfile_lines("/home/$name/.ssh/authorized_keys")], [$key_one],
+    "the literal key has been installed");
+
+# a file argument installs all keys it contains, empty lines are skipped
+open(my $fh, '>', $keyfile) or die "cannot write $keyfile: $!";
+print {$fh} "$key_one\n\n$key_two\n";
+close($fh);
+
+$name = 'ausakfile';
+assert_user_does_not_exist($name);
+add_user_with_keys($name, '--add-authorized-key', $keyfile);
+is_deeply([keyfile_lines("/home/$name/.ssh/authorized_keys")],
+    [$key_one, $key_two],
+    "both keys of the file have been installed, empty line skipped");
+
+# the option can be given more than once
+$name = 'ausakrepeat';
+assert_user_does_not_exist($name);
+add_user_with_keys($name,
+    '--add-authorized-key', $key_three,
+    '--add-authorized-key', $keyfile);
+is_deeply([keyfile_lines("/home/$name/.ssh/authorized_keys")],
+    [$key_three, $key_one, $key_two],
+    "keys of all occurrences of the option have been installed");
+
+# keys are appended, an authorized_keys from /etc/skel is kept
+my $skeldir = '/etc/skel/.ssh';
+my $skelfile = "$skeldir/authorized_keys";
+my $skel_created = 0;
+if (! -e $skeldir) {
+    mkdir($skeldir, 0700) or die "cannot create $skeldir: $!";
+    $skel_created = 1;
+}
+if (! -e $skelfile) {
+    open(my $sfh, '>', $skelfile) or die "cannot write $skelfile: $!";
+    print {$sfh} "$key_two\n";
+    close($sfh);
+
+    $name = 'ausakexisting';
+    assert_user_does_not_exist($name);
+    add_user_with_keys($name, '--add-authorized-key', $key_one);
+    is_deeply([keyfile_lines("/home/$name/.ssh/authorized_keys")],
+        [$key_two, $key_one],
+        "the authorized_keys from /etc/skel has been kept");
+
+    unlink($skelfile);
+}
+rmdir($skeldir) if ($skel_created);
+
+# no home directory, no keys, but the user is still created
+$name = 'ausaknohome';
+assert_user_does_not_exist($name);
+add_user_with_keys($name, '--no-create-home', '--add-authorized-key', $key_one);
+assert_path_does_not_exist("/home/$name/.ssh");
+
+$name = 'ausaknonexist';
+assert_user_does_not_exist($name);
+add_user_with_keys($name, '--home', '/nonexistent',
+    '--add-authorized-key', $key_one);
+assert_path_does_not_exist("/nonexistent/.ssh");
+
+# the option is not supported for system users
+$name = 'ausaksystem';
+assert_user_does_not_exist($name);
+assert_command_success(
+    '/usr/sbin/adduser',
+    '--stdoutmsglevel=error', '--stderrmsglevel=error',
+    '--comment', '""',
+    '--system',
+    '--add-authorized-key', $key_one,
+    $name
+);
+assert_user_exists($name);
+assert_path_does_not_exist("/home/$name/.ssh");
+
+# done_testing(); done in END
+
+# vim: tabstop=4 shiftwidth=4 expandtab


=====================================
doc/adduser.8
=====================================
@@ -18,6 +18,7 @@
 adduser, addgroup \- add or manipulate users or groups
 .SH SYNOPSIS
 .SY adduser
+.OP \-\-add\-authorized\-key key
 .OP \-\-add\-extra\-groups
 .OP \-\-allow\-all\-names
 .OP \-\-allow\-bad\-names
@@ -402,6 +403,36 @@ Short versions for certain options may exist for historical reasons.
 They are going to stay supported, but are removed from the documentation.
 Users are advised to migrate to the long version of options.
 .TP
+.B \-\-add\-authorized\-key key
+Add
+.I key
+to the \fI\%~/.ssh/authorized_keys\fP file of the new user.
+If
+.I key
+is the name of an existing file,
+the contents of that file are added,
+which allows to add several keys at once.
+Otherwise,
+.I key
+is taken literally as a single key.
+The option can be given more than once.
+Keys are appended to \fI\%authorized_keys\fP,
+so that keys shipped in \fI\%/etc/skel\fP are kept.
+Keys are not checked for validity;
+an unusable key is going to be rejected by the SSH daemon later.
+No key is installed if the home directory has not been created by
+\fB\%adduser\fP,
+for example if \fB\-\-no\-create\-home\fP has been given,
+if the home directory is \fI\%/nonexistent\fP,
+or if the home directory does already exist.
+In that case,
+a warning is issued and the user is still created.
+Note that \fB\%sshd\fP with \fBStrictModes\fP enabled ignores
+\fI\%authorized_keys\fP if the home directory or \fI\%~/.ssh\fP
+is writable by group or other,
+which can happen with a permissive \fBDIR_MODE\fP setting.
+Valid modes: \fBadduser\fP.
+.TP
 .B \-\-add\-extra\-groups
 Add new user to extra groups defined in the configuration files'
 \fBEXTRA_GROUPS\fP setting.



View it on GitLab: https://salsa.debian.org/debian/adduser/-/compare/e4b802bc8c8272ebbfbcc95ce9dca15b23913f82...b1a5696c2bd43d80d5bc7e5511dc03d3b1fb5fde

-- 
View it on GitLab: https://salsa.debian.org/debian/adduser/-/compare/e4b802bc8c8272ebbfbcc95ce9dca15b23913f82...b1a5696c2bd43d80d5bc7e5511dc03d3b1fb5fde
You're receiving this email because of your account on salsa.debian.org. Manage all notifications: https://salsa.debian.org/-/profile/notifications | Help: https://salsa.debian.org/help


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-shadow-devel/attachments/20260828/2c6d9c7e/attachment-0001.htm>


More information about the Pkg-shadow-devel mailing list