Skip to content

Commit

Permalink
Add support for entering an SSH key when creating a virtual server
Browse files Browse the repository at this point in the history
  • Loading branch information
jcameron committed May 17, 2021
1 parent 88ee7b1 commit 94d2a08
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 11 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -1619,3 +1619,5 @@ SSL certicates can now be generated and managed for virtual servers even when th
Added the Cloud DNS Providers page, for configuring Virtualmin to use Route53 to host DNS rather than doing it locally.
---- Changes since 6.15 ----
PHP-FPM using socket files instead of TCP ports is now fully supported, using the modify-web API command.
---- Changes since 6.16 ----
Added a field to the virtual server creation page to use an existing SSH key for logins, or generate a new key.
39 changes: 39 additions & 0 deletions create-domain.pl
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,17 @@ package virtual_server;
elsif ($a eq "--generate-ssl-cert") {
$always_ssl = 1;
}
elsif ($a eq "--generate-ssh-key") {
$sshmode = 1;
}
elsif ($a eq "--use-ssh-key") {
$sshmode = 2;
$sshkey = shift(@ARGV);
if ($sshkey =~ /^\//) {

This comment has been minimized.

Copy link
@iliajie

iliajie May 17, 2021

Collaborator

I think this would be better to check on if (-r $sshkey) {} in case a file expected to be located on the same dir.

This comment has been minimized.

Copy link
@jcameron

jcameron May 18, 2021

Author Collaborator

No, we need to force use of a full path because this command might chdir before getting to this code

$sshkey = &read_file_contents($sshkey);
}
$sshkey =~ /\S/ || &usage("--use-ssh-key must be followed by a key file or data");
}
elsif ($a eq "--multiline") {
$multiline = 1;
}
Expand Down Expand Up @@ -867,6 +878,33 @@ package virtual_server;
&$second_print($text{'setup_done'});
}

if ($sshmode == 1) {
# Generate and use a key
&$first_print($text{'setup_sshkey1'});
($sshkey, $err) = &create_domain_ssh_key(\%dom);
if (!$err) {
$err = &save_domain_ssh_pubkey(\%dom, $sshkey);
}
if ($err) {
&$second_print(&text('setup_esshkey', $err));
}
else {
&$second_print($text{'setup_done'});
}
}
elsif ($sshmode == 2) {
# Just use an existing key
&$first_print($text{'setup_sshkey2'});
$sshkey =~ s/\r|\n/ /g;
$err = &save_domain_ssh_pubkey(\%dom, $sshkey);
if ($err) {
&$second_print(&text('setup_esshkey', $err));
}
else {
&$second_print($text{'setup_done'});
}
}

&virtualmin_api_log(\@OLDARGV, \%dom, $dom{'hashpass'} ? [ "pass" ] : [ ]);
&run_post_actions_silently();
&unlock_domain_name($domain);
Expand Down Expand Up @@ -954,6 +992,7 @@ sub usage
print " [--mysql-server hostname]\n";
print " [--break-ssl-cert | --link-ssl-cert]\n";
print " [--generate-ssl-cert]\n";
print " [--generate-ssh-key | --use-ssh-key file|data]\n";
exit(1);
}

Expand Down
8 changes: 8 additions & 0 deletions domain_form.cgi
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,14 @@ if (!$parentuser) {
print &ui_table_row(&hlink($text{'form_pass'}, "password"),
&new_password_input("vpass"),
undef, \@tds);

# SSH public key for Unix user
print &ui_table_row(&hlink($text{'form_sshkey'}, "sshkey"),
&ui_radio("sshkey_mode", 0,
[ [ 0, $text{'form_sshkey0'} ],
[ 1, $text{'form_sshkey1'} ],
[ 2, $text{'form_sshkey2'} ] ])."<br>\n".
&ui_textarea("sshkey", undef, 3, 60), undef, \@tds);
}

# Generate Javascript for template change
Expand Down
28 changes: 28 additions & 0 deletions domain_setup.cgi
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,34 @@ if (!$dom{'alias'} && &domain_has_website(\%dom) &&
&$second_print($text{'setup_done'});
}

# Setup SSH public key if one was given

This comment has been minimized.

Copy link
@iliajie

iliajie May 17, 2021

Collaborator

I suggest adding this right after creating administration user, like this:

Creating administration group ilia ..
.. done
Creating administration user ilia ..
.. done
Authorizing SSH public key ..
.. done
if ($in{'sshkey_mode'} == 1) {
# Generate a keypair for the user
&$first_print($text{'setup_sshkey1'});
($sshkey, $err) = &create_domain_ssh_key(\%dom);
if (!$err) {
$err = &save_domain_ssh_pubkey(\%dom, $sshkey);
}
if ($err) {
&$second_print(&text('setup_esshkey', $err));
}
else {
&$second_print($text{'setup_done'});
}
}
elsif ($in{'sshkey_mode'} == 2) {
# Use only the given public key
&$first_print($text{'setup_sshkey2'});
$in{'sshkey'} =~ s/\r|\n/ /g;
$err = &save_domain_ssh_pubkey(\%dom, $in{'sshkey'});
if ($err) {
&$second_print(&text('setup_esshkey', $err));
}
else {
&$second_print($text{'setup_done'});
}
}

&run_post_actions();
&unlock_domain_name($dname);
&webmin_log("create", "domain", $dom{'dom'}, \%dom);
Expand Down
19 changes: 19 additions & 0 deletions help/sshkey.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<header>SSH public key</header>

This field can be used to grant SSH access authenticated via a public key
to the new virtual server's Unix user. The options are : <p>

<dl>
<dt><b>No default key</b>
<dd>Don't setup an SSH key. Only logins with a password will be allowed. <p>

<dt><b>Generate public and private keys</b>
<dd>Create a new SSH key pair for this virtual server, and allow logins using
that key. The server owner should copy the private key after creation.<p>

<dt><b>Use public key below</b>
<dd>SSH logins will be allowed using the private key that corresponds to the
public key entered in the text box below.<p>
</dl>

<footer>
10 changes: 10 additions & 0 deletions lang/en
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,10 @@ form_name1=Name-based
form_name0=IP-based
form_ip=IP address for website and domain
form_pass=Administration password
form_sshkey=SSH public key
form_sshkey0=No default key
form_sshkey1=Generate private and public keys
form_sshkey2=Use public key below ..
form_pass1=Generate randomly
form_pass0=As entered
form_passf=Password
Expand Down Expand Up @@ -616,6 +620,12 @@ setup_evaddaliasdomain=.. vaddaliasdomain failed : $1
setup_fwding=Adding alias to forward all email to $1 ..
setup_styleing=Applying initial website style ..
setup_contenting=Creating initial website index page ..
setup_sshkey1=Generating SSH private and public keys ..
setup_sshkey2=Authorizing SSH public key ..
setup_esshkeydir=Virtual server need a directory and Unix user
setup_esshsshd=SSH server is not installed
setup_esshnopub=No public key file was created!
setup_esshkey=.. key generation failed : $1
setup_domaliases=Adding default mail aliases ..
setup_domaliasbad=.. not a valid domain name!
setup_noquotainf0=Unlimited server quota cannot be selected, as you have a quota limit of $1 in force.
Expand Down
65 changes: 54 additions & 11 deletions virtual-server-lib-funcs.pl
Original file line number Diff line number Diff line change
Expand Up @@ -18268,21 +18268,21 @@ sub load_plugin_libraries
# fix GRUB.
sub needs_xfs_quota_fix
{
return 0 if ($gconfig{'os_type'} !~ /-linux$/); # Some other OS
return 0 if (!$config{'quotas'}); # Quotas not even in use
return 0 if ($config{'quota_commands'}); # Using external commands
return 0 if ($gconfig{'os_type'} !~ /-linux$/); # Some other OS
return 0 if (!$config{'quotas'}); # Quotas not even in use
return 0 if ($config{'quota_commands'}); # Using external commands
&require_useradmin();
return 0 if (!$home_base); # Don't know base dir
return 0 if (&running_in_zone()); # Zones have no quotas
return 0 if (!$home_base); # Don't know base dir
return 0 if (&running_in_zone()); # Zones have no quotas
my ($home_mtab, $home_fstab) = &mount_point($home_base);
return 0 if (!$home_mtab || !$home_fstab); # No mount found?
return 0 if ($home_mtab->[2] ne "xfs"); # Other FS type
return 0 if ($home_mtab->[0] ne "/"); # /home is not on the / FS
return 0 if (!&quota::quota_can($home_mtab, # Not enabled in fstab
return 0 if (!$home_mtab || !$home_fstab); # No mount found?
return 0 if ($home_mtab->[2] ne "xfs"); # Other FS type
return 0 if ($home_mtab->[0] ne "/"); # /home is not on the / FS
return 0 if (!&quota::quota_can($home_mtab, # Not enabled in fstab
$home_fstab));
my $now = &quota::quota_now($home_mtab, $home_fstab);
$now -= 4 if ($now >= 4); # Ignore XFS always bit
return 0 if ($now); # Already enabled in mtab
$now -= 4 if ($now >= 4); # Ignore XFS always bit
return 0 if ($now); # Already enabled in mtab

# At this point, we are definite in a bad state
my $grubfile = "/etc/default/grub";
Expand Down Expand Up @@ -18311,6 +18311,49 @@ sub needs_xfs_quota_fix
return 2;
}

# create_domain_ssh_key(&domain)
# Creates an SSH public and private key for a domain, and returns the public
# key and an error message.
sub create_domain_ssh_key
{
my ($d) = @_;
return (undef, $text{'setup_esshkeydir'}) if (!$d->{'dir'} || !$d->{'unix'});
return (undef, $text{'setup_esshsshd'}) if (!&foreign_installed("sshd"));
my $sshdir = $d->{'home'}."/.ssh";
my %oldpubs = map { $_, 1 } glob("$sshdir/*.pub");
&foreign_require("sshd");
my $cmd = $sshd::config{'keygen_path'}." -P \"\"";
$cmd = &command_as_user($d->{'user'}, 0, $cmd);
my $out;
my $inp = "\n";
&execute_command($cmd, \$inp, \$out, \$out);
if ($?) {
return (undef, $out);
}
my @newpubs = grep { !$oldpubs{$_} } glob("$sshdir/*.pub");
return (undef, $text{'setup_esshnopub'}) if (!@newpubs);
return (&read_file_contents($newpubs[0]), undef);
}

# save_domain_ssh_pubkey(&domain, pubkey)
# Adds an SSH public key to the authorized keys file
sub save_domain_ssh_pubkey
{
my ($d, $pubkey) = @_;
return $text{'setup_esshkeydir'} if (!$d->{'dir'});
my $sshdir = $d->{'home'}."/.ssh";
if (!-d $sshdir) {
&make_dir_as_domain_user($d, $sshdir, 0700);
}
my $sshfile = $sshdir."/authorized_keys";
my $ex = -e $sshfile;

This comment has been minimized.

Copy link
@iliajie

iliajie May 17, 2021

Collaborator

This needs a fix. 🙂 f398a78 $ex will always be false and we need to set 600 for id_rsa.pub when the keys are generated on the command above.

This comment has been minimized.

Copy link
@jcameron

jcameron May 18, 2021

Author Collaborator

No the original code was correct. The goal is to only set permissions on authorized_keys if creating it new.

This comment has been minimized.

Copy link
@iliajie

iliajie May 18, 2021

Collaborator

Oh, I see. You were making sure that initial permissions are kept. Right. Sorry, my bad. I reverted previous patch and made a new one with a question in a commit's description - 385cf46

&open_tempfile_as_domain_user($d, SSHFILE, ">>$sshfile");
&print_tempfile(SSHFILE, $pubkey."\n");
&close_tempfile_as_domain_user($d, SSHFILE);
&set_permissions_as_domain_user($d, 0600, $sshfile) if (!$ex);
return undef;
}

sub get_module_version_and_type
{
my ($list, $gpl) = @_;
Expand Down

0 comments on commit 94d2a08

Please sign in to comment.