From 8c91e1edb0e96dc883b68066aee73bf2d1a14b20 Mon Sep 17 00:00:00 2001
From: Matt Birkholz
This small institute has a public server on the Internet, Front, that @@ -48,7 +48,7 @@ connects to Front making the institute email, cloud, etc. available to members off campus.
-+= _|||_ =-The-Institute-= @@ -83,7 +83,7 @@ desktops. When off campus, members access institute resources via the VPN on Front (via hotel Wi-Fi). When on campus, members can use the much faster and always available (despite Internet connectivity issues) VPN on Gate (via campus Wi-Fi). A member's Android phones and -devices can use the same Wi-Fis, VPNs (via the OpenVPN app) and +devices can use the same Wi-Fis, VPNs (via the WireGuard⢠app) and services. On a desktop or by phone, at home or abroad, members can access their email and the institute's private web and cloud. @@ -95,8 +95,8 @@ uses OpenPGP encryption to secure message content.
This small institute prizes its privacy, so there is little or no @@ -144,8 +144,8 @@ month) because of this assumption.
The small institute's network is designed to provide a number of @@ -157,8 +157,8 @@ policies. On first reading, those subsections should be skipped; they reference particulars first introduced in the following chapter.
The institute has a public domain, e.g. small.example.org
, and a
@@ -172,8 +172,8 @@ names like core
.
Front provides the public SMTP (Simple Mail Transfer Protocol) service
@@ -247,8 +247,8 @@ setting for the maximum message size is given in a code block labeled
configurations wherever <<postfix-message-size>>
appears.
The institute aims to accommodate encrypted email containing short @@ -263,7 +263,7 @@ handle maxi-messages.
postfix-message-size
- { p: message_size_limit, v: 104857600 }
+postfix-message-size
- { p: message_size_limit, v: 104857600 }
postfix-queue-times
- { p: delay_warning_time, v: 1h }
+postfix-queue-times
- { p: delay_warning_time, v: 1h }
- { p: maximal_queue_lifetime, v: 4h }
- { p: bounce_queue_lifetime, v: 4h }
@@ -292,7 +292,7 @@ disables relaying (other than for the local networks).
-postfix-relaying
- p: smtpd_relay_restrictions
+postfix-relaying
- p: smtpd_relay_restrictions
v: permit_mynetworks reject_unauth_destination
@@ -304,7 +304,7 @@ effect.
-postfix-maildir
- { p: home_mailbox, v: Maildir/ }
+postfix-maildir
- { p: home_mailbox, v: Maildir/ }
@@ -315,8 +315,8 @@ in the respective roles below.
The Dovecot settings on both Front and Core disable POP and require @@ -330,7 +330,7 @@ The official documentation for Dovecot once was a Wiki but now is
dovecot-tls
protocols = imap
+dovecot-tls
protocols = imap
ssl = required
dovecot-ports
service imap-login {
+dovecot-ports
service imap-login {
inet_listener imap {
port = 0
}
@@ -356,7 +356,7 @@ directories.
-dovecot-maildir
mail_location = maildir:~/Maildir
+dovecot-maildir
mail_location = maildir:~/Maildir
@@ -368,15 +368,15 @@ common settings with host specific settings for ssl_cert
and
Front provides the public HTTP service that serves institute web pages
at e.g. https://small.example.org/
. The small institute initially
runs with a self-signed, "snake oil" server certificate, causing
browsers to warn of possible fraud, but this certificate is easily
-replaced by one signed by a recognized authority, as discussed in The
+replaced by one signed by a recognized authority, as discussed in The
Front Role.
Core runs Nextcloud to provide a private institute cloud at
https://core.small.private/nextcloud/
. It is managed manually per
The Nextcloud Server Administration Guide. The code and data,
including especially database dumps, are stored in /Nextcloud/
which
-is included in Core's backup procedure as described in Backups. The
+is included in Core's backup procedure as described in Backups. The
default Apache2 configuration expects to find the web scripts in
/var/www/nextcloud/
, so the institute symbolically links this to
/Nextcloud/nextcloud/
.
@@ -453,130 +453,15 @@ private network.
-The institute's public and campus VPNs have many common configuration
-options that are discussed here. These are included, with example
-certificates and network addresses, in the complete server
-configurations of The Front Role and The Gate Role, as well as the
-matching client configurations in The Core Role and the .ovpn
files
-generated by The Client Command. The configurations are based on the
-documentation for OpenVPN v2.4: the openvpn(8)
manual page and this
-web page.
-
-The institute VPNs use UDP on a subnet topology (rather than
-point-to-point) with "split tunneling". The UDP support accommodates
-real-time, connection-less protocols. The split tunneling is for
-efficiency with frontier bandwidth. The subnet topology, with the
-client-to-client
option, allows members to "talk" to each other on
-the VPN subnets using any (experimental) protocol.
-
openvpn-dev-mode
dev-type tun
-dev ovpn
-topology subnet
-client-to-client
-
-
-A keepalive
option is included on the servers so that clients detect
-an unreachable server and reset the TLS session. The option's default
-is doubled to 2 minutes out of respect for frontier service
-interruptions.
-
openvpn-keepalive
keepalive 10 120
-
--As mentioned in The Name Service, the institute uses a campus name -server. OpenVPN is instructed to push its address and the campus -search domain. -
- -openvpn-dns
push "dhcp-option DOMAIN {{ domain_priv }}"
-push "dhcp-option DNS {{ core_addr }}"
-
-
-The institute does not put the OpenVPN server in a chroot
jail, but
-it does drop privileges to run as user nobody:nobody
. The
-persist-
options are needed because nobody
cannot open the tunnel
-device nor the key files.
-
openvpn-drop-priv
user nobody
-group nogroup
-persist-key
-persist-tun
-
-
-The institute does a little additional hardening, sacrificing some
-compatibility with out-of-date clients. Such clients are generally
-frowned upon at the institute. Here cipher
is set to AES-256-GCM
,
-the default for OpenVPN v2.4, and auth
is upped to SHA256
from
-SHA1
.
-
openvpn-crypt
cipher AES-256-GCM
-auth SHA256
-
-
-Finally, a max-client
limit was chosen to frustrate flooding while
-accommodating a few members with a handful of devices each.
-
openvpn-max
max-clients 20
-
-
-The institute's servers are lightly loaded so a few debugging options
-are appropriate. To help recognize host addresses in the logs, and
-support direct client-to-client communication, host IP addresses are
-made "persistent" in the ipp.txt
file. The server's status is
-periodically written to the openvpn-status.log
and verbosity is
-raised from the default level 1 to level 3 (just short of a deluge).
-
openvpn-debug
ifconfig-pool-persist ipp.txt
-status openvpn-status.log
-verb 3
-
-
A small institute has just a handful of members. For simplicity (and
thus security) static configuration files are preferred over complex
account management systems, LDAP, Active Directory, and the like. The
Ansible scripts configure the same set of user accounts on Core and
-Front. The Institute Commands (e.g. ./inst new dick
) capture the
+Front. The Institute Commands (e.g. ./inst new dick
) capture the
processes of enrolling, modifying and retiring members of the
institute. They update the administrator's membership roll, and run
Ansible to create (and disable) accounts on Core, Front, Nextcloud,
@@ -591,9 +476,9 @@ accomplished via the campus cloud and the resulting desktop files can
all be private (readable and writable only by the owner) by default.
The institute avoids the use of the root
account (uid 0
) because
it is exempt from the normal Unix permissions checking. The sudo
@@ -601,22 +486,22 @@ command is used to consciously (conscientiously!) run specific scripts
and programs as root
. When installation of a Debian OS leaves the
host with no user accounts, just the root
account, the next step is
to create a system administrator's account named sysadm
and to give
-it permission to use the sudo
command (e.g. as described in The
+it permission to use the sudo
command (e.g. as described in The
Front Machine). When installation prompts for the name of an
initial, privileged user account the same name is given (e.g. as
-described in The Core Machine). Installation may not prompt and
+described in The Core Machine). Installation may not prompt and
still create an initial user account with a distribution specific name
(e.g. pi
). Any name can be used as long as it is provided as the
value of ansible_user
in hosts
. Its password is specified by a
vault-encrypted variable in the Secret/become.yml
file. (The
-hosts
and Secret/become.yml
files are described in The Ansible
+hosts
and Secret/become.yml
files are described in The Ansible
Configuration.)
The institute's Core uses a special account named monkey
to run
background jobs with limited privileges. One of those jobs is to keep
@@ -626,9 +511,9 @@ account is created on Front as well.
The institute keeps its "master secrets" in an encrypted volume on an off-line hard drive, e.g. a LUKS (Linux Unified Key @@ -658,36 +543,26 @@ Front's.)
-The institute uses a number of X.509 certificates to authenticate VPN
-clients and servers. They are created by the EasyRSA Certificate
-Authority stored in Secret/CA/
.
+The institute uses a couple X.509 certificates to authenticate
+servers. They are created by the EasyRSA Certificate Authority stored
+in Secret/CA/
.
Secret/CA/pki/ca.crt
Secret/CA/pki/issued/small.example.org.crt
Secret/CA/pki/issued/gate.small.private.crt
Secret/CA/pki/issued/small.example.org.crt
Secret/CA/pki/issued/core.small.private.crt
Secret/CA/pki/issued/core.crt
-The ./inst client
command creates client certificates and keys, and
-can generate OpenVPN configuration (.ovpn
) files for Android and
-Debian. The command updates the institute membership roll, requiring
-the member's username, keeping a list of the member's clients (in case
-all authorizations need to be revoked quickly). The list of client
-certificates that have been revoked is stored along with the
-membership roll (in private/members.yml
as the value of revoked
).
+The ./inst client
command updates the institute membership roll,
+which lists members and their clients' public keys, and is stored in
+private/members.yml
.
@@ -705,8 +580,8 @@ e.g. root@core.small.private
.
The institute administrator updates a couple encrypted copies of this -drive after enrolling new members, changing a password, issuing VPN -credentials, etc. +drive after enrolling new members, changing a password, +(de)authorizing a VPN client, etc.
@@ -722,9 +597,9 @@ the administrator's password keep, to install a new SSH key.
The small institute backs up its data, but not so much so that nothing
can be deleted. It actually mirrors user directories (/home/
), the
@@ -759,7 +634,7 @@ files mentioned in the Nextcloud database dump).
private/backup
#!/bin/bash -e
+private/backup
#!/bin/bash -e
#
# DO NOT EDIT. Maintained (will be replaced) by Ansible.
#
@@ -861,8 +736,8 @@ finish
This chapter introduces Ansible variables intended to simplify
@@ -874,13 +749,13 @@ stored in separate files: public/vars.yml
a
The example settings in this document configure VirtualBox VMs as -described in the Testing chapter. For more information about how a +described in the Testing chapter. For more information about how a small institute turns the example Ansible code into a working Ansible -configuration, see chapter The Ansible Configuration. +configuration, see chapter The Ansible Configuration.
The small institute's domain name is used quite frequently in the @@ -919,22 +794,22 @@ domain_priv: small.private
-The small institute uses a private Ethernet, two VPNs, and an
-untrusted Ethernet (for the campus Wi-Fi access point). Each must
-have a unique private network address. Hosts using the VPNs are also
-using foreign private networks, e.g. a notebook on a hotel Wi-Fi. To
-better the chances that all of these networks get unique addresses,
-the small institute uses addresses in the IANA's (Internet Assigned
-Numbers Authority's) private network address ranges except the
-192.168
address range already in widespread use. This still leaves
-69,632 8 bit networks (each addressing up to 254 hosts) from which to
-choose. The following table lists their CIDRs (subnet numbers in
-Classless Inter-Domain Routing notation) in abbreviated form (eliding
-69,624 rows).
+The small institute uses a private Ethernet, two VPNs, and a "wild",
+untrusted Ethernet for the campus Wi-Fi access point(s) and wired IoT
+appliances. Each must have a unique private network address. Hosts
+using the VPNs are also using foreign private networks, e.g. a
+notebook on a hotel Wi-Fi. To better the chances that all of these
+networks get unique addresses, the small institute uses addresses in
+the IANA's (Internet Assigned Numbers Authority's) private network
+address ranges except the 192.168
address range already in
+widespread use. This still leaves 69,632 8 bit networks (each
+addressing up to 254 hosts) from which to choose. The following table
+lists their CIDRs (subnet numbers in Classless Inter-Domain Routing
+notation) in abbreviated form (eliding 69,624 rows).
Test Host | +WireGuard⢠Private Key | +
---|---|
front |
+AJkzVxfTm/KvRjzTN/9X2jYy+CAugiwZfN5F3JTegms= | +
gate |
+yOBdLbXh6KBwYQvvb5mhiku8Fxkqc5Cdyz6gNgjc/2U= | +
core |
+AI+KhwnsHzSPqyIyAObx7EBBTBXFZPiXb2/Qcts8zEI= | +
thing |
+KIwQT5eGOl9w1qOa5I+2xx5kJH3z4xdpmirS/eGdsXY= | +
dick |
+WAhrlGccPf/BaFS5bRtBE4hEyt3kDxCavmwZfVTsfGs= | +
dicks-phone |
+oG/Kou9HOBCBwHAZGypPA1cZWUL6nR6WoxBiXc/OQWQ= | +
dicks-razr |
+IGNcF0VpkIBcJQAcLZ9jgRmk0SYyUr/WwSNXZoXXUWQ= | +
The next code block implements the CA
sub-command, which creates a
@@ -6859,14 +6683,8 @@ config.
my $dom = $domain_name;
my $pvt = $domain_priv;
mysystem "cd Secret/CA; ./easyrsa build-server-full $dom nopass";
- mysystem "cd Secret/CA; ./easyrsa build-server-full gate.$pvt nopass";
mysystem "cd Secret/CA; ./easyrsa build-server-full core.$pvt nopass";
- mysystem "cd Secret/CA; ./easyrsa build-client-full core nopass";
umask 077;
- mysystem "openvpn --genkey secret Secret/front-shared.key";
- mysystem "openvpn --genkey secret Secret/gate-shared.key";
- mysystem "openssl dhparam -out Secret/front-dh2048.pem 2048";
- mysystem "openssl dhparam -out Secret/gate-dh2048.pem 2048";
mysystem "mkdir --mode=700 Secret/root.gnupg";
mysystem ("gpg --homedir Secret/root.gnupg",
@@ -6902,13 +6720,13 @@ config.
The next code block implements the config
sub-command, which
provisions network services by running the site.yml
playbook
-described in playbooks/site.yml
. It recognizes an optional -n
+described in playbooks/site.yml
. It recognizes an optional -n
flag indicating that the service configurations should just be
checked. Given an optional host name, it provisions (or checks) just
the named host.
@@ -6953,12 +6771,12 @@ Example command lines:
For general information about members and their Unix accounts, see
-Accounts. The account management sub-commands maintain a mapping
+Accounts. The account management sub-commands maintain a mapping
associating member "usernames" (Unix account names) with their
records. The mapping is stored among other things in
private/members.yml
as the value associated with the key members
.
@@ -6969,17 +6787,19 @@ A new member's record in the members
mapping will have the st
key value
current
. That key gets value former
when the member
leaves.3 Access by former members is revoked by invalidating the
Unix account passwords, removing any authorized SSH keys from Front
-and Core, and disabling their VPN certificates.
+and Core, and removing their public keys from the WireGuardâ¢
+configurations.
The example file (below) contains a membership roll with one
-membership record, for an account named dick
, which was issued
-client certificates for devices named dick-note
, dick-phone
and
-dick-razr
. dick-phone
appears to be lost because its certificate
-was revoked. Dick's membership record includes a vault-encrypted
-password (for Fetchmail) and the two password hashes installed on
-Front and Core. (The example hashes are truncated versions.)
+membership record, for an account named dick
, which registered the
+public keys of devices named dick
, dicks-phone
and dicks-razr
.
+dicks-razr
is presumably a replacement for dicks-phone
, which was
+lost and its key invalidated. Lastly, Dick's membership record
+includes a vault-encrypted password (for Fetchmail) and the two
+password hashes installed on Front and Core. (The example hashes are
+truncated versions.)
private/members-empty.yml
---
members:
usernames: []
-revoked: []
+clients: []
inst
-sub print_member ($$) {
- my ($out, $member) = @_;
- print $out " ", $member->{"username"}, ":\n";
- print $out " username: ", $member->{"username"}, "\n";
+sub print_member ($$$) {
+ my ($out, $username, $member) = @_;
+ print $out " ", $username, ":\n";
print $out " status: ", $member->{"status"}, "\n";
if (@{$member->{"clients"} || []}) {
print $out " clients:\n";
@@ -7142,7 +6961,7 @@ each record.
print $out " $line\n";
}
}
- my @standard_keys = ( "username", "status", "clients",
+ my @standard_keys = ( "status", "clients",
"password_front", "password_core",
"password_fetchmail" );
my @other_keys = (sort
@@ -7157,8 +6976,8 @@ each record.
The next code block implements the new
sub-command. It adds a new
@@ -7192,14 +7011,11 @@ initial, generated password.
mysystem ("ansible-playbook -e \@Secret/become.yml",
" playbooks/nextcloud-new.yml",
" -e user=$user", " -e pass=\"$epass\"");
- $members->{$user} = { "username" => $user,
- "status" => "current",
+ $members->{$user} = { "status" => "current",
"password_front" => $front,
"password_core" => $core,
"password_fetchmail" => $vault };
- write_members_yaml
- { "members" => $members,
- "revoked" => $yaml->{"revoked"} };
+ write_members_yaml $yaml;
mysystem ("ansible-playbook -e \@Secret/become.yml",
" -t accounts -l core,front playbooks/site.yml");
exit;
@@ -7262,8 +7078,8 @@ initial, generated password.
The institute's passwd
command on Core securely emails root
with a
@@ -7277,8 +7093,8 @@ Ansible site.yml
playbook to update the
message is sent to member@core
.
The next code block implements the less aggressive passwd
command.
@@ -7374,8 +7190,8 @@ print "
The following code block implements the ./inst pass
command, used by
@@ -7472,8 +7288,8 @@ users:resetpassword command using expect(1)
.
The following Ansible tasks install the less aggressive passwd
@@ -7541,11 +7357,11 @@ configuration so that the email to root can be encrypted.
-The old
command disables a member's accounts and clients.
+The old
command disables a member's account (and thus their clients).
old
command disables a member's accounts and clients.
mysystem ("ansible-playbook -e \@Secret/become.yml",
"playbooks/nextcloud-old.yml -e user=$user");
$member->{"status"} = "former";
- write_members_yaml { "members" => $members,
- "revoked" => [ sort @{$member->{"clients"}},
- @{$yaml->{"revoked"}} ] };
+ write_members_yaml $yaml;
mysystem ("ansible-playbook -e \@Secret/become.yml",
"-t accounts playbooks/site.yml");
exit;
@@ -7587,174 +7401,246 @@ The old
command disables a member's accounts and clients.
-The client
command creates an OpenVPN configuration (.ovpn
) file
-authorizing wireless devices to connect to the institute's VPNs. The
-command uses the EasyRSA CA in Secret/
. The generated configuration
-is slightly different depending on the type of host, given as the
-first argument to the command.
+The client
command registers the public key of a client wishing to
+connect to the institute's WireGuard⢠subnets. The command allocates
+a host number, associates it with the provided public key, and updates
+the configuration files front-wg0.conf
and gate-wg0.conf
. These
+are distributed to the servers, which are then reset. Thereafter the
+servers recognize the new peer (and drop packets from any "peer" that
+is no longer authorized).
./inst client android NEW USER
android
host runs OpenVPN for Android or work-alike. Two files
-are generated. campus.ovpnconfigures a campus VPN connection, -and
public.ovpnconfigures a connection to the institute's public -VPN.
./inst client debian NEW USER
debian
host runs a Debian desktop with Network Manager. Again
-two files are generated, for the campus and public VPNs../inst client campus NEW
campus
host is a Debian host (with or without desktop) that is
-used by the institute generally, is not the property of a member,
-never roams off campus, and so is remotely administered with
-Ansible. One file is generated, campus.ovpn.
-The administrator uses encrypted email to send .ovpn
files to new
-members. New members install the network-manager-openvpn-gnome
and
-openvpn-systemd-resolved
packages, and import the .ovpn
files into
-Network Manager on their desktops. The .ovpn
files for an
-Android device are transferred by USB stick and should automatically
-install when "opened". On campus hosts, the system administrator
-copies the campus.ovpn
file to /etc/openvpn/campus.conf
.
+The client
command also generates template WireGuard⢠configuration
+files for the client. They contain the necessary parameters except
+the client's PrivateKey
, which in most cases should be found in the
+local /etc/wireguard/private-key
, not in the configuration files.
+Private keys (and corresponding public keys) should be generated on
+the client (i.e. by the WireGuard for Android⢠app) and never revealed
+(i.e. sent in email, copied to a network drive, etc.).
-The OpenVPN configurations generated for Debian hosts specify an up
-script, update-systemd-resolved
, installed in /etc/openvpn/
by the
-openvpn-systemd-resolved
package. The following configuration lines
-instruct the OpenVPN clients to run this script whenever the
-connection is restarted.
+The generated configuration vary depending on the type of client,
+which must be given as the first argument to the command. For most
+types, two configuration files are generated. campus.conf
contains
+the client's campus VPN configuration, and public.conf
the client's
+public VPN configuration.
openvpn-up
script-security 2
-up /etc/openvpn/update-systemd-resolved
-up-restart
-
-./inst client android NAME USER PUBKEY
android
client runs WireGuard for Android⢠or work-alike../inst client debian NAME USER PUBKEY
debian
client runs a Debian/Linux desktop with Network Manager
+(though wg-quick
is currently used)../inst client campus NAME PUBKEY
campus
client is an institute machine (with or without desktop)
+that is used by the institute generally, is not the property of a
+member, never roams off campus, and so is remotely administered with
+Ansible. Just one configuration file is generated: campus.conf.
+The administrator emails the template .conf
files to new members.
+(They contain no secrets.) The members will have already installed
+the wireguard
package in order to run the wg genkey
and wg
+pubkey
commands. After receiving the .conf
templates, they paste
+in their private keys and install the resulting files in
+e.g. /etc/wireguard/wg0.conf
and wg1.conf
. To connect, members
+run a command like systemctl start wg-quick@wg0
. (There may be
+better support in Network Manager soon.)
+
inst
sub write_template ($$$$$$$$$);
-sub read_file ($);
-sub add_client ($$$);
+inst
sub write_wg_server ($$$$$);
+sub write_wg_client ($$$$$$);
+sub hostnum_to_ipaddr ($$);
+sub hostnum_to_ipaddr_cidr ($$);
if (defined $ARGV[0] && $ARGV[0] eq "client") {
- die "Secret/CA/easyrsa: not found\n" if ! -x "Secret/CA/easyrsa";
my $type = $ARGV[1]||"";
my $name = $ARGV[2]||"";
my $user = $ARGV[3]||"";
- if ($type eq "campus") {
- die "usage: $0 client campus NAME\n" if @ARGV != 3;
+ my $pubkey = $ARGV[4]||"";
+ if ($type eq "android" || $type eq "debian") {
+ die "usage: $0 client $type NAME USER PUBKEY\n" if @ARGV != 5;
die "$name: invalid host name\n" if $name !~ /^[a-z][-a-z0-9]+$/;
- } elsif ($type eq "android" || $type eq "debian") {
- die "usage: $0 client $type NAME USER\n" if @ARGV != 4;
+ } elsif ($type eq "campus") {
+ die "usage: $0 client campus NAME PUBKEY\n" if @ARGV != 4;
die "$name: invalid host name\n" if $name !~ /^[a-z][-a-z0-9]+$/;
+ $pubkey = $user;
+ $user = "";
} else {
- die "usage: $0 client [debian|android|campus]\n" if @ARGV != 4;
+ die "usage: $0 client [debian|android|campus]\n";
}
my $yaml;
- my $member;
- if ($type ne "campus") {
- $yaml = read_members_yaml;
- my $members = $yaml->{"members"};
- if (@ARGV == 4) {
- $member = $members->{$user};
- die "$user: does not exist\n" if ! defined $member;
- }
- if (defined $member) {
- my ($owner) = grep { grep { $_ eq $name } @{$_->{"clients"}} }
- values %{$members};
- die "$name: owned by $owner->{username}\n"
- if defined $owner && $owner->{username} ne $member->{username};
- }
- }
+ $yaml = read_members_yaml;
+ my $members = $yaml->{"members"};
+ my $member = $members->{$user};
+ die "$user: does not exist\n"
+ if !defined $member && $type ne "campus";
- die "Secret/CA: no certificate authority found"
- if ! -d "Secret/CA/pki/issued";
+ my @campus_peers # [ name, hostnum, type, pubkey, user|"" ]
+ = map { [ (split / /), "" ] } @{$yaml->{"clients"}};
- if (! -f "Secret/CA/pki/issued/$name.crt") {
- mysystem "cd Secret/CA; ./easyrsa build-client-full $name nopass";
- } else {
- print "Using existing key/cert...\n";
+ my @member_peers = ();
+ for my $u (sort keys %$members) {
+ push @member_peers,
+ map { [ (split / /), $u ] } @{$members->{$u}->{"clients"}};
}
- if ($type ne "campus") {
- my $clients = $member->{"clients"};
- if (! grep { $_ eq $name } @$clients) {
- $member->{"clients"} = [ $name, @$clients ];
- write_members_yaml $yaml;
- }
+ my @all_peers = sort { $a->[1] <=> $b->[1] }
+ (@campus_peers, @member_peers);
+
+ for my $p (@all_peers) {
+ my ($n, $h, $t, $k, $u) = @$p;
+ die "$n: name already in use by $u\n"
+ if $name eq $n && $u ne "";
+ die "$n: name already in use on campus\n"
+ if $name eq $n && $u eq "";
}
+ my $hostnum = (@all_peers
+ ? 1 + $all_peers[$#all_peers][1]
+ : 3);
+
+ push @{$type eq "campus"
+ ? $yaml->{"clients"}
+ : $member->{"clients"}},
+ "$name $hostnum $type $pubkey";
+
umask 077;
- my $DEV = $type eq "android" ? "tun" : "ovpn";
- my $CA = read_file "Secret/CA/pki/ca.crt";
- my $CRT = read_file "Secret/CA/pki/issued/$name.crt";
- my $KEY = read_file "Secret/CA/pki/private/$name.key";
- my $UP = $type eq "android" ? "" : "
-<<openvpn-up>>";
-
- if ($type ne "campus") {
- my $TC = read_file "Secret/front-shared.key";
- write_template ($DEV,$UP,$CA,$CRT,$KEY,$TC, $front_addr,
- $domain_name, "public.ovpn");
- print "Wrote public VPN configuration to public.ovpn.\n";
+ write_members_yaml $yaml;
+
+ if ($type eq "campus") {
+ push @all_peers, [ $name, $hostnum, $type, $pubkey, "" ];
+ } else {
+ push @member_peers, [ $name, $hostnum, $type, $pubkey, $user ];
+ push @all_peers, [ $name, $hostnum, $type, $pubkey, $user ];
}
- my $TC = read_file "Secret/gate-shared.key";
- write_template ($DEV,$UP,$CA,$CRT,$KEY,$TC, $gate_wild_addr,
- "gate.$domain_priv", "campus.ovpn");
- print "Wrote campus VPN configuration to campus.ovpn.\n";
- exit;
+ my $core_wg_addr = hostnum_to_ipaddr (2, $public_wg_net_cidr);
+ my $extra_front_config = "
+PostUp = resolvectl dns %i $core_addr
+PostUp = resolvectl domain %i $domain_priv
+
+# Core
+[Peer]
+PublicKey = $core_wg_pubkey
+AllowedIPs = $core_wg_addr
+AllowedIPs = $private_net_cidr
+AllowedIPs = $campus_wg_net_cidr\n";
+
+ write_wg_server ("private/front-wg0.conf", \@member_peers,
+ hostnum_to_ipaddr_cidr (1, $public_wg_net_cidr),
+ $public_wg_port, $extra_front_config)
+ if $type ne "campus";
+ write_wg_server ("private/gate-wg0.conf", \@all_peers,
+ hostnum_to_ipaddr_cidr (1, $campus_wg_net_cidr),
+ $campus_wg_port, "\n");
+
+ write_wg_client ("public.conf",
+ hostnum_to_ipaddr ($hostnum, $public_wg_net_cidr),
+ $type,
+ $front_wg_pubkey,
+ "$front_addr:$public_wg_port",
+ hostnum_to_ipaddr (1, $public_wg_net_cidr))
+ if $type ne "campus";
+ write_wg_client ("campus.conf",
+ hostnum_to_ipaddr ($hostnum, $campus_wg_net_cidr),
+ $type,
+ $gate_wg_pubkey,
+ "$gate_wild_addr:$campus_wg_port",
+ hostnum_to_ipaddr (1, $campus_wg_net_cidr));
}
-sub write_template ($$$$$$$$$) {
- my ($DEV,$UP,$CA,$CRT,$KEY,$TC,$ADDR,$NAME,$FILE) = @_;
+sub write_wg_server ($$$$$) {
+ my ($file, $peers, $addr_cidr, $port, $extra) = @_;
my $O = new IO::File;
- open ($O, ">$FILE.tmp") or die "Could not open $FILE.tmp: $!\n";
- print $O "client
-dev-type tun
-dev $DEV
-remote $ADDR
-nobind
-<<openvpn-drop-priv>>
-remote-cert-tls server
-verify-x509-name $NAME name
-<<openvpn-crypt>>$UP
-verb 3
-key-direction 1
-<ca>\n$CA</ca>
-<cert>\n$CRT</cert>
-<key>\n$KEY</key>
-<tls-crypt>\n$TC</tls-crypt>\n";
- close $O or die "Could not close $FILE.tmp: $!\n";
- rename ("$FILE.tmp", $FILE)
- or die "Could not rename $FILE.tmp: $!\n";
+ open ($O, ">$file.tmp") or die "Could not open $file.tmp: $!\n";
+ print $O "[Interface]
+Address = $addr_cidr
+ListenPort = $port
+PostUp = wg set %i private-key /etc/wireguard/private-key$extra";
+ for my $p (@$peers) {
+ my ($n, $h, $t, $k, $u) = @$p;
+ next if $k =~ /^-/;
+ my $ip = hostnum_to_ipaddr ($h, $addr_cidr);
+ print $O "
+# $n
+[Peer]
+PublicKey = $k
+AllowedIPs = $ip\n";
+ }
+ close $O or die "Could not close $file.tmp: $!\n";
+ rename ("$file.tmp", $file)
+ or die "Could not rename $file.tmp: $!\n";
}
-sub read_file ($) {
- my ($path) = @_;
- my $I = new IO::File;
- open ($I, "<$path") or die "$path: could not read: $!\n";
- local $/;
- my $c = <$I>;
- close $I or die "$path: could not close: $!\n";
- return $c;
+sub write_wg_client ($$$$$$) {
+ my ($file, $addr, $type, $pubkey, $endpt, $server_addr) = @_;
+ my $O = new IO::File;
+ my $DNS = ($type eq "android"
+ ? "
+DNS=$core_addr\nDomain=$domain_priv"
+ : "
+PostUp = resolvectl dns %i $core_addr
+PostUp = resolvectl domain %i $domain_priv");
+ open ($O, ">$file.tmp") or die "Could not open $file.tmp: $!\n";
+ print $O "[Interface]
+Address = $addr
+PostUp = wg set %i private-key /etc/wireguard/private-key$DNS
+
+[Peer]
+PublicKey = $pubkey
+EndPoint = $endpt
+AllowedIPs = $server_addr
+AllowedIPs = $private_net_cidr
+AllowedIPs = $public_wg_net_cidr
+AllowedIPs = $campus_wg_net_cidr\n";
+ close $O or die "Could not close $file.tmp: $!\n";
+ rename ("$file.tmp", $file)
+ or die "Could not rename $file.tmp: $!\n";
+
+ exit;
}
+
+sub hostnum_to_ipaddr ($$)
+{
+ my ($hostnum, $net_cidr) = @_;
+
+ # Assume 24bit subnet, 8bit hostnum.
+ # Find a Perl library for more generality?
+ die "$hostnum: hostnum too large\n" if $hostnum > 255;
+ my ($prefix) = $net_cidr =~ m"^(\d+\.\d+\.\d+)\.\d+/24$";
+ die if !$prefix;
+ return "$prefix.$hostnum";
+}
+
+sub hostnum_to_ipaddr_cidr ($$)
+{
+ my ($hostnum, $net_cidr) = @_;
+
+ # Assume 24bit subnet, 8bit hostnum.
+ # Find a Perl library for more generality?
+ die "$hostnum: hostnum too large\n" if $hostnum > 255;
+ my ($prefix) = $net_cidr =~ m"^(\d+\.\d+\.\d+)\.\d+/24$";
+ die if !$prefix;
+ return "$prefix.$hostnum/24";
+}
This should be the last block tangled into the inst
script. It
@@ -7770,8 +7656,8 @@ above.
The example files in this document, ansible.cfg
and hosts
as well
@@ -7790,7 +7676,7 @@ simulation is the VirtualBox host.
The next two sections list the steps taken to create the simulated Core, Gate and Front machines, and connect them to their networks. -The process is similar to that described in The (Actual) Hardware, but +The process is similar to that described in The (Actual) Hardware, but is covered in detail here where the VirtualBox hypervisor can be assumed and exact command lines can be given (and copied during re-testing). The remaining sections describe the manual testing @@ -7806,8 +7692,8 @@ HTML version of the latest revision can be found on the official web site at https://www.virtualbox.org/manual/UserManual.html.
The networks used in the test:
@@ -7871,15 +7757,15 @@ on the private 192.168.15.0/24
network.
The virtual machines are created by VBoxManage
command lines in the
following sub-sections. They each start with a recent Debian release
(e.g. debian-12.5.0-amd64-netinst.iso
) in their simulated DVD
-drives. As in The Hardware preparation process being simulated, a few
-additional software packages are installed. Unlike in The Hardware
+drives. As in The Hardware preparation process being simulated, a few
+additional software packages are installed. Unlike in The Hardware
preparation, machines are moved to their final networks and then
remote access is authorized. (They are not accessible via ssh
on
the VirtualBox NAT network where they first boot.)
@@ -7891,8 +7777,8 @@ privileged accounts on the virtual machines, they are prepared for
configuration by Ansible.
The following shell function contains most of the VBoxManage
@@ -8023,8 +7909,8 @@ preparation (below).
The front
machine is created with 512MiB of RAM, 4GiB of disk, and
@@ -8037,7 +7923,7 @@ After Debian is installed (as detailed above) front
is shut down an
its primary network interface moved to the simulated Internet, the NAT
network premises
. front
also gets a second network interface, on
the host-only network vboxnet1
, to make it directly accessible to
-the administrator's notebook (as described in The Test Networks).
+the administrator's notebook (as described in The Test Networks).
front
, which is never
deployed on a frontier, always in the cloud. Additional Debian
packages are assumed to be readily available. Thus Ansible installs
them as necessary, but first the administrator authorizes remote
-access by following the instructions in the final section: Ansible
+access by following the instructions in the final section: Ansible
Test Authorization.
The gate
machine is created with the same amount of RAM and disk as
@@ -8090,14 +7976,14 @@ create_vm
-After Debian is installed (as detailed in A Test Machine) and the +After Debian is installed (as detailed in A Test Machine) and the machine rebooted, the administrator logs in and installs several additional software packages.
sudo apt install netplan.io systemd-resolved unattended-upgrades \
- ufw isc-dhcp-server postfix openvpn
+ ufw isc-dhcp-server postfix wireguard
Finally, the administrator authorizes remote access by following the -instructions in the final section: Ansible Test Authorization. +instructions in the final section: Ansible Test Authorization.
The core
machine is created with 1GiB of RAM and 6GiB of disk.
@@ -8213,14 +8099,14 @@ create_vm
-After Debian is installed (as detailed in A Test Machine) and the +After Debian is installed (as detailed in A Test Machine) and the machine rebooted, the administrator logs in and installs several additional software packages.
sudo apt install netplan.io systemd-resolved unattended-upgrades \
- ntp isc-dhcp-server bind9 apache2 openvpn \
+ ntp isc-dhcp-server bind9 apache2 wireguard \
postfix dovecot-imapd fetchmail expect rsync \
gnupg
sudo apt install mariadb-server php php-{apcu,bcmath,curl,gd,gmp}\
@@ -8279,12 +8165,12 @@ Netplan soon.)
Finally, the administrator authorizes remote access by following the
-instructions in the next section: Ansible Test Authorization.
+instructions in the next section: Ansible Test Authorization.
To authorize Ansible's access to the three test machines, they must @@ -8350,8 +8236,8 @@ ssh-keygen -f ~/.ssh/known_hosts -R 192.168.57.3
At this point the three test machines core
, gate
, and front
are
@@ -8369,8 +8255,8 @@ not.
At this point the test institute is just core
, gate
and front
,
@@ -8432,12 +8318,12 @@ instant attention).
Further tests involve Nextcloud account management. Nextcloud is
-installed on core
as described in Configure Nextcloud. Once
+installed on core
as described in Configure Nextcloud. Once
/Nextcloud/
is created, ./inst config core
will validate
or update its configuration files.
The process starts with enrolling the first member of the institute
-using the ./inst new
command and issuing client VPN keys with the
-./inst client
command.
+using the ./inst new
command and registering a client's public key
+with the ./inst client
command.
A member must be enrolled so that a member's client machine can be @@ -8480,14 +8366,14 @@ Take note of Dick's initial password.
A test member's notebook is created next, much like the servers,
except with memory and disk space doubled to 2GiB and 8GiB, and a
desktop. This machine is not configured by Ansible. Rather, its
-desktop VPN client and web browser test the OpenVPN configurations on
+WireGuard⢠tunnels and web browser test the VPN configurations on
gate
and front
, and the Nextcloud installation on core
.
-Debian is installed much as detailed in A Test Machine except that +Debian is installed much as detailed in A Test Machine except that the SSH server option is not needed and the GNOME desktop option is. When the machine reboots, the administrator logs into the desktop and installs a couple additional software packages (which @@ -8517,61 +8403,77 @@ require several more).
sudo apt install network-manager-openvpn-gnome \
- openvpn-systemd-resolved \
- nextcloud-desktop evolution
+sudo apt install wireguard nextcloud-desktop evolution
-The ./inst client
command is used to issue keys for the institute's
-VPNs. The following command generates two .ovpn
(OpenVPN
-configuration) files, small.ovpn
and campus.ovpn
, authorizing
-access by the holder, identified as dick
, owned by member dick
, to
-the test VPNs.
+The ./inst client
command is used to register the public key of a
+client wishing to connect to the institute's VPNs. In this test, new
+member Dick wants to connect his notebook, dick
, to the institute
+VPNs. First he generates a pair of WireGuard⢠keys by running the
+following commands on Dick's notebook.
./inst client debian dick dick
+( umask 077; wg genkey >private)
+wg pubkey <private >public
+
+
+The administrator uses the key in public
to run the following
+command, generating campus.conf
and public.conf
files.
+
./inst client debian dick dick \
+ 4qd4xdRztZBKhFrX9jI/b4fnMzpKQ5qhg691hwYSsX8=
-The campus.ovpn
OpenVPN configuration file (generated in Test Client
-Command) is transferred to dick
, which is at the Wi-Fi access
-point's wifi_wan_addr
.
+The campus.conf
WireGuard⢠configuration file (generated in Test
+Client Command) is transferred to dick
, which is at the Wi-Fi access
+point's IP address, host 2 on the wild Ethernet.
scp *.ovpn sysadm@192.168.57.2:
+scp *.conf sysadm@192.168.57.2:
-The file is installed using the Network tab of the desktop Settings
-app. The administrator uses the "+" button, chooses "Import from
-file…" and the campus.ovpn
file. Importantly the administrator
-checks the "Use this connection only for resources on its network"
-checkbox in the IPv4 tab of the Add VPN dialog. The admin does the
-same with the small.ovpn
file, for use on the simulated Internet.
+Dick then pastes his notebook's private key into the template
+campus.conf
file and installs the result in
+/etc/wireguard/wg0.conf
, doing the same to complete public.conf
+and install it in /etc/wireguard/wg1.conf
.
-The administrator turns on the campus VPN on dick
(which connects
-instantly) and does a few basic tests in a terminal.
+To connect to the campus VPN, the following command is run.
+
systemctl start wg-quick@wg0
+
++A few basic tests are then performed in a terminal.
systemctl status
-ping -c 1 8.8.4.4 # dns.google
+ping -c 1 8.8.8.8 # dns.google
ping -c 1 192.168.56.1 # core
host dns.google
host core.small.private
@@ -8580,8 +8482,8 @@ host www
Next, the administrator copies Backup/WWW/
(included in the
@@ -8620,8 +8522,8 @@ will warn but allow the luser to continue.
Modify /WWW/live/index.html
on core
and wait 15 minutes for it to
@@ -8635,8 +8537,8 @@ Hack /home/www/index.html
on front
and observe the result at
Nextcloud is typically installed and configured after the first
@@ -8644,9 +8546,9 @@ Ansible run, when core
has Internet access via gate
.
installation directory /Nextcloud/nextcloud/
appears, the Ansible
code skips parts of the Nextcloud configuration. The same
installation (or restoration) process used on Core is used on core
-to create /Nextcloud/
. The process starts with Create
-/Nextcloud/
, involves Restore Nextcloud or Install Nextcloud,
-and runs ./inst config core
again 8.23.6. When the ./inst
+to create
/Nextcloud/
. The process starts with Create
+/Nextcloud/
, involves Restore Nextcloud or Install Nextcloud,
+and runs ./inst config core
again 8.23.6. When the ./inst
config core
command is happy with the Nextcloud configuration on
core
, the administrator uses Dick's notebook to test it, performing
the following tests on dick
's desktop.
@@ -8724,8 +8626,8 @@ the calendar.
With Evolution running on the member notebook dick
, one second email
@@ -8753,8 +8655,8 @@ Outgoing email is also tested. A message to
At this point, dick
can move abroad, from the campus Wi-Fi
@@ -8769,12 +8671,17 @@ machine does not need to be shut down.
-The administrator might wait to see evidence of the change in
-networks. Evolution may start "Testing reachability of mail account
-dick@small.example.org." Eventually, the campus
VPN should
-disconnect. After it does, the administrator turns on the small
-VPN, which connects in a second or two. Again, some basics are
-tested in a terminal.
+Then the campus VPN is disconnected and the public VPN connected.
+
systemctl stop wg-quick@wg0
+systemctl start wg-quick@wg1
+
++Again, some basics are tested in a terminal.
To test the ./inst pass
command, the administrator logs in to core
@@ -8855,8 +8762,8 @@ Finally, the administrator verifies that dick
can login on co
One more institute command is left to exercise. The administrator
@@ -8870,22 +8777,22 @@ retires dick
and his main device dick
.
The administrator tests Dick's access to core
, front
and
-Nextcloud, and attempts to re-connect the small
VPN. All of these
-should fail.
+Nextcloud, and attempts to access the campus VPN. All of these should
+fail.
The small institute's network, as currently defined in this doocument, is lacking in a number of respects.
The current network monitoring is rudimentary. It could use some @@ -8906,49 +8813,21 @@ units.
-The institute's private domain names (e.g. www.small.private
) are
-not resolvable on Front. Reverse domains (86.177.10.in-addr.arpa
)
-mapping institute network addresses back to names in the private
-domain small.private
work only on the campus Ethernet. These nits
-might be picked when OpenVPN supports the DHCP option
-rdnss-selection
(RFC6731), or with hard-coded resolvectl
commands.
-
-The ./inst old dick
command does not break VPN connections to Dick's
-clients. New connections cannot be created, but old connections can
-continue to work for some time.
-
-The ./inst client android dick-phone dick
command generates .ovpn
-files that require the member to remember to check the "Use this
-connection only for resources on its network" box in the IPv4 (and
-IPv6) tab(s) of the Add VPN dialog. The command should include an
-OpenVPN setting that the NetworkManager file importer recognizes as
-the desired setting.
-
-The VPN service is overly complex. The OpenVPN 2.4.7 clients allow
-multiple server addresses, but the openvpn(8)
manual page suggests
-per connection parameters are restricted to a set that does not
-include the essential verify-x509-name
. Use the same name on
-separate certificates for Gate and Front? Use the same certificate
-and key on Gate and Front?
+The institute's reverse domains (e.g. 86.177.10.in-addr.arpa
) are
+not available on Front, yet.
The testing process described in the previous chapter is far from complete. Additional tests are needed.
The backup
command has not been tested. It needs an encrypted
@@ -8957,8 +8836,8 @@ partition with which to sync? And then some way to compare that to
The restore process has not been tested. It might just copy Backup/
@@ -8968,8 +8847,8 @@ perhaps permissions too. It could also use an example
Email access (IMAPS) on front
is… difficult to test unless
@@ -8993,8 +8872,8 @@ could be used.
Creating the private network from whole cloth (machines with recent @@ -9014,11 +8893,11 @@ etc.: quite a bit of temporary, manual localnet configuration just to get to the additional packages.
-The strategy pursued in The Hardware is two phase: prepare the servers +The strategy pursued in The Hardware is two phase: prepare the servers on the Internet where additional packages are accessible, then connect them to the campus facilities (the private Ethernet switch, Wi-Fi AP, ISP), manually configure IP addresses (while the DHCP client silently @@ -9026,8 +8905,8 @@ fails), and avoid names until BIND9 is configured.
The strategy of Starting With Gate concentrates on configuring Gate's @@ -9071,8 +8950,8 @@ ansible-playbook -l core site.yml
A refinement of the current strategy might avoid the need to maintain @@ -9125,7 +9004,7 @@ routes on Front and Gate, making the simulation less… similar.