"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
-<!-- 2025-05-31 Sat 22:27 -->
+<!-- 2025-06-15 Sun 12:23 -->
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>A Small Institute</title>
secure and private campus that can function with or without the
Internet.
</p>
-<div id="outline-container-org12986b7" class="outline-2">
-<h2 id="org12986b7"><span class="section-number-2">1.</span> Overview</h2>
+<div id="outline-container-orga8e230f" class="outline-2">
+<h2 id="orga8e230f"><span class="section-number-2">1.</span> Overview</h2>
<div class="outline-text-2" id="text-1">
<p>
This small institute has a public server on the Internet, Front, that
members off campus.
</p>
-<pre class="example" id="org9ebcfa4">
+<pre class="example" id="orga1e7f29">
=
_|||_
=-The-Institute-=
VPN on Front (via hotel Wi-Fi). When <i>on</i> 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.
</p>
</p>
</div>
</div>
-<div id="outline-container-org68fcf87" class="outline-2">
-<h2 id="org68fcf87"><span class="section-number-2">2.</span> Caveats</h2>
+<div id="outline-container-org0a30205" class="outline-2">
+<h2 id="org0a30205"><span class="section-number-2">2.</span> Caveats</h2>
<div class="outline-text-2" id="text-2">
<p>
This small institute prizes its privacy, so there is little or no
</p>
</div>
</div>
-<div id="outline-container-org1120238" class="outline-2">
-<h2 id="org1120238"><span class="section-number-2">3.</span> The Services</h2>
+<div id="outline-container-orgb3ec65d" class="outline-2">
+<h2 id="orgb3ec65d"><span class="section-number-2">3.</span> The Services</h2>
<div class="outline-text-2" id="text-3">
<p>
The small institute's network is designed to provide a number of
reference particulars first introduced in the following chapter.
</p>
</div>
-<div id="outline-container-org189ccc3" class="outline-3">
-<h3 id="org189ccc3"><span class="section-number-3">3.1.</span> The Name Service</h3>
+<div id="outline-container-org7a5fce0" class="outline-3">
+<h3 id="org7a5fce0"><span class="section-number-3">3.1.</span> The Name Service</h3>
<div class="outline-text-3" id="text-3-1">
<p>
The institute has a public domain, e.g. <code>small.example.org</code>, and a
</p>
</div>
</div>
-<div id="outline-container-org73635f2" class="outline-3">
-<h3 id="org73635f2"><span class="section-number-3">3.2.</span> The Email Service</h3>
+<div id="outline-container-org474e9f9" class="outline-3">
+<h3 id="org474e9f9"><span class="section-number-3">3.2.</span> The Email Service</h3>
<div class="outline-text-3" id="text-3-2">
<p>
Front provides the public SMTP (Simple Mail Transfer Protocol) service
configurations wherever <code><<postfix-message-size>></code> appears.
</p>
</div>
-<div id="outline-container-org630ebd0" class="outline-4">
-<h4 id="org630ebd0"><span class="section-number-4">3.2.1.</span> The Postfix Configurations</h4>
+<div id="outline-container-org234f92e" class="outline-4">
+<h4 id="org234f92e"><span class="section-number-4">3.2.1.</span> The Postfix Configurations</h4>
<div class="outline-text-4" id="text-3-2-1">
<p>
The institute aims to accommodate encrypted email containing short
</p>
<div class="org-src-container">
-<code>postfix-message-size</code><pre class="src src-conf" id="org76e6614"><code>- { p: message_size_limit, v: 104857600 }
+<code>postfix-message-size</code><pre class="src src-conf" id="orgd79c252"><code>- { p: message_size_limit, v: 104857600 }
</code></pre>
</div>
</p>
<div class="org-src-container">
-<code>postfix-queue-times</code><pre class="src src-conf" id="orge1f6b9f"><code>- { p: delay_warning_time, v: 1h }
+<code>postfix-queue-times</code><pre class="src src-conf" id="org65c9d39"><code>- { p: delay_warning_time, v: 1h }
- { p: maximal_queue_lifetime, v: 4h }
- { p: bounce_queue_lifetime, v: 4h }
</code></pre>
</p>
<div class="org-src-container">
-<code>postfix-relaying</code><pre class="src src-conf" id="orgfc41c7f"><code>- p: smtpd_relay_restrictions
+<code>postfix-relaying</code><pre class="src src-conf" id="orga9d70d2"><code>- p: smtpd_relay_restrictions
v: permit_mynetworks reject_unauth_destination
</code></pre>
</div>
</p>
<div class="org-src-container">
-<code>postfix-maildir</code><pre class="src src-conf" id="org38b410e"><code>- { p: home_mailbox, v: Maildir/ }
+<code>postfix-maildir</code><pre class="src src-conf" id="org709078f"><code>- { p: home_mailbox, v: Maildir/ }
</code></pre>
</div>
</p>
</div>
</div>
-<div id="outline-container-org4c351b3" class="outline-4">
-<h4 id="org4c351b3"><span class="section-number-4">3.2.2.</span> The Dovecot Configurations</h4>
+<div id="outline-container-org9345774" class="outline-4">
+<h4 id="org9345774"><span class="section-number-4">3.2.2.</span> The Dovecot Configurations</h4>
<div class="outline-text-4" id="text-3-2-2">
<p>
The Dovecot settings on both Front and Core disable POP and require
</p>
<div class="org-src-container">
-<code>dovecot-tls</code><pre class="src src-conf" id="org9aa23d4"><code><span class="org-variable-name">protocols</span> = imap
+<code>dovecot-tls</code><pre class="src src-conf" id="orgfcc2fc9"><code><span class="org-variable-name">protocols</span> = imap
<span class="org-variable-name">ssl</span> = required
</code></pre>
</div>
</p>
<div class="org-src-container">
-<code>dovecot-ports</code><pre class="src src-conf" id="orgf5fbf4b"><code><span class="org-type">service imap-login</span> {
+<code>dovecot-ports</code><pre class="src src-conf" id="org19ccc28"><code><span class="org-type">service imap-login</span> {
<span class="org-type">inet_listener imap</span> {
<span class="org-variable-name">port</span> = 0
}
</p>
<div class="org-src-container">
-<code>dovecot-maildir</code><pre class="src src-conf" id="org4ab71c8"><code><span class="org-variable-name">mail_location</span> = maildir:~/Maildir
+<code>dovecot-maildir</code><pre class="src src-conf" id="org45ed273"><code><span class="org-variable-name">mail_location</span> = maildir:~/Maildir
</code></pre>
</div>
</div>
</div>
</div>
-<div id="outline-container-orgf659a90" class="outline-3">
-<h3 id="orgf659a90"><span class="section-number-3">3.3.</span> The Web Services</h3>
+<div id="outline-container-org4e9f4f9" class="outline-3">
+<h3 id="org4e9f4f9"><span class="section-number-3">3.3.</span> The Web Services</h3>
<div class="outline-text-3" id="text-3-3">
<p>
Front provides the public HTTP service that serves institute web pages
at e.g. <code>https://small.example.org/</code>. 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 <a href="#org1024107">The
+replaced by one signed by a recognized authority, as discussed in <a href="#orgd6c5c74">The
Front Role</a>.
</p>
</p>
</div>
</div>
-<div id="outline-container-orgd0834d2" class="outline-3">
-<h3 id="orgd0834d2"><span class="section-number-3">3.4.</span> The Cloud Service</h3>
+<div id="outline-container-org582454b" class="outline-3">
+<h3 id="org582454b"><span class="section-number-3">3.4.</span> The Cloud Service</h3>
<div class="outline-text-3" id="text-3-4">
<p>
Core runs Nextcloud to provide a private institute cloud at
<code>https://core.small.private/nextcloud/</code>. It is managed manually per
<a href="https://docs.nextcloud.com/server/latest/admin_manual/">The Nextcloud Server Administration Guide</a>. The code <i>and</i> data,
including especially database dumps, are stored in <q>/Nextcloud/</q> which
-is included in Core's backup procedure as described in <a href="#org2a7965d">Backups</a>. The
+is included in Core's backup procedure as described in <a href="#orgd6409ab">Backups</a>. The
default Apache2 configuration expects to find the web scripts in
<q>/var/www/nextcloud/</q>, so the institute symbolically links this to
<q>/Nextcloud/nextcloud/</q>.
</p>
</div>
</div>
-<div id="outline-container-org1ea6412" class="outline-3">
-<h3 id="org1ea6412"><span class="section-number-3">3.5.</span> The VPN Services</h3>
+<div id="outline-container-org03cae01" class="outline-3">
+<h3 id="org03cae01"><span class="section-number-3">3.5.</span> Accounts</h3>
<div class="outline-text-3" id="text-3-5">
<p>
-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 <a href="#org1024107">The Front Role</a> and <a href="#org936487d">The Gate Role</a>, as well as the
-matching client configurations in <a href="#orge6d718e">The Core Role</a> and the <q>.ovpn</q> files
-generated by <a href="#orgdc36b90">The Client Command</a>. The configurations are based on the
-documentation for OpenVPN v2.4: the <code>openvpn(8)</code> manual page and <a href="https://openvpn.net/community-resources/reference-manual-for-openvpn-2-4/">this
-web page</a>.
-</p>
-</div>
-<div id="outline-container-orgf621905" class="outline-4">
-<h4 id="orgf621905"><span class="section-number-4">3.5.1.</span> The VPN Configuration Options</h4>
-<div class="outline-text-4" id="text-3-5-1">
-<p>
-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
-<code>client-to-client</code> option, allows members to "talk" to each other on
-the VPN subnets using any (experimental) protocol.
-</p>
-
-<div class="org-src-container">
-<code>openvpn-dev-mode</code><pre class="src src-conf" id="org2722342"><code>dev-type tun
-dev ovpn
-topology subnet
-client-to-client
-</code></pre>
-</div>
-
-<p>
-A <code>keepalive</code> 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.
-</p>
-
-<div class="org-src-container">
-<code>openvpn-keepalive</code><pre class="src src-conf" id="orgfe45128"><code>keepalive 10 120
-</code></pre>
-</div>
-
-<p>
-As mentioned in <a href="#org189ccc3">The Name Service</a>, the institute uses a campus name
-server. OpenVPN is instructed to push its address and the campus
-search domain.
-</p>
-
-<div class="org-src-container">
-<code>openvpn-dns</code><pre class="src src-conf" id="org035983f"><code>push <span class="org-string">"dhcp-option DOMAIN {{ domain_priv }}"</span>
-push <span class="org-string">"dhcp-option DNS {{ core_addr }}"</span>
-</code></pre>
-</div>
-
-<p>
-The institute does not put the OpenVPN server in a <code>chroot</code> jail, but
-it does drop privileges to run as user <code>nobody:nobody</code>. The
-<code>persist-</code> options are needed because <code>nobody</code> cannot open the tunnel
-device nor the key files.
-</p>
-
-<div class="org-src-container">
-<code>openvpn-drop-priv</code><pre class="src src-conf" id="orge4d440d"><code>user nobody
-group nogroup
-persist-key
-persist-tun
-</code></pre>
-</div>
-
-<p>
-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 <code>cipher</code> is set to <code>AES-256-GCM</code>,
-the default for OpenVPN v2.4, and <code>auth</code> is upped to <code>SHA256</code> from
-<code>SHA1</code>.
-</p>
-
-<div class="org-src-container">
-<code>openvpn-crypt</code><pre class="src src-conf" id="orgb13e625"><code>cipher AES-256-GCM
-auth SHA256
-</code></pre>
-</div>
-
-<p>
-Finally, a <code>max-client</code> limit was chosen to frustrate flooding while
-accommodating a few members with a handful of devices each.
-</p>
-
-<div class="org-src-container">
-<code>openvpn-max</code><pre class="src src-conf" id="orgd79815a"><code>max-clients 20
-</code></pre>
-</div>
-
-<p>
-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 <q>ipp.txt</q> file. The server's status is
-periodically written to the <q>openvpn-status.log</q> and verbosity is
-raised from the default level 1 to level 3 (just short of a deluge).
-</p>
-
-<div class="org-src-container">
-<code>openvpn-debug</code><pre class="src src-conf" id="org59b4726"><code>ifconfig-pool-persist ipp.txt
-status openvpn-status.log
-verb 3
-</code></pre>
-</div>
-</div>
-</div>
-</div>
-<div id="outline-container-orga38c5ed" class="outline-3">
-<h3 id="orga38c5ed"><span class="section-number-3">3.6.</span> Accounts</h3>
-<div class="outline-text-3" id="text-3-6">
-<p>
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. <a href="#org5db480b">The Institute Commands</a> (e.g. <code>./inst new dick</code>) capture the
+Front. <a href="#org1eb4766">The Institute Commands</a> (e.g. <code>./inst new dick</code>) 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,
all be private (readable and writable only by the owner) by default.
</p>
</div>
-<div id="outline-container-org4a8c2dc" class="outline-4">
-<h4 id="org4a8c2dc"><span class="section-number-4">3.6.1.</span> The Administration Accounts</h4>
-<div class="outline-text-4" id="text-3-6-1">
+<div id="outline-container-org43f41c7" class="outline-4">
+<h4 id="org43f41c7"><span class="section-number-4">3.5.1.</span> The Administration Accounts</h4>
+<div class="outline-text-4" id="text-3-5-1">
<p>
The institute avoids the use of the <code>root</code> account (<code>uid 0</code>) because
it is exempt from the normal Unix permissions checking. The <code>sudo</code>
and programs as <code>root</code>. When installation of a Debian OS leaves the
host with no user accounts, just the <code>root</code> account, the next step is
to create a system administrator's account named <code>sysadm</code> and to give
-it permission to use the <code>sudo</code> command (e.g. as described in <a href="#org9282df0">The
+it permission to use the <code>sudo</code> command (e.g. as described in <a href="#orgd887559">The
Front Machine</a>). When installation prompts for the name of an
initial, privileged user account the same name is given (e.g. as
-described in <a href="#orgae4ce75">The Core Machine</a>). Installation may <i>not</i> prompt and
+described in <a href="#org935c0f2">The Core Machine</a>). Installation may <i>not</i> prompt and
still create an initial user account with a distribution specific name
(e.g. <code>pi</code>). Any name can be used as long as it is provided as the
value of <code>ansible_user</code> in <a href="hosts"><q>hosts</q></a>. Its password is specified by a
vault-encrypted variable in the <a href="Secret/become.yml"><q>Secret/become.yml</q></a> file. (The
-<a href="hosts"><q>hosts</q></a> and <a href="Secret/become.yml"><q>Secret/become.yml</q></a> files are described in <a href="#orgda31017">The Ansible
+<a href="hosts"><q>hosts</q></a> and <a href="Secret/become.yml"><q>Secret/become.yml</q></a> files are described in <a href="#org9259070">The Ansible
Configuration</a>.)
</p>
</div>
</div>
-<div id="outline-container-org9470e9b" class="outline-4">
-<h4 id="org9470e9b"><span class="section-number-4">3.6.2.</span> The Monkey Accounts</h4>
-<div class="outline-text-4" id="text-3-6-2">
+<div id="outline-container-orgbc62415" class="outline-4">
+<h4 id="orgbc62415"><span class="section-number-4">3.5.2.</span> The Monkey Accounts</h4>
+<div class="outline-text-4" id="text-3-5-2">
<p>
The institute's Core uses a special account named <code>monkey</code> to run
background jobs with limited privileges. One of those jobs is to keep
</div>
</div>
</div>
-<div id="outline-container-org1c1be46" class="outline-3">
-<h3 id="org1c1be46"><span class="section-number-3">3.7.</span> Keys</h3>
-<div class="outline-text-3" id="text-3-7">
+<div id="outline-container-org8244014" class="outline-3">
+<h3 id="org8244014"><span class="section-number-3">3.6.</span> Keys</h3>
+<div class="outline-text-3" id="text-3-6">
<p>
The institute keeps its "master secrets" in an encrypted
volume on an off-line hard drive, e.g. a LUKS (Linux Unified Key
</dl>
<p>
-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 <a href="Secret/CA/"><q>Secret/CA/</q></a>.
+The institute uses a couple X.509 certificates to authenticate
+servers. They are created by the EasyRSA Certificate Authority stored
+in <a href="Secret/CA/"><q>Secret/CA/</q></a>.
</p>
<dl class="org-dl">
<dt><a href="Secret/CA/pki/ca.crt"><q>Secret/CA/pki/ca.crt</q></a></dt><dd>The institute CA certificate, used to
sign the other certificates.</dd>
-<dt><a href="Secret/CA/pki/issued/small.example.org.crt"><q>Secret/CA/pki/issued/small.example.org.crt</q></a></dt><dd>The public Apache,
-Postfix, and OpenVPN servers on Front.</dd>
-
-<dt><a href="Secret/CA/pki/issued/gate.small.private.crt"><q>Secret/CA/pki/issued/gate.small.private.crt</q></a></dt><dd>The campus
-OpenVPN server on Gate.</dd>
+<dt><a href="Secret/CA/pki/issued/small.example.org.crt"><q>Secret/CA/pki/issued/small.example.org.crt</q></a></dt><dd>The public
+Postfix, Dovecot and Apache servers on Front.</dd>
<dt><a href="Secret/CA/pki/issued/core.small.private.crt"><q>Secret/CA/pki/issued/core.small.private.crt</q></a></dt><dd>The campus
-Apache (thus Nextcloud), and Dovecot-IMAPd servers.</dd>
-
-<dt><a href="Secret/CA/pki/issued/core.crt"><q>Secret/CA/pki/issued/core.crt</q></a></dt><dd>Core's client certificate, by
-which it authenticates to Front.</dd>
+Postfix, Dovecot and Apache (thus Nextcloud) servers on Core.</dd>
</dl>
<p>
-The <code>./inst client</code> command creates client certificates and keys, and
-can generate OpenVPN configuration (<q>.ovpn</q>) 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 <q>private/members.yml</q> as the value of <code>revoked</code>).
+The <code>./inst client</code> command updates the institute membership roll,
+which lists members and their clients' public keys, and is stored in
+<q>private/members.yml</q>.
</p>
<p>
<p>
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.
</p>
<pre class="example">
</p>
</div>
</div>
-<div id="outline-container-org2a7965d" class="outline-3">
-<h3 id="org2a7965d"><span class="section-number-3">3.8.</span> Backups</h3>
-<div class="outline-text-3" id="text-3-8">
+<div id="outline-container-orgd6409ab" class="outline-3">
+<h3 id="orgd6409ab"><span class="section-number-3">3.7.</span> Backups</h3>
+<div class="outline-text-3" id="text-3-7">
<p>
The small institute backs up its data, but not so much so that nothing
can be deleted. It actually mirrors user directories (<q>/home/</q>), the
</p>
<div class="org-src-container">
-<a href="private/backup"><q>private/backup</q></a><pre class="src src-sh" id="org8d148e8"><code><span class="org-comment-delimiter">#</span><span class="org-comment">!/bin/</span><span class="org-keyword">bash</span><span class="org-comment"> -e</span>
+<a href="private/backup"><q>private/backup</q></a><pre class="src src-sh" id="orgf388eed"><code><span class="org-comment-delimiter">#</span><span class="org-comment">!/bin/</span><span class="org-keyword">bash</span><span class="org-comment"> -e</span>
<span class="org-comment-delimiter">#</span>
<span class="org-comment-delimiter"># </span><span class="org-comment">DO NOT EDIT. Maintained (will be replaced) by Ansible.</span>
<span class="org-comment-delimiter">#</span>
</div>
</div>
</div>
-<div id="outline-container-orgb4f5586" class="outline-2">
-<h2 id="orgb4f5586"><span class="section-number-2">4.</span> The Particulars</h2>
+<div id="outline-container-org77f802b" class="outline-2">
+<h2 id="org77f802b"><span class="section-number-2">4.</span> The Particulars</h2>
<div class="outline-text-2" id="text-4">
<p>
This chapter introduces Ansible variables intended to simplify
<p>
The example settings in this document configure VirtualBox VMs as
-described in the <a href="#org7832c43">Testing</a> chapter. For more information about how a
+described in the <a href="#org3d2b7a1">Testing</a> chapter. For more information about how a
small institute turns the example Ansible code into a working Ansible
-configuration, see chapter <a href="#orgda31017">The Ansible Configuration</a>.
+configuration, see chapter <a href="#org9259070">The Ansible Configuration</a>.
</p>
</div>
-<div id="outline-container-org0ba16f9" class="outline-3">
-<h3 id="org0ba16f9"><span class="section-number-3">4.1.</span> Generic Particulars</h3>
+<div id="outline-container-org31c2f38" class="outline-3">
+<h3 id="org31c2f38"><span class="section-number-3">4.1.</span> Generic Particulars</h3>
<div class="outline-text-3" id="text-4-1">
<p>
The small institute's domain name is used quite frequently in the
</div>
</div>
</div>
-<div id="outline-container-org29f9840" class="outline-3">
-<h3 id="org29f9840"><span class="section-number-3">4.2.</span> Subnets</h3>
+<div id="outline-container-org31e3a27" class="outline-3">
+<h3 id="org31e3a27"><span class="section-number-3">4.2.</span> Subnets</h3>
<div class="outline-text-3" id="text-4-2">
<p>
-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 <i>except</i> the
-<code>192.168</code> 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 <i>except</i> the <code>192.168</code> 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).
</p>
<table border="2" cellspacing="0" cellpadding="6" rules="groups" frame="hsides">
</code></pre>
</div>
-<div class="TEXT" id="org078b489">
+<div class="TEXT" id="org829cf98">
<p>
=> 10.62.17.0/24
</p>
sensitive information so again the code block below "tangles" into
<a href="private/vars.yml"><q>private/vars.yml</q></a> rather than <a href="public/vars.yml"><q>public/vars.yml</q></a>. Two of the
addresses are in <code>192.168</code> subnets because they are part of a test
-configuration using mostly-default VirtualBoxes (described <a href="#org7832c43">here</a>).
+configuration using mostly-default VirtualBoxes (described <a href="#org3d2b7a1">here</a>).
</p>
<div class="org-src-container">
<a href="private/vars.yml"><q>private/vars.yml</q></a><pre class="src src-conf"><code>
private_net_cidr: 192.168.56.0/24
wild_net_cidr: 192.168.57.0/24
-public_vpn_net_cidr: 10.177.86.0/24
-campus_vpn_net_cidr: 10.84.138.0/24
+public_wg_net_cidr: 10.177.87.0/24
+campus_wg_net_cidr: 10.84.139.0/24
</code></pre>
</div>
<p>
The network addresses are needed in several additional formats, e.g.
-network address and subnet mask (<code>10.84.138.0 255.255.255.0</code>). The
+network address and subnet mask (<code>10.84.139.0 255.255.255.0</code>). The
following boilerplate uses Ansible's <code>ipaddr</code> filter to set several
corresponding variables, each with an appropriate suffix,
e.g. <code>_net_and_mask</code> rather than <code>_net_cidr</code>.
private_net_mask:
<span class="org-string">"{{ private_net_cidr | ansible.utils.ipaddr('netmask') }}"</span>
private_net_and_mask: <span class="org-string">"{{ private_net }} {{ private_net_mask }}"</span>
-public_vpn_net:
- <span class="org-string">"{{ public_vpn_net_cidr | ansible.utils.ipaddr('network') }}"</span>
-public_vpn_net_mask:
- <span class="org-string">"{{ public_vpn_net_cidr | ansible.utils.ipaddr('netmask') }}"</span>
-public_vpn_net_and_mask:
- <span class="org-string">"{{ public_vpn_net }} {{ public_vpn_net_mask }}"</span>
-campus_vpn_net:
- <span class="org-string">"{{ campus_vpn_net_cidr | ansible.utils.ipaddr('network') }}"</span>
-campus_vpn_net_mask:
- <span class="org-string">"{{ campus_vpn_net_cidr | ansible.utils.ipaddr('netmask') }}"</span>
-campus_vpn_net_and_mask:
- <span class="org-string">"{{ campus_vpn_net }} {{ campus_vpn_net_mask }}"</span>
wild_net: <span class="org-string">"{{ wild_net_cidr | ansible.utils.ipaddr('network') }}"</span>
wild_net_mask:
<span class="org-string">"{{ wild_net_cidr | ansible.utils.ipaddr('netmask') }}"</span>
wild_net_and_mask: <span class="org-string">"{{ wild_net }} {{ wild_net_mask }}"</span>
wild_net_broadcast:
<span class="org-string">"{{ wild_net_cidr | ansible.utils.ipaddr('broadcast') }}"</span>
+public_wg_net:
+ <span class="org-string">"{{ public_wg_net_cidr | ansible.utils.ipaddr('network') }}"</span>
+public_wg_net_mask:
+ <span class="org-string">"{{ public_wg_net_cidr | ansible.utils.ipaddr('netmask') }}"</span>
+public_wg_net_and_mask:
+ <span class="org-string">"{{ public_wg_net }} {{ public_wg_net_mask }}"</span>
+campus_wg_net:
+ <span class="org-string">"{{ campus_wg_net_cidr | ansible.utils.ipaddr('network') }}"</span>
+campus_wg_net_mask:
+ <span class="org-string">"{{ campus_wg_net_cidr | ansible.utils.ipaddr('netmask') }}"</span>
+campus_wg_net_and_mask:
+ <span class="org-string">"{{ campus_wg_net }} {{ campus_wg_net_mask }}"</span>
</code></pre>
</div>
</p>
<p>
-Finally, five host addresses are needed frequently in the Ansible
+Finally, four host addresses are needed frequently in the Ansible
code. The first two are Core's and Gate's addresses on the private
-Ethernet. The next two are Gate's and the campus Wi-Fi's addresses on
-the "wild" subnet, the untrusted Ethernet (<code>wild_net</code>) between Gate
-and the campus Wi-Fi access point(s) and IoT appliances. The last is
-Front's address on the public VPN, perversely called
-<code>front_private_addr</code>. The following code block picks the obvious IP
-addresses for Core (host 1) and Gate (host 2).
+Ethernet. The other two are Gate's and the campus Wi-Fi's addresses
+on the wild Ethernet. The following code block chooses host 1 for
+Core and host 2 for Gate on the private Ethernet. On the wild
+Ethernet, host 1 is Gate and host 2 is the access point (or wired
+IoT appliance).
</p>
<div class="org-src-container">
gate_addr_cidr: <span class="org-string">"{{ private_net_cidr | ansible.utils.ipaddr('2') }}"</span>
gate_wild_addr_cidr:
<span class="org-string">"{{ wild_net_cidr | ansible.utils.ipaddr('1') }}"</span>
-wifi_wan_addr_cidr: <span class="org-string">"{{ wild_net_cidr | ansible.utils.ipaddr('2') }}"</span>
-front_private_addr_cidr:
- <span class="org-string">"{{ public_vpn_net_cidr | ansible.utils.ipaddr('1') }}"</span>
+front_wg_addr_cidr:
+ <span class="org-string">"{{ public_wg_net_cidr | ansible.utils.ipaddr('1') }}"</span>
core_addr: <span class="org-string">"{{ core_addr_cidr | ansible.utils.ipaddr('address') }}"</span>
gate_addr: <span class="org-string">"{{ gate_addr_cidr | ansible.utils.ipaddr('address') }}"</span>
gate_wild_addr:
<span class="org-string">"{{ gate_wild_addr_cidr | ansible.utils.ipaddr('address') }}"</span>
-wifi_wan_addr:
- <span class="org-string">"{{ wifi_wan_addr_cidr | ansible.utils.ipaddr('address') }}"</span>
-front_private_addr:
- <span class="org-string">"{{ front_private_addr_cidr | ansible.utils.ipaddr('address') }}"</span>
+front_wg_addr:
+ <span class="org-string">"{{ front_wg_addr_cidr | ansible.utils.ipaddr('address') }}"</span>
</code></pre>
</div>
</div>
</div>
</div>
-<div id="outline-container-orgbf29081" class="outline-2">
-<h2 id="orgbf29081"><span class="section-number-2">5.</span> The Hardware</h2>
+<div id="outline-container-org5af003d" class="outline-2">
+<h2 id="org5af003d"><span class="section-number-2">5.</span> The Hardware</h2>
<div class="outline-text-2" id="text-5">
<p>
The small institute's network was built by its system administrator
using Ansible on a trusted notebook. The Ansible configuration and
scripts were generated by "tangling" the Ansible code included here.
-(<a href="#orgda31017">The Ansible Configuration</a> describes how to do this.) The following
+(<a href="#org9259070">The Ansible Configuration</a> describes how to do this.) The following
sections describe how Front, Gate and Core were prepared for Ansible.
</p>
</div>
-<div id="outline-container-org9282df0" class="outline-3">
-<h3 id="org9282df0"><span class="section-number-3">5.1.</span> The Front Machine</h3>
+<div id="outline-container-orgd887559" class="outline-3">
+<h3 id="orgd887559"><span class="section-number-3">5.1.</span> The Front Machine</h3>
<div class="outline-text-3" id="text-5-1">
<p>
Front is the small institute's public facing server, a virtual machine
Internet café using just the administrator's notebook.
</p>
</div>
-<div id="outline-container-org7dbbd56" class="outline-4">
-<h4 id="org7dbbd56"><span class="section-number-4">5.1.1.</span> A Digital Ocean Droplet</h4>
+<div id="outline-container-orgcc4ff46" class="outline-4">
+<h4 id="orgcc4ff46"><span class="section-number-4">5.1.1.</span> A Digital Ocean Droplet</h4>
<div class="outline-text-4" id="text-5-1-1">
<p>
The following example prepared a new front on a Digital Ocean droplet.
<p>
The freshly created Digital Ocean droplet came with just one account,
<code>root</code>, but the small institute avoids remote access to the "super
-user" account (per the policy in <a href="#org4a8c2dc">The Administration Accounts</a>), so the
+user" account (per the policy in <a href="#org43f41c7">The Administration Accounts</a>), so the
administrator created a <code>sysadm</code> account with the ability to request
escalated privileges via the <code>sudo</code> command.
</p>
The password was generated by <code>gpw</code>, saved in the administrator's
password keep, and later added to <a href="Secret/become.yml"><q>Secret/become.yml</q></a> as shown below.
(Producing a working Ansible configuration with <a href="Secret/become.yml"><q>Secret/become.yml</q></a>
-file is described in <a href="#orgda31017">The Ansible Configuration</a>.)
+file is described in <a href="#org9259070">The Ansible Configuration</a>.)
</p>
<pre class="example">
<p>
After creating the <code>sysadm</code> account on the droplet, the administrator
concatenated a personal public ssh key and the key found in
-<a href="Secret/ssh_admin/"><q>Secret/ssh_admin/</q></a> (created by <a href="#org3fe9e37">The CA Command</a>) into an <q>admin_keys</q>
+<a href="Secret/ssh_admin/"><q>Secret/ssh_admin/</q></a> (created by <a href="#orgd14a881">The CA Command</a>) into an <q>admin_keys</q>
file, copied it to the droplet, and installed it as the
<q>authorized_keys</q> for <code>sysadm</code>.
</p>
</p>
<pre class="example">
-notebook$ scp Secret/ssh_front/etc/ssh/ssh_host_* sysadm@159.65.75.60:
+notebook$ ( cd Secret/ssh_front/etc/ssh/;
+notebook_ scp ssh_host_* sysadm@159.65.75.60: )
notebook$ ssh sysadm@159.65.75.60
sysadm@ubuntu$ chmod 600 ssh_host_*
sysadm@ubuntu$ chmod 644 ssh_host_*.pub
</div>
</div>
</div>
-<div id="outline-container-orgae4ce75" class="outline-3">
-<h3 id="orgae4ce75"><span class="section-number-3">5.2.</span> The Core Machine</h3>
+<div id="outline-container-org935c0f2" class="outline-3">
+<h3 id="org935c0f2"><span class="section-number-3">5.2.</span> The Core Machine</h3>
<div class="outline-text-3" id="text-5-2">
<p>
Core is the small institute's private file, email, cloud and whatnot
freshly installed. During installation, the machine was named <code>core</code>,
no desktop or server software was installed, no root password was set,
and a privileged account named <code>sysadm</code> was created (per the policy in
-<a href="#org4a8c2dc">The Administration Accounts</a>).
+<a href="#org43f41c7">The Administration Accounts</a>).
</p>
<pre class="example">
The password was generated by <code>gpw</code>, saved in the administrator's
password keep, and later added to <a href="Secret/become.yml"><q>Secret/become.yml</q></a> as shown below.
(Producing a working Ansible configuration with <a href="Secret/become.yml"><q>Secret/become.yml</q></a>
-file is described in <a href="#orgda31017">The Ansible Configuration</a>.)
+file is described in <a href="#org9259070">The Ansible Configuration</a>.)
</p>
<pre class="example">
<pre class="example">
$ 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 openssh-server
</pre>
<p>
Next, the administrator concatenated a personal public ssh key and the
-key found in <a href="Secret/ssh_admin/"><q>Secret/ssh_admin/</q></a> (created by <a href="#org3fe9e37">The CA Command</a>) into an
+key found in <a href="Secret/ssh_admin/"><q>Secret/ssh_admin/</q></a> (created by <a href="#orgd14a881">The CA Command</a>) into an
<q>admin_keys</q> file, copied it to Core, and installed it as the
<q>authorized_keys</q> for <code>sysadm</code>.
</p>
<p>
In the example command lines below, the address <code>10.227.248.1</code> was
generated by the random subnet address picking procedure described in
-<a href="#org29f9840">Subnets</a>, and is named <code>core_addr</code> in the Ansible code. The second
+<a href="#org31e3a27">Subnets</a>, and is named <code>core_addr</code> in the Ansible code. The second
address, <code>10.227.248.2</code>, is the corresponding address for Gate's
Ethernet interface, and is named <code>gate_addr</code> in the Ansible
code.
</p>
</div>
</div>
-<div id="outline-container-org40481f7" class="outline-3">
-<h3 id="org40481f7"><span class="section-number-3">5.3.</span> The Gate Machine</h3>
+<div id="outline-container-org817fd99" class="outline-3">
+<h3 id="org817fd99"><span class="section-number-3">5.3.</span> The Gate Machine</h3>
<div class="outline-text-3" id="text-5-3">
<p>
Gate is the small institute's route to the Internet, and the campus
campground Wi-Fi access point, etc.</li>
</ol>
-<pre class="example" id="orgf49687b">
+<pre class="example" id="orgaf88c7e">
=============== | ==================================================
| Premises
(Campus ISP)
+----Ethernet switch
</pre>
</div>
-<div id="outline-container-org6edd1eb" class="outline-4">
-<h4 id="org6edd1eb"><span class="section-number-4">5.3.1.</span> Alternate Gate Topology</h4>
+<div id="outline-container-org528e565" class="outline-4">
+<h4 id="org528e565"><span class="section-number-4">5.3.1.</span> Alternate Gate Topology</h4>
<div class="outline-text-4" id="text-5-3-1">
<p>
While Gate and Core really need to be separate machines for security
following topology.
</p>
-<pre class="example" id="org0d82eb6">
+<pre class="example" id="org80ac5eb">
=============== | ==================================================
| Premises
(House ISP)
</p>
</div>
</div>
-<div id="outline-container-orgaa27474" class="outline-4">
-<h4 id="orgaa27474"><span class="section-number-4">5.3.2.</span> Original Gate Topology</h4>
+<div id="outline-container-orge82f877" class="outline-4">
+<h4 id="orge82f877"><span class="section-number-4">5.3.2.</span> Original Gate Topology</h4>
<div class="outline-text-4" id="text-5-3-2">
<p>
The Ansible code in this document is somewhat dependent on the
-physical network shown in the <a href="#org12986b7">Overview</a> wherein Gate has three network
+physical network shown in the <a href="#orga8e230f">Overview</a> wherein Gate has three network
interfaces.
</p>
freshly installed. During installation, the machine was named <code>gate</code>,
no desktop or server software was installed, no root password was set,
and a privileged account named <code>sysadm</code> was created (per the policy in
-<a href="#org4a8c2dc">The Administration Accounts</a>).
+<a href="#org43f41c7">The Administration Accounts</a>).
</p>
<pre class="example">
The password was generated by <code>gpw</code>, saved in the administrator's
password keep, and later added to <a href="Secret/become.yml"><q>Secret/become.yml</q></a> as shown below.
(Producing a working Ansible configuration with <a href="Secret/become.yml"><q>Secret/become.yml</q></a>
-file is described in <a href="#orgda31017">The Ansible Configuration</a>.)
+file is described in <a href="#org9259070">The Ansible Configuration</a>.)
</p>
<pre class="example">
<pre class="example">
$ sudo apt install netplan.io systemd-resolved unattended-upgrades \
-_ ufw isc-dhcp-server postfix openvpn \
+_ ufw isc-dhcp-server postfix wireguard \
_ openssh-server
</pre>
<p>
Next, the administrator concatenated a personal public ssh key and the
-key found in <a href="Secret/ssh_admin/"><q>Secret/ssh_admin/</q></a> (created by <a href="#org3fe9e37">The CA Command</a>) into an
+key found in <a href="Secret/ssh_admin/"><q>Secret/ssh_admin/</q></a> (created by <a href="#orgd14a881">The CA Command</a>) into an
<q>admin_keys</q> file, copied it to Gate, and installed it as the
<q>authorized_keys</q> for <code>sysadm</code>.
</p>
<p>
In the example command lines below, the address <code>10.227.248.2</code> was
generated by the random subnet address picking procedure described in
-<a href="#org29f9840">Subnets</a>, and is named <code>gate_addr</code> in the Ansible code.
+<a href="#org31e3a27">Subnets</a>, and is named <code>gate_addr</code> in the Ansible code.
</p>
<pre class="example">
campus Wi-Fi access point and the campus ISP. The three network
adapters are known by their MAC addresses, the values of the variables
<code>gate_lan_mac</code>, <code>gate_wild_mac</code>, and <code>gate_isp_mac</code>. (For more
-information, see the Gate role's <a href="#orgfd4af3f">Configure Netplan</a> task.)
+information, see the Gate role's <a href="#org2c03bc2">Configure Netplan</a> task.)
</p>
<p>
</div>
</div>
</div>
-<div id="outline-container-org72438bf" class="outline-2">
-<h2 id="org72438bf"><span class="section-number-2">6.</span> The All Role</h2>
+<div id="outline-container-org550bf9d" class="outline-2">
+<h2 id="org550bf9d"><span class="section-number-2">6.</span> The All Role</h2>
<div class="outline-text-2" id="text-6">
<p>
The <code>all</code> role contains tasks that are executed on all of the
institute's servers. At the moment there is just the one.
</p>
</div>
-<div id="outline-container-org204ceac" class="outline-3">
-<h3 id="org204ceac"><span class="section-number-3">6.1.</span> Include Particulars</h3>
+<div id="outline-container-org5f5eca0" class="outline-3">
+<h3 id="org5f5eca0"><span class="section-number-3">6.1.</span> Include Particulars</h3>
<div class="outline-text-3" id="text-6-1">
<p>
The <code>all</code> role's task contains a reference to a common institute
particular, the institute's <code>domain_name</code>, a variable found in the
<q>public/vars.yml</q> file. Thus the first task of the <code>all</code> role is to
-include the variables defined in this file (described in <a href="#orgb4f5586">The
+include the variables defined in this file (described in <a href="#org77f802b">The
Particulars</a>). The code block below is the first to tangle into
-<a href="roles/front/tasks/main.yml"><q>roles/all/tasks/main.yml</q></a>.
+<a href="roles/all/tasks/main.yml"><q>roles/all/tasks/main.yml</q></a>.
</p>
<div class="org-src-container">
-<a href="roles/front/tasks/main.yml"><q>roles/all/tasks/main.yml</q></a><pre class="src src-conf"><code>---
+<a href="roles/all/tasks/main.yml"><q>roles/all/tasks/main.yml</q></a><pre class="src src-conf"><code>---
- name: Include public variables.
include_vars: ../public/vars.yml
tags: accounts
</div>
</div>
</div>
-<div id="outline-container-orgbfaf303" class="outline-3">
-<h3 id="orgbfaf303"><span class="section-number-3">6.2.</span> Enable Systemd Resolved</h3>
+<div id="outline-container-org4bb7687" class="outline-3">
+<h3 id="org4bb7687"><span class="section-number-3">6.2.</span> Enable Systemd Resolved</h3>
<div class="outline-text-3" id="text-6-2">
<p>
The <code>systemd-networkd</code> and <code>systemd-resolved</code> service units are not
</p>
<div class="org-src-container">
-<a href="roles_t/front/tasks/main.yml"><q>roles_t/all/tasks/main.yml</q></a><pre class="src src-conf"><code>
+<a href="roles_t/all/tasks/main.yml"><q>roles_t/all/tasks/main.yml</q></a><pre class="src src-conf"><code>
- name: Install systemd-resolved.
become: yes
<span class="org-variable-name">apt: pkg</span>=systemd-resolved
</div>
</div>
</div>
-<div id="outline-container-org2756611" class="outline-3">
-<h3 id="org2756611"><span class="section-number-3">6.3.</span> Trust Institute Certificate Authority</h3>
+<div id="outline-container-org0e4f2a5" class="outline-3">
+<h3 id="org0e4f2a5"><span class="section-number-3">6.3.</span> Trust Institute Certificate Authority</h3>
<div class="outline-text-3" id="text-6-3">
<p>
All servers should recognize the institute's Certificate Authority as
trustworthy, so its certificate is added to the set of trusted CAs on
each host. More information about how the small institute manages its
-X.509 certificates is available in <a href="#org1c1be46">Keys</a>.
+X.509 certificates is available in <a href="#org8244014">Keys</a>.
</p>
<div class="org-src-container">
-<a href="roles_t/front/tasks/main.yml"><q>roles_t/all/tasks/main.yml</q></a><pre class="src src-conf"><code>
+<a href="roles_t/all/tasks/main.yml"><q>roles_t/all/tasks/main.yml</q></a><pre class="src src-conf"><code>
- name: Trust the institute CA.
become: yes
copy:
</div>
<div class="org-src-container">
-<a href="roles_t/front/handlers/main.yml"><q>roles_t/all/handlers/main.yml</q></a><pre class="src src-conf"><code>
+<a href="roles_t/all/handlers/main.yml"><q>roles_t/all/handlers/main.yml</q></a><pre class="src src-conf"><code>
- name: Update CAs.
become: yes
command: update-ca-certificates
</div>
</div>
</div>
-<div id="outline-container-org1024107" class="outline-2">
-<h2 id="org1024107"><span class="section-number-2">7.</span> The Front Role</h2>
+<div id="outline-container-orgd6c5c74" class="outline-2">
+<h2 id="orgd6c5c74"><span class="section-number-2">7.</span> The Front Role</h2>
<div class="outline-text-2" id="text-7">
<p>
The <code>front</code> role installs and configures the services expected on the
institute's publicly accessible "front door": email, web, VPN. The
virtual machine is prepared with an Ubuntu Server install and remote
access to a privileged, administrator's account. (For details, see
-<a href="#org9282df0">The Front Machine</a>.)
+<a href="#orgd887559">The Front Machine</a>.)
</p>
<p>
perhaps with symbolic links to, for example,
<q>/etc/letsencrypt/live/small.example.org/fullchain.pem</q>.
</p>
-
-<p>
-Note that the OpenVPN server does <i>not</i> use <q>/etc/server.crt</q>. It
-uses the institute's CA and server certificates, and expects client
-certificates signed by the institute CA.
-</p>
</div>
-<div id="outline-container-org92fa9ca" class="outline-3">
-<h3 id="org92fa9ca"><span class="section-number-3">7.1.</span> Include Particulars</h3>
+<div id="outline-container-org4dc0d2c" class="outline-3">
+<h3 id="org4dc0d2c"><span class="section-number-3">7.1.</span> Include Particulars</h3>
<div class="outline-text-3" id="text-7-1">
<p>
-The first task, as in <a href="#org72438bf">The All Role</a>, is to include the institute
+The first task, as in <a href="#org550bf9d">The All Role</a>, is to include the institute
particulars. The <code>front</code> role refers to private variables and the
membership roll, so these are included was well.
</p>
</div>
</div>
</div>
-<div id="outline-container-orgd1223b4" class="outline-3">
-<h3 id="orgd1223b4"><span class="section-number-3">7.2.</span> Configure Hostname</h3>
+<div id="outline-container-orgca8551e" class="outline-3">
+<h3 id="orgca8551e"><span class="section-number-3">7.2.</span> Configure Hostname</h3>
<div class="outline-text-3" id="text-7-2">
<p>
This task ensures that Front's <q>/etc/hostname</q> and <q>/etc/mailname</q> are
</div>
</div>
</div>
-<div id="outline-container-orgc60729b" class="outline-3">
-<h3 id="orgc60729b"><span class="section-number-3">7.3.</span> Add Administrator to System Groups</h3>
+<div id="outline-container-org29ab388" class="outline-3">
+<h3 id="org29ab388"><span class="section-number-3">7.3.</span> Add Administrator to System Groups</h3>
<div class="outline-text-3" id="text-7-3">
<p>
The administrator often needs to read (directories of) log files owned
</div>
</div>
</div>
-<div id="outline-container-org581d004" class="outline-3">
-<h3 id="org581d004"><span class="section-number-3">7.4.</span> Configure SSH</h3>
+<div id="outline-container-org6e30fce" class="outline-3">
+<h3 id="org6e30fce"><span class="section-number-3">7.4.</span> Configure SSH</h3>
<div class="outline-text-3" id="text-7-4">
<p>
The SSH service on Front needs to be known to Monkey. The following
</div>
</div>
</div>
-<div id="outline-container-org345d680" class="outline-3">
-<h3 id="org345d680"><span class="section-number-3">7.5.</span> Configure Monkey</h3>
+<div id="outline-container-org5256a97" class="outline-3">
+<h3 id="org5256a97"><span class="section-number-3">7.5.</span> Configure Monkey</h3>
<div class="outline-text-3" id="text-7-5">
<p>
The small institute runs cron jobs and web scripts that generate
system account named <code>monkey</code>. One of Monkey's more important jobs on
Core is to run <code>rsync</code> to update the public web site on Front. Monkey
on Core will login as <code>monkey</code> on Front to synchronize the files (as
-described in <a href="#orgbcda1ce">*Configure Apache2</a>). To do that without needing a
+described in <a href="#org40128e7">*Configure Apache2</a>). To do that without needing a
password, the <code>monkey</code> account on Front should authorize Monkey's SSH
key on Core.
</p>
</div>
</div>
</div>
-<div id="outline-container-orgce756fc" class="outline-3">
-<h3 id="orgce756fc"><span class="section-number-3">7.6.</span> Install Rsync</h3>
+<div id="outline-container-orgdcab48f" class="outline-3">
+<h3 id="orgdcab48f"><span class="section-number-3">7.6.</span> Install Rsync</h3>
<div class="outline-text-3" id="text-7-6">
<p>
Monkey uses Rsync to keep the institute's public web site up-to-date.
</div>
</div>
</div>
-<div id="outline-container-org0ec2b68" class="outline-3">
-<h3 id="org0ec2b68"><span class="section-number-3">7.7.</span> Install Unattended Upgrades</h3>
+<div id="outline-container-orgb5ea472" class="outline-3">
+<h3 id="orgb5ea472"><span class="section-number-3">7.7.</span> Install Unattended Upgrades</h3>
<div class="outline-text-3" id="text-7-7">
<p>
The institute prefers to install security updates as soon as possible.
</div>
</div>
</div>
-<div id="outline-container-orgdd1398a" class="outline-3">
-<h3 id="orgdd1398a"><span class="section-number-3">7.8.</span> Configure User Accounts</h3>
+<div id="outline-container-org9f70d16" class="outline-3">
+<h3 id="org9f70d16"><span class="section-number-3">7.8.</span> Configure User Accounts</h3>
<div class="outline-text-3" id="text-7-8">
<p>
User accounts are created immediately so that Postfix and Dovecot can
start delivering email immediately, <i>without</i> returning "no such
-recipient" replies. The <a href="#org6138e49">Account Management</a> chapter describes the
+recipient" replies. The <a href="#org944d8f7">Account Management</a> chapter describes the
<code>members</code> and <code>usernames</code> variables used below.
</p>
</div>
</div>
</div>
-<div id="outline-container-org7ce2595" class="outline-3">
-<h3 id="org7ce2595"><span class="section-number-3">7.9.</span> Install Server Certificate</h3>
+<div id="outline-container-orgc4b9bdf" class="outline-3">
+<h3 id="orgc4b9bdf"><span class="section-number-3">7.9.</span> Install Server Certificate</h3>
<div class="outline-text-3" id="text-7-9">
<p>
The servers on Front use the same certificate (and key) to
</div>
</div>
</div>
-<div id="outline-container-orga330953" class="outline-3">
-<h3 id="orga330953"><span class="section-number-3">7.10.</span> Configure Postfix on Front</h3>
+<div id="outline-container-org1237c08" class="outline-3">
+<h3 id="org1237c08"><span class="section-number-3">7.10.</span> Configure Postfix on Front</h3>
<div class="outline-text-3" id="text-7-10">
<p>
Front uses Postfix to provide the institute's public SMTP service, and
</ul>
<p>
-As discussed in <a href="#org73635f2">The Email Service</a> above, Front's Postfix configuration
+As discussed in <a href="#org474e9f9">The Email Service</a> above, Front's Postfix configuration
includes site-wide support for larger message sizes, shorter queue
times, the relaying configuration, and the common path to incoming
emails. These and a few Front-specific Postfix configurations
</p>
<p>
-Front relays messages from the institute's public VPN via which Core
-relays messages from the campus.
+Front relays messages from the institute's public WireGuard™ subnet
+via which Core relays messages from the campus.
</p>
<div class="org-src-container">
-<code>postfix-front-networks</code><pre class="src src-conf" id="orgba644bd"><code>- p: mynetworks
+<code>postfix-front-networks</code><pre class="src src-conf" id="org18244e0"><code>- p: mynetworks
v: >-
- {{ public_vpn_net_cidr }}
+ {{ public_wg_net_cidr }}
127.0.0.0/8
[<span class="org-type">::ffff:127.0.0.0</span>]/104
[<span class="org-type">::1</span>]/128
</p>
<div class="org-src-container">
-<code>postfix-front-restrictions</code><pre class="src src-conf" id="orga288f25"><code>- p: smtpd_recipient_restrictions
+<code>postfix-front-restrictions</code><pre class="src src-conf" id="orge3d5cde"><code>- p: smtpd_recipient_restrictions
v: >-
permit_mynetworks
reject_unauth_pipelining
</p>
<div class="org-src-container">
-<code>postfix-header-checks</code><pre class="src src-conf" id="orga20329d"><code>- p: smtp_header_checks
+<code>postfix-header-checks</code><pre class="src src-conf" id="orge624ce5"><code>- p: smtp_header_checks
v: regexp:/etc/postfix/header_checks.cf
</code></pre>
</div>
<div class="org-src-container">
-<code>postfix-header-checks-content</code><pre class="src src-conf" id="org0057dfe"><code>/^Received:/ IGNORE
+<code>postfix-header-checks-content</code><pre class="src src-conf" id="org63fc398"><code>/^Received:/ IGNORE
/^User-Agent:/ IGNORE
</code></pre>
</div>
</p>
<div class="org-src-container">
-<code>postfix-front</code><pre class="src src-conf" id="org5ee702d"><code>- { p: smtpd_tls_cert_file, v: /etc/server.crt }
+<code>postfix-front</code><pre class="src src-conf" id="org591d058"><code>- { p: smtpd_tls_cert_file, v: /etc/server.crt }
- { p: smtpd_tls_key_file, v: /etc/server.key }
<<postfix-front-networks>>
<<postfix-front-restrictions>>
</div>
</div>
</div>
-<div id="outline-container-org75a0395" class="outline-3">
-<h3 id="org75a0395"><span class="section-number-3">7.11.</span> Configure Public Email Aliases</h3>
+<div id="outline-container-org901d745" class="outline-3">
+<h3 id="org901d745"><span class="section-number-3">7.11.</span> Configure Public Email Aliases</h3>
<div class="outline-text-3" id="text-7-11">
<p>
The institute's Front needs to deliver email addressed to a number of
abuse: root
webmaster: root
admin: root
- monkey: monkey@{{ front_private_addr }}
+ monkey: monkey@{{ front_wg_addr }}
root: {{ ansible_user }}
path: /etc/aliases
marker: <span class="org-string">"# {mark} INSTITUTE MANAGED BLOCK"</span>
</div>
</div>
</div>
-<div id="outline-container-orgdc00207" class="outline-3">
-<h3 id="orgdc00207"><span class="section-number-3">7.12.</span> Configure Dovecot IMAPd</h3>
+<div id="outline-container-org44abb98" class="outline-3">
+<h3 id="org44abb98"><span class="section-number-3">7.12.</span> Configure Dovecot IMAPd</h3>
<div class="outline-text-3" id="text-7-12">
<p>
Front uses Dovecot's IMAPd to allow user Fetchmail jobs on Core to
bit "over the top" given that Core accesses Front via VPN, but helps
to ensure privacy even when members must, in extremis, access recent
email directly from their accounts on Front. For more information
-about Front's role in the institute's email services, see <a href="#org73635f2">The Email
+about Front's role in the institute's email services, see <a href="#org474e9f9">The Email
Service</a>.
</p>
</div>
</div>
</div>
-<div id="outline-container-orga7d4870" class="outline-3">
-<h3 id="orga7d4870"><span class="section-number-3">7.13.</span> Configure Apache2 <a id="orgbcda1ce"></a></h3>
+<div id="outline-container-orgd0433e4" class="outline-3">
+<h3 id="orgd0433e4"><span class="section-number-3">7.13.</span> Configure Apache2 <a id="org40128e7"></a></h3>
<div class="outline-text-3" id="text-7-13">
<p>
This is the small institute's public web site. It is simple, static,
</p>
<div class="org-src-container">
-<code>apache-ciphers</code><pre class="src src-conf" id="orgd32683d"><code>SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
+<code>apache-ciphers</code><pre class="src src-conf" id="orgbd8c682"><code>SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
SSLHonorCipherOrder on
<span class="org-type">SSLCipherSuite {</span>{ [ <span class="org-string">'ECDHE-ECDSA-AES128-GCM-SHA256'</span>,
<span class="org-string">'ECDHE-ECDSA-AES256-GCM-SHA384'</span>,
</p>
<div class="org-src-container">
-<code>apache-userdir-front</code><pre class="src src-conf" id="org5949197"><code>UserDir /home/www-users
+<code>apache-userdir-front</code><pre class="src src-conf" id="orgf8d4a88"><code>UserDir /home/www-users
<Directory /home/www-users/>
Require all granted
AllowOverride None
</p>
<div class="org-src-container">
-<code>apache-redirect-front</code><pre class="src src-conf" id="org8203d34"><code><VirtualHost *:80>
+<code>apache-redirect-front</code><pre class="src src-conf" id="orgf20dd06"><code><VirtualHost *:80>
Redirect permanent / https://{{ domain_name }}/
</VirtualHost>
</code></pre>
</p>
<div class="org-src-container">
-<code>apache-front</code><pre class="src src-conf" id="org26265cf"><code>ServerName {{ domain_name }}
+<code>apache-front</code><pre class="src src-conf" id="org5b2dff3"><code>ServerName {{ domain_name }}
ServerAdmin webmaster@{{ domain_name }}
DocumentRoot /home/www
</div>
</div>
</div>
-<div id="outline-container-org3bc00f9" class="outline-3">
-<h3 id="org3bc00f9"><span class="section-number-3">7.14.</span> Configure OpenVPN</h3>
+<div id="outline-container-org290050a" class="outline-3">
+<h3 id="org290050a"><span class="section-number-3">7.14.</span> Configure Public WireGuard™ Subnet</h3>
<div class="outline-text-3" id="text-7-14">
<p>
-Front uses OpenVPN to provide the institute's public VPN service. The
-configuration is straightforward with one complication. OpenVPN needs
-to know how to route to the campus VPN, which is only accessible when
-Core is connected. OpenVPN supports these dynamic routes internally
-with client-specific configuration files. The small institute uses
-one of these, <q>/etc/openvpn/ccd/core</q>, so that OpenVPN will know to
-route packets for the campus networks to Core.
+Front uses WireGuard™ to provide a public (Internet accessible) VPN
+service. Core has an interface on this VPN and is expected to forward
+packets between it and the institute's other private networks.
</p>
-<div class="org-src-container">
-<code>openvpn-ccd-core</code><pre class="src src-conf" id="orgbbdd4f4"><code>iroute {{ private_net_and_mask }}
-iroute {{ campus_vpn_net_and_mask }}
-</code></pre>
-</div>
-
<p>
-The VPN clients are <i>not</i> configured to route <i>all</i> of their traffic
-through the VPN, so Front pushes routes to the other institute
-networks. The clients thus know to route traffic for the private
-Ethernet or campus VPN to Front on the public VPN. (If the clients
-<i>were</i> configured to route all traffic through the VPN, the one
-default route is all that would be needed.) Front itself is in the
-same situation, outside the institute networks with a default route
-through some ISP, and thus needs the same routes as the clients.
+The following example <a href="#orgeeaba66"><q>private/front-wg0.conf</q></a> configuration recognizes
+Core by its public key and routes the institute's private networks to
+it. It also recognizes Dick's notebook and his (replacement) phone,
+assigning them host numbers 4 and 6 on the VPN.
</p>
<div class="org-src-container">
-<code>openvpn-front-routes</code><pre class="src src-conf" id="org9e2d873"><code>route {{ private_net_and_mask }}
-route {{ campus_vpn_net_and_mask }}
-push <span class="org-string">"route {{ private_net_and_mask }}"</span>
-push <span class="org-string">"route {{ campus_vpn_net_and_mask }}"</span>
+<q>private/front-wg0.conf</q><pre class="src src-conf" id="orgeeaba66"><code>[<span class="org-type">Interface</span>]
+<span class="org-variable-name">Address</span> = 10.177.87.1/24
+<span class="org-variable-name">ListenPort</span> = 39608
+<span class="org-variable-name">PostUp</span> = wg set %i private-key /etc/wireguard/private-key
+<span class="org-variable-name">PostUp</span> = resolvectl dns %i 192.168.56.1
+<span class="org-variable-name">PostUp</span> = resolvectl domain %i small.private
+
+<span class="org-comment-delimiter"># </span><span class="org-comment">Core</span>
+[<span class="org-type">Peer</span>]
+<span class="org-variable-name">PublicKey</span> = lGhC51IBgZtlq4H2bsYFuKvPtV0VAEwUvVIn5fW7D0c=
+<span class="org-variable-name">AllowedIPs</span> = 10.177.87.2
+<span class="org-variable-name">AllowedIPs</span> = 192.168.56.0/24
+<span class="org-variable-name">AllowedIPs</span> = 10.84.139.0/24
+
+<span class="org-comment-delimiter"># </span><span class="org-comment">dick</span>
+[<span class="org-type">Peer</span>]
+<span class="org-variable-name">PublicKey</span> = 4qd4xdRztZBKhFrX9jI/b4fnMzpKQ5qhg691hwYSsX8=
+<span class="org-variable-name">AllowedIPs</span> = 10.177.87.4
+
+<span class="org-comment-delimiter"># </span><span class="org-comment">dicks-razr</span>
+[<span class="org-type">Peer</span>]
+<span class="org-variable-name">PublicKey</span> = zho0qMxoLclJSQu4GeJEcMkk0hx4Q047OcNc8vOejVw=
+<span class="org-variable-name">AllowedIPs</span> = 10.177.87.6
</code></pre>
</div>
<p>
-The complete OpenVPN configuration for Front includes a <code>server</code>
-option, the <code>client-config-dir</code> option, the routes mentioned above,
-and the common options discussed in <a href="#org1ea6412">The VPN Service</a>.
+The configuration used on Dick's notebook when it is abroad looks like
+this:
</p>
<div class="org-src-container">
-<code>openvpn-front</code><pre class="src src-conf" id="org069808e"><code>server {{ public_vpn_net_and_mask }}
-client-config-dir /etc/openvpn/ccd
-<<openvpn-front-routes>>
-<<openvpn-dev-mode>>
-<<openvpn-keepalive>>
-<<openvpn-dns>>
-<<openvpn-drop-priv>>
-<<openvpn-crypt>>
-<<openvpn-max>>
-<<openvpn-debug>>
-ca /usr/local/share/ca-certificates/{{ domain_name }}.crt
-cert server.crt
-key server.key
-dh dh2048.pem
-tls-crypt shared.key
+WireGuard™ tunnel on Dick's notebook, used abroad<pre class="src src-conf"><code>[<span class="org-type">Interface</span>]
+<span class="org-variable-name">Address</span> = 10.177.87.3
+<span class="org-variable-name">PostUp</span> = wg set %i private-key /etc/wireguard/private-key
+<span class="org-variable-name">PostUp</span> = resolvectl dns %i 192.168.56.1
+<span class="org-variable-name">PostUp</span> = resolvectl domain %i small.private
+
+<span class="org-comment-delimiter"># </span><span class="org-comment">Front</span>
+[<span class="org-type">Peer</span>]
+<span class="org-variable-name">PublicKey</span> = S+6HaTnOwwhWgUGXjSBcPAvifKw+j8BDTRfq534gNW4=
+<span class="org-variable-name">AllowedIPs</span> = 10.177.87.1
+<span class="org-variable-name">AllowedIPs</span> = 10.177.87.0/24
+<span class="org-variable-name">AllowedIPs</span> = 192.168.56.0/24
+<span class="org-variable-name">AllowedIPs</span> = 10.84.139.0/24
</code></pre>
</div>
<p>
-Finally, here are the tasks (and handler) required to install and
-configure the OpenVPN server on Front.
+The following tasks install WireGuard™, configure it with
+<a href="#orgeeaba66"><q>private/front-wg0.conf</q></a>, and enable the service.
</p>
<div class="org-src-container">
<a href="roles_t/front/tasks/main.yml"><q>roles_t/front/tasks/main.yml</q></a><pre class="src src-conf"><code>
-- name: Install OpenVPN.
- become: yes
- <span class="org-variable-name">apt: pkg</span>=openvpn
-
-- name: Enable IP forwarding.
- become: yes
- sysctl:
- name: net.ipv4.ip_forward
- value: <span class="org-string">"1"</span>
- state: present
-
-- name: Create OpenVPN client configuration directory.
- become: yes
- file:
- path: /etc/openvpn/ccd
- state: directory
- notify: Restart OpenVPN.
-
-- name: Install OpenVPN client configuration for Core.
- become: yes
- copy:
- content: |
- <<openvpn-ccd-core>>
- dest: /etc/openvpn/ccd/core
- notify: Restart OpenVPN.
-
-- name: Disable former VPN clients.
+- name: Install WireGuard™.
become: yes
- copy:
- content: <span class="org-string">"disable\n"</span>
- dest: /etc/openvpn/ccd/{{ item }}
- loop: <span class="org-string">"{{ revoked }}"</span>
- tags: accounts
+ <span class="org-variable-name">apt: pkg</span>=wireguard
-- name: Install OpenVPN server certificate/key.
+- name: Configure WireGuard™.
become: yes
copy:
- src: ../Secret/CA/pki/{{ item.path }}.{{ item.typ }}
- dest: /etc/openvpn/server.{{ item.typ }}
- mode: <span class="org-string">"{{ item.mode }}"</span>
- loop:
- - { path: <span class="org-string">"issued/{{ domain_name }}"</span>, typ: crt,
- mode: <span class="org-string">"u=r,g=r,o=r"</span> }
- - { path: <span class="org-string">"private/{{ domain_name }}"</span>, typ: key,
- mode: <span class="org-string">"u=r,g=,o="</span> }
- notify: Restart OpenVPN.
-
-- name: Install OpenVPN secrets.
- become: yes
- copy:
- src: ../Secret/{{ item.src }}
- dest: /etc/openvpn/{{ item.dest }}
+ src: ../private/front-wg0.conf
+ dest: /etc/wireguard/wg0.conf
<span class="org-variable-name">mode: u</span>=r,g=,o=
- loop:
- - { src: front-dh2048.pem, dest: dh2048.pem }
- - { src: front-shared.key, dest: shared.key }
- notify: Restart OpenVPN.
-
-- name: Configure OpenVPN.
- become: yes
- copy:
- content: |
- <<openvpn-front>>
- dest: /etc/openvpn/server.conf
- <span class="org-variable-name">mode: u</span>=r,g=r,o=
- notify: Restart OpenVPN.
+ owner: root
+ group: root
+ notify: Restart WireGuard™.
-- name: Enable/Start OpenVPN.
+- name: Enable/Start WireGuard™ on boot.
become: yes
systemd:
- service: openvpn@server
+ service: wg-quick@wg0
enabled: yes
state: started
</code></pre>
<div class="org-src-container">
<a href="roles_t/front/handlers/main.yml"><q>roles_t/front/handlers/main.yml</q></a><pre class="src src-conf"><code>
-- name: Restart OpenVPN.
+- name: Restart WireGuard™.
become: yes
systemd:
- service: openvpn@server
+ service: wg-quick@wg0
state: restarted
</code></pre>
</div>
</div>
</div>
-<div id="outline-container-org4ec56d2" class="outline-3">
-<h3 id="org4ec56d2"><span class="section-number-3">7.15.</span> Configure Kamailio</h3>
+<div id="outline-container-org0a1eb35" class="outline-3">
+<h3 id="org0a1eb35"><span class="section-number-3">7.15.</span> Configure Kamailio</h3>
<div class="outline-text-3" id="text-7-15">
<p>
Front uses Kamailio to provide a SIP service on the public VPN so that
to listen <i>only</i> on Front's public VPN. The private name
<code>sip.small.private</code> resolves to this address for the convenience
of members configuring SIP clients. The server configuration
-specifies the actual IP, known here as <code>front_private_addr</code>.
+specifies the actual IP, known here as <code>front_wg_addr</code>.
</p>
<div class="org-src-container">
-<code>kamailio</code><pre class="src src-conf" id="orga8e875c"><code><span class="org-variable-name">listen</span>=udp:{{ front_private_addr }}:5060
+<code>kamailio</code><pre class="src src-conf" id="org21ef356"><code><span class="org-variable-name">listen</span>=udp:{{ front_wg_addr }}:5060
</code></pre>
</div>
<p>
Now the configuration drop concerns the network device on which
-Kamailio will be listening, the <code>tun</code> device created by OpenVPN. The
-added configuration settings inform Systemd that Kamailio should not
-be started before the <code>tun</code> device has appeared.
+Kamailio will be listening, the <code>wg0</code> device created by WireGuard™.
+The added configuration settings inform Systemd that Kamailio should
+not be started before the <code>wg0</code> device has appeared.
</p>
<div class="org-src-container">
path: /etc/systemd/system/kamailio.service.d
state: directory
-- name: Create Kamailio dependence on OpenVPN server.
+- name: Create Kamailio dependence on WireGuard™ interface.
become: yes
copy:
content: |
[<span class="org-type">Unit</span>]
- <span class="org-variable-name">Requires</span>=sys-devices-virtual-net-ovpn.device
- <span class="org-variable-name">After</span>=sys-devices-virtual-net-ovpn.device
+ <span class="org-variable-name">Requires</span>=sys-devices-virtual-net-wg0.device
+ <span class="org-variable-name">After</span>=sys-devices-virtual-net-wg0.device
dest: /etc/systemd/system/kamailio.service.d/depend.conf
notify: Reload Systemd.
</code></pre>
</div>
</div>
</div>
-<div id="outline-container-orge6d718e" class="outline-2">
-<h2 id="orge6d718e"><span class="section-number-2">8.</span> The Core Role</h2>
+<div id="outline-container-org03278f3" class="outline-2">
+<h2 id="org03278f3"><span class="section-number-2">8.</span> The Core Role</h2>
<div class="outline-text-2" id="text-8">
<p>
The <code>core</code> role configures many essential campus network services as
well as the institute's private cloud, so the core machine has
horsepower (CPUs and RAM) and large disks and is prepared with a
Debian install and remote access to a privileged, administrator's
-account. (For details, see <a href="#orgae4ce75">The Core Machine</a>.)
+account. (For details, see <a href="#org935c0f2">The Core Machine</a>.)
</p>
</div>
-<div id="outline-container-org9f10251" class="outline-3">
-<h3 id="org9f10251"><span class="section-number-3">8.1.</span> Include Particulars</h3>
+<div id="outline-container-org6c77b72" class="outline-3">
+<h3 id="org6c77b72"><span class="section-number-3">8.1.</span> Include Particulars</h3>
<div class="outline-text-3" id="text-8-1">
<p>
-The first task, as in <a href="#org1024107">The Front Role</a>, is to include the institute
+The first task, as in <a href="#orgd6c5c74">The Front Role</a>, is to include the institute
particulars and membership roll.
</p>
</div>
</div>
</div>
-<div id="outline-container-orgf5285f9" class="outline-3">
-<h3 id="orgf5285f9"><span class="section-number-3">8.2.</span> Configure Hostname</h3>
+<div id="outline-container-org3157392" class="outline-3">
+<h3 id="org3157392"><span class="section-number-3">8.2.</span> Configure Hostname</h3>
<div class="outline-text-3" id="text-8-2">
<p>
This task ensures that Core's <q>/etc/hostname</q> and <q>/etc/mailname</q> are
</div>
</div>
</div>
-<div id="outline-container-org06a86c3" class="outline-3">
-<h3 id="org06a86c3"><span class="section-number-3">8.3.</span> Configure Systemd Resolved</h3>
+<div id="outline-container-orge0057bb" class="outline-3">
+<h3 id="orge0057bb"><span class="section-number-3">8.3.</span> Configure Systemd Resolved</h3>
<div class="outline-text-3" id="text-8-3">
<p>
Core runs the campus name server, so Resolved is configured to use it
</div>
</div>
</div>
-<div id="outline-container-org9272d80" class="outline-3">
-<h3 id="org9272d80"><span class="section-number-3">8.4.</span> Configure Netplan</h3>
+<div id="outline-container-orga3b52f5" class="outline-3">
+<h3 id="orga3b52f5"><span class="section-number-3">8.4.</span> Configure Netplan</h3>
<div class="outline-text-3" id="text-8-4">
<p>
Core's network interface is statically configured using Netplan and an
<q>/etc/netplan/60-core.yaml</q> file. That file provides Core's address
on the private Ethernet, the campus name server and search domain, and
the default route through Gate to the campus ISP. A second route,
-through Core itself to Front, is advertised to other hosts, but is not
-created here. It is created by OpenVPN when Core connects to Front's
-VPN.
+through Core itself to Front, is advertised to other hosts.
</p>
<p>
</div>
</div>
</div>
-<div id="outline-container-orgf1c124e" class="outline-3">
-<h3 id="orgf1c124e"><span class="section-number-3">8.5.</span> Configure DHCP For the Private Ethernet</h3>
+<div id="outline-container-org3efb09c" class="outline-3">
+<h3 id="org3efb09c"><span class="section-number-3">8.5.</span> Configure DHCP For the Private Ethernet</h3>
<div class="outline-text-3" id="text-8-5">
<p>
Core speaks DHCP (Dynamic Host Configuration Protocol) using the
option broadcast-address 192.168.56.255;
option routers 192.168.56.2;
option ntp-servers 192.168.56.1;
- option rfc3442-routes 24, 10,177,86, 192,168,56,1, 0, 192,168,56,2;
+ option rfc3442-routes 24, 10,177,87, 192,168,56,1,
+ 0, 192,168,56,2;
}
<span class="org-type">host core</span> {
</div>
</div>
</div>
-<div id="outline-container-org40752b1" class="outline-3">
-<h3 id="org40752b1"><span class="section-number-3">8.6.</span> Configure BIND9</h3>
+<div id="outline-container-org0f63368" class="outline-3">
+<h3 id="org0f63368"><span class="section-number-3">8.6.</span> Configure BIND9</h3>
<div class="outline-text-3" id="text-8-6">
<p>
Core uses BIND9 to provide name service for the institute as described
-in <a href="#org189ccc3">The Name Service</a>. The configuration supports reverse name lookups,
+in <a href="#org7a5fce0">The Name Service</a>. The configuration supports reverse name lookups,
resolving many private network addresses to private domain names.
</p>
</p>
<div class="org-src-container">
-<code>bind-options</code><pre class="src src-conf" id="orgaf3e96b"><code><span class="org-type">acl </span><span class="org-string"><span class="org-type">"trusted"</span></span> {
+<code>bind-options</code><pre class="src src-conf" id="org29770de"><code><span class="org-type">acl </span><span class="org-string"><span class="org-type">"trusted"</span></span> {
{{ private_net_cidr }};
{{ wild_net_cidr }};
- {{ public_vpn_net_cidr }};
- {{ campus_vpn_net_cidr }};
+ {{ public_wg_net_cidr }};
+ {{ campus_wg_net_cidr }};
localhost;
};
</div>
<div class="org-src-container">
-<code>bind-local</code><pre class="src src-conf" id="org5ab6230"><code>include <span class="org-string">"/etc/bind/zones.rfc1918"</span>;
+<code>bind-local</code><pre class="src src-conf" id="orgf98ba3a"><code>include <span class="org-string">"/etc/bind/zones.rfc1918"</span>;
<span class="org-type">zone </span><span class="org-string"><span class="org-type">"{{ domain_priv }}."</span></span> {
type master;
file <span class="org-string">"/etc/bind/db.private"</span>;
};
-<span class="org-type">zone </span><span class="org-string"><span class="org-type">"{</span></span><span class="org-string">{ public_vpn_net_cidr | ansible.utils.ipaddr('revdns')</span>
+<span class="org-type">zone </span><span class="org-string"><span class="org-type">"{</span></span><span class="org-string">{ public_wg_net_cidr | ansible.utils.ipaddr('revdns')</span>
<span class="org-string"> </span><span class="org-string"><span class="org-type">| regex_replace('^0\.','') }}"</span></span> {
type master;
file <span class="org-string">"/etc/bind/db.public_vpn"</span>;
};
-<span class="org-type">zone </span><span class="org-string"><span class="org-type">"{</span></span><span class="org-string">{ campus_vpn_net_cidr | ansible.utils.ipaddr('revdns')</span>
+<span class="org-type">zone </span><span class="org-string"><span class="org-type">"{</span></span><span class="org-string">{ campus_wg_net_cidr | ansible.utils.ipaddr('revdns')</span>
<span class="org-string"> </span><span class="org-string"><span class="org-type">| regex_replace('^0\.','') }}"</span></span> {
type master;
file <span class="org-string">"/etc/bind/db.campus_vpn"</span>;
<span class="org-string">test IN CNAME core.small.private.</span>
<span class="org-string">live IN CNAME core.small.private.</span>
<span class="org-string">ntp IN CNAME core.small.private.</span>
-<span class="org-string">sip IN A 10.177.86.1</span>
+<span class="org-string">sip IN A 10.177.87.1</span>
<span class="org-string">;</span>
<span class="org-string">core IN A 192.168.56.1</span>
<span class="org-string">gate IN A 192.168.56.2</span>
</div>
</div>
</div>
-<div id="outline-container-org7b737c7" class="outline-3">
-<h3 id="org7b737c7"><span class="section-number-3">8.7.</span> Add Administrator to System Groups</h3>
+<div id="outline-container-org72f1783" class="outline-3">
+<h3 id="org72f1783"><span class="section-number-3">8.7.</span> Add Administrator to System Groups</h3>
<div class="outline-text-3" id="text-8-7">
<p>
The administrator often needs to read (directories of) log files owned
</div>
</div>
</div>
-<div id="outline-container-orgd39c2c0" class="outline-3">
-<h3 id="orgd39c2c0"><span class="section-number-3">8.8.</span> Configure Monkey</h3>
+<div id="outline-container-org97f4e6a" class="outline-3">
+<h3 id="org97f4e6a"><span class="section-number-3">8.8.</span> Configure Monkey</h3>
<div class="outline-text-3" id="text-8-8">
<p>
The small institute runs cron jobs and web scripts that generate
reports and perform checks. The un-privileged jobs are run by a
system account named <code>monkey</code>. One of Monkey's more important jobs on
Core is to run <code>rsync</code> to update the public web site on Front (as
-described in <a href="#org682dcfc">*Configure Apache2</a>).
+described in <a href="#orgd35be7c">*Configure Apache2</a>).
</p>
<div class="org-src-container">
</div>
</div>
</div>
-<div id="outline-container-orgb3bf17a" class="outline-3">
-<h3 id="orgb3bf17a"><span class="section-number-3">8.9.</span> Install Unattended Upgrades</h3>
+<div id="outline-container-orgd22f668" class="outline-3">
+<h3 id="orgd22f668"><span class="section-number-3">8.9.</span> Install Unattended Upgrades</h3>
<div class="outline-text-3" id="text-8-9">
<p>
The institute prefers to install security updates as soon as possible.
</div>
</div>
</div>
-<div id="outline-container-org9e31a35" class="outline-3">
-<h3 id="org9e31a35"><span class="section-number-3">8.10.</span> Install Expect</h3>
+<div id="outline-container-orgd7b5013" class="outline-3">
+<h3 id="orgd7b5013"><span class="section-number-3">8.10.</span> Install Expect</h3>
<div class="outline-text-3" id="text-8-10">
<p>
-The <code>expect</code> program is used by <a href="#org5db480b">The Institute Commands</a> to interact
+The <code>expect</code> program is used by <a href="#org1eb4766">The Institute Commands</a> to interact
with Nextcloud on the command line.
</p>
</div>
</div>
</div>
-<div id="outline-container-org2d28846" class="outline-3">
-<h3 id="org2d28846"><span class="section-number-3">8.11.</span> Configure User Accounts</h3>
+<div id="outline-container-org0770c20" class="outline-3">
+<h3 id="org0770c20"><span class="section-number-3">8.11.</span> Configure User Accounts</h3>
<div class="outline-text-3" id="text-8-11">
<p>
User accounts are created immediately so that backups can begin
-restoring as soon as possible. The <a href="#org6138e49">Account Management</a> chapter
+restoring as soon as possible. The <a href="#org944d8f7">Account Management</a> chapter
describes the <code>members</code> and <code>usernames</code> variables.
</p>
</div>
</div>
</div>
-<div id="outline-container-org3c35b95" class="outline-3">
-<h3 id="org3c35b95"><span class="section-number-3">8.12.</span> Install Server Certificate</h3>
+<div id="outline-container-orgb8f0771" class="outline-3">
+<h3 id="orgb8f0771"><span class="section-number-3">8.12.</span> Install Server Certificate</h3>
<div class="outline-text-3" id="text-8-12">
<p>
The servers on Core use the same certificate (and key) to authenticate
notify:
- Restart Postfix.
- Restart Dovecot.
- - Restart OpenVPN.
</code></pre>
</div>
</div>
</div>
-<div id="outline-container-org668ece0" class="outline-3">
-<h3 id="org668ece0"><span class="section-number-3">8.13.</span> Install NTP</h3>
+<div id="outline-container-org19f1511" class="outline-3">
+<h3 id="org19f1511"><span class="section-number-3">8.13.</span> Install NTP</h3>
<div class="outline-text-3" id="text-8-13">
<p>
Core uses NTP to provide a time synchronization service to the campus.
</div>
</div>
</div>
-<div id="outline-container-org296b053" class="outline-3">
-<h3 id="org296b053"><span class="section-number-3">8.14.</span> Configure Postfix on Core</h3>
+<div id="outline-container-org67c82ee" class="outline-3">
+<h3 id="org67c82ee"><span class="section-number-3">8.14.</span> Configure Postfix on Core</h3>
<div class="outline-text-3" id="text-8-14">
<p>
Core uses Postfix to provide SMTP service to the campus. The default
</ul>
<p>
-As discussed in <a href="#org73635f2">The Email Service</a> above, Core delivers email addressed
+As discussed in <a href="#org474e9f9">The Email Service</a> above, Core delivers email addressed
to any internal domain name locally, and uses its smarthost Front to
relay the rest. Core is reachable only on institute networks, so
there is little benefit in enabling TLS, but it does need to handle
</p>
<div class="org-src-container">
-<code>postfix-core-networks</code><pre class="src src-conf" id="org24ddb59"><code>- p: mynetworks
+<code>postfix-core-networks</code><pre class="src src-conf" id="org52d047a"><code>- p: mynetworks
v: >-
{{ private_net_cidr }}
- {{ public_vpn_net_cidr }}
- {{ campus_vpn_net_cidr }}
+ {{ public_wg_net_cidr }}
+ {{ campus_wg_net_cidr }}
127.0.0.0/8
[<span class="org-type">::ffff:127.0.0.0</span>]/104
[<span class="org-type">::1</span>]/128
</p>
<div class="org-src-container">
-<code>postfix-core-relayhost</code><pre class="src src-conf" id="orgabfee7f"><code>- { p: relayhost, v: <span class="org-string">"[{{ front_private_addr }}]"</span> }
+<code>postfix-core-relayhost</code><pre class="src src-conf" id="org73301ab"><code>- { p: relayhost, v: <span class="org-string">"[{{ front_wg_addr }}]"</span> }
</code></pre>
</div>
</p>
<div class="org-src-container">
-<code>postfix-transport</code><pre class="src src-conf" id="org7dc82f6"><code>.{{ domain_name }} local:$myhostname
+<code>postfix-transport</code><pre class="src src-conf" id="org4a27214"><code>.{{ domain_name }} local:$myhostname
.{{ domain_priv }} local:$myhostname
</code></pre>
</div>
</p>
<div class="org-src-container">
-<code>postfix-core</code><pre class="src src-conf" id="org7e631cc"><code><<postfix-relaying>>
+<code>postfix-core</code><pre class="src src-conf" id="org7c21d40"><code><<postfix-relaying>>
- { p: smtpd_tls_security_level, v: none }
- { p: smtp_tls_security_level, v: none }
<<postfix-message-size>>
</div>
</div>
</div>
-<div id="outline-container-org8d9476a" class="outline-3">
-<h3 id="org8d9476a"><span class="section-number-3">8.15.</span> Configure Private Email Aliases</h3>
+<div id="outline-container-org66d1a08" class="outline-3">
+<h3 id="org66d1a08"><span class="section-number-3">8.15.</span> Configure Private Email Aliases</h3>
<div class="outline-text-3" id="text-8-15">
<p>
The institute's Core needs to deliver email addressed to institute
-aliases including those advertised on the campus web site, in VPN
+aliases including those advertised on the campus web site, in X.509
certificates, etc. System daemons like <code>cron(8)</code> may also send email
to e.g. <code>monkey</code>. The following aliases are installed in
<q>/etc/aliases</q> with a special marker so that additional blocks can be
</div>
</div>
</div>
-<div id="outline-container-org77e47c0" class="outline-3">
-<h3 id="org77e47c0"><span class="section-number-3">8.16.</span> Configure Dovecot IMAPd</h3>
+<div id="outline-container-orgfbad8af" class="outline-3">
+<h3 id="orgfbad8af"><span class="section-number-3">8.16.</span> Configure Dovecot IMAPd</h3>
<div class="outline-text-3" id="text-8-16">
<p>
Core uses Dovecot's IMAPd to store and serve member emails. As on
networks, but helps to ensure privacy even when members accidentally
attempt connections from outside the private networks. For more
information about Core's role in the institute's email services, see
-<a href="#org73635f2">The Email Service</a>.
+<a href="#org474e9f9">The Email Service</a>.
</p>
<p>
<q>README.Debian</q> (in <q>/usr/share/dovecot-core/</q>) but replaces the
default "snake oil" certificate with another, signed by the institute.
(For more information about the institute's X.509 certificates, see
-<a href="#org1c1be46">Keys</a>.)
+<a href="#org8244014">Keys</a>.)
</p>
<p>
</div>
</div>
</div>
-<div id="outline-container-orgea0cf88" class="outline-3">
-<h3 id="orgea0cf88"><span class="section-number-3">8.17.</span> Configure Fetchmail</h3>
+<div id="outline-container-orge7d5910" class="outline-3">
+<h3 id="orge7d5910"><span class="section-number-3">8.17.</span> Configure Fetchmail</h3>
<div class="outline-text-3" id="text-8-17">
<p>
Core runs a <code>fetchmail</code> for each member of the institute. Individual
</p>
<div class="org-src-container">
-<code>fetchmail-config</code><pre class="src src-conf" id="orge18ab2e"><code><span class="org-comment-delimiter"># </span><span class="org-comment">Permissions on this file may be no greater than 0600.</span>
+<code>fetchmail-config</code><pre class="src src-conf" id="org486cf27"><code><span class="org-comment-delimiter"># </span><span class="org-comment">Permissions on this file may be no greater than 0600.</span>
set no bouncemail
set no spambounce
set no syslog
<span class="org-comment-delimiter">#</span><span class="org-comment">set logfile /home/{{ item }}/.fetchmail.log</span>
-poll {{ front_private_addr }} protocol imap timeout 15
+poll {{ front_wg_addr }} protocol imap timeout 15
username {{ item }}
password <span class="org-string">"{{ members[item].password_fetchmail }}"</span> fetchall
ssl sslproto tls1.2+ sslcertck sslcommonname {{ domain_name }}
</p>
<div class="org-src-container">
-<code>fetchmail-service</code><pre class="src src-conf" id="orga4027c9"><code>[<span class="org-type">Unit</span>]
+<code>fetchmail-service</code><pre class="src src-conf" id="org7df6879"><code>[<span class="org-type">Unit</span>]
<span class="org-variable-name">Description</span>=Fetchmail --idle task for {{ item }}.
<span class="org-variable-name">AssertPathExists</span>=/home/{{ item }}/.fetchmailrc
-<span class="org-variable-name">After</span>=openvpn@front.service
-<span class="org-variable-name">Wants</span>=sys-devices-virtual-net-ovpn.device
+<span class="org-variable-name">After</span>=wg-quick@wg0.service
+<span class="org-variable-name">Wants</span>=sys-devices-virtual-net-wg0.device
[<span class="org-type">Service</span>]
<span class="org-variable-name">User</span>={{ item }}
</div>
</div>
</div>
-<div id="outline-container-orgbdf8137" class="outline-3">
-<h3 id="orgbdf8137"><span class="section-number-3">8.18.</span> Configure Apache2 <a id="org682dcfc"></a></h3>
+<div id="outline-container-org51c6dbc" class="outline-3">
+<h3 id="org51c6dbc"><span class="section-number-3">8.18.</span> Configure Apache2 <a id="orgd35be7c"></a></h3>
<div class="outline-text-3" id="text-8-18">
<p>
This is the small institute's campus web server. It hosts several web
-sites as described in <a href="#orgf659a90">The Web Services</a>.
+sites as described in <a href="#org4e9f4f9">The Web Services</a>.
</p>
<table border="2" cellspacing="0" cellpadding="6" rules="groups" frame="hsides">
</p>
<div class="org-src-container">
-<code>apache-userdir-core</code><pre class="src src-conf" id="orge344fe6"><code>UserDir Public/HTML
+<code>apache-userdir-core</code><pre class="src src-conf" id="orgc8c0ba8"><code>UserDir Public/HTML
<Directory /home/*/Public/HTML/>
Require all granted
AllowOverride None
</p>
<div class="org-src-container">
-<code>apache-live</code><pre class="src src-conf" id="org6306943"><code><VirtualHost *:80>
+<code>apache-live</code><pre class="src src-conf" id="org0dc13cb"><code><VirtualHost *:80>
ServerName live
ServerAlias live.{{ domain_priv }}
ServerAdmin webmaster@core.{{ domain_priv }}
</p>
<div class="org-src-container">
-<code>apache-test</code><pre class="src src-conf" id="org455bb19"><code><VirtualHost *:80>
+<code>apache-test</code><pre class="src src-conf" id="org0b1b4a5"><code><VirtualHost *:80>
ServerName test
ServerAlias test.{{ domain_priv }}
ServerAdmin webmaster@core.{{ domain_priv }}
</p>
<div class="org-src-container">
-<code>apache-campus</code><pre class="src src-conf" id="org474a92e"><code><VirtualHost *:80>
+<code>apache-campus</code><pre class="src src-conf" id="org96d6bd5"><code><VirtualHost *:80>
ServerName www
ServerAlias www.{{ domain_priv }}
ServerAdmin webmaster@core.{{ domain_priv }}
</div>
</div>
</div>
-<div id="outline-container-org88cbb1f" class="outline-3">
-<h3 id="org88cbb1f"><span class="section-number-3">8.19.</span> Configure Website Updates</h3>
+<div id="outline-container-org97cebd9" class="outline-3">
+<h3 id="org97cebd9"><span class="section-number-3">8.19.</span> Configure Website Updates</h3>
<div class="outline-text-3" id="text-8-19">
<p>
Monkey on Core runs <q>/usr/local/sbin/webupdate</q> every 15 minutes via a
</p>
<div class="org-src-container">
-<a href="private/webupdate"><q>private/webupdate</q></a><pre class="src src-sh" id="org56430fc"><code><span class="org-comment-delimiter">#</span><span class="org-comment">!/bin/</span><span class="org-keyword">bash</span><span class="org-comment"> -e</span>
+<a href="private/webupdate"><q>private/webupdate</q></a><pre class="src src-sh" id="orgc32ac69"><code><span class="org-comment-delimiter">#</span><span class="org-comment">!/bin/</span><span class="org-keyword">bash</span><span class="org-comment"> -e</span>
<span class="org-comment-delimiter">#</span>
<span class="org-comment-delimiter"># </span><span class="org-comment">DO NOT EDIT. This file was tangled from institute.org.</span>
<p>
The following tasks install the <q>webupdate</q> script from <a href="private/"><q>private/</q></a>,
and create Monkey's <code>cron</code> job. An example <q>webupdate</q> script is
-provided <a href="#org56430fc">here</a>.
+provided <a href="#orgc32ac69">here</a>.
</p>
<div class="org-src-container">
</div>
</div>
</div>
-<div id="outline-container-org31f8027" class="outline-3">
-<h3 id="org31f8027"><span class="section-number-3">8.20.</span> Configure OpenVPN Connection to Front</h3>
+<div id="outline-container-orgd2fde7b" class="outline-3">
+<h3 id="orgd2fde7b"><span class="section-number-3">8.20.</span> Configure Core WireGuard™ Interface</h3>
<div class="outline-text-3" id="text-8-20">
<p>
-Core connects to Front's public VPN to provide members abroad with a
-route to the campus networks. As described in the configuration of
-Front's OpenVPN service, Front expects Core to connect using a client
-certificate with Common Name <code>Core</code>.
+Core connects to Front's WireGuard™ service to provide members abroad
+with a route to the campus networks. As described in <a href="#org290050a">Configure Public
+WireGuard™ Subnet</a> for Front, Core is expected to forward packets from/to the
+private networks.
</p>
<p>
-Core's OpenVPN client configuration uses the Debian default Systemd
-service unit to keep Core connected to Front. The configuration
-is installed in <q>/etc/openvpn/front.conf</q> so the Systemd service is
-called <code>openvpn@front</code>.
+The following example <a href="private/core-wg0.conf"><q>private/core-wg0.conf</q></a> configuration recognizes
+Front by its public key, <code>S+6HaT</code>, looking for it at the institute's
+public IP address and a special port.
</p>
<div class="org-src-container">
-<code>openvpn-core</code><pre class="src src-conf" id="org7db87cf"><code>client
-dev-type tun
-dev ovpn
-remote {{ front_addr }}
-nobind
-<<openvpn-drop-priv>>
-<<openvpn-crypt>>
-remote-cert-tls server
-verify-x509-name {{ domain_name }} name
-verb 3
-ca /usr/local/share/ca-certificates/{{ domain_name }}.crt
-cert client.crt
-key client.key
-tls-crypt shared.key
+<a href="private/core-wg0.conf"><q>private/core-wg0.conf</q></a><pre class="src src-conf"><code>[<span class="org-type">Interface</span>]
+<span class="org-variable-name">Address</span> = 10.177.87.2
+<span class="org-variable-name">PostUp</span> = wg set %i private-key /etc/wireguard/private-key
+
+<span class="org-comment-delimiter"># </span><span class="org-comment">Front</span>
+[<span class="org-type">Peer</span>]
+<span class="org-variable-name">EndPoint</span> = 192.168.15.5:39608
+<span class="org-variable-name">PublicKey</span> = S+6HaTnOwwhWgUGXjSBcPAvifKw+j8BDTRfq534gNW4=
+<span class="org-variable-name">AllowedIPs</span> = 10.177.87.1
+<span class="org-variable-name">AllowedIPs</span> = 10.177.87.0/24
</code></pre>
</div>
<p>
-The tasks that install and configure the OpenVPN client configuration
-for Core.
+The following tasks install WireGuard™, configure it with
+<a href="private/core-wg0.conf"><q>private/core-wg0.conf</q></a>, and enable the service.
</p>
<div class="org-src-container">
<a href="roles_t/core/tasks/main.yml"><q>roles_t/core/tasks/main.yml</q></a><pre class="src src-conf"><code>
-- name: Install OpenVPN.
- become: yes
- <span class="org-variable-name">apt: pkg</span>=openvpn
-
-- name: Enable IP forwarding.
+- name: Install WireGuard™.
become: yes
- sysctl:
- name: net.ipv4.ip_forward
- value: <span class="org-string">"1"</span>
- state: present
+ <span class="org-variable-name">apt: pkg</span>=wireguard
-- name: Install OpenVPN secret.
+- name: Configure WireGuard™.
become: yes
copy:
- src: ../Secret/front-shared.key
- dest: /etc/openvpn/shared.key
+ src: ../private/core-wg0.conf
+ dest: /etc/wireguard/wg0.conf
<span class="org-variable-name">mode: u</span>=r,g=,o=
- notify: Restart OpenVPN.
-
-- name: Install OpenVPN client certificate/key.
- become: yes
- copy:
- src: ../Secret/CA/pki/{{ item.path }}.{{ item.typ }}
- dest: /etc/openvpn/client.{{ item.typ }}
- mode: <span class="org-string">"{{ item.mode }}"</span>
- loop:
- - { path: <span class="org-string">"issued/core"</span>, typ: crt, mode: <span class="org-string">"u=r,g=r,o=r"</span> }
- - { path: <span class="org-string">"private/core"</span>, typ: key, mode: <span class="org-string">"u=r,g=,o="</span> }
- notify: Restart OpenVPN.
-
-- name: Configure OpenVPN.
- become: yes
- copy:
- content: |
- <<openvpn-core>>
- dest: /etc/openvpn/front.conf
- <span class="org-variable-name">mode: u</span>=r,g=r,o=
- notify: Restart OpenVPN.
+ owner: root
+ group: root
+ notify: Restart WireGuard™.
-- name: Enable/Start OpenVPN.
+- name: Enable/Start WireGuard™ on boot.
become: yes
systemd:
- service: openvpn@front
- state: started
+ service: wg-quick@wg0
enabled: yes
+ state: started
</code></pre>
</div>
<div class="org-src-container">
<a href="roles_t/core/handlers/main.yml"><q>roles_t/core/handlers/main.yml</q></a><pre class="src src-conf"><code>
-- name: Restart OpenVPN.
+- name: Restart WireGuard™.
become: yes
systemd:
- service: openvpn@front
+ service: wg-quick@wg0
state: restarted
</code></pre>
</div>
</div>
</div>
-<div id="outline-container-org6cb627e" class="outline-3">
-<h3 id="org6cb627e"><span class="section-number-3">8.21.</span> Configure NAGIOS</h3>
+<div id="outline-container-org7a1b1ea" class="outline-3">
+<h3 id="org7a1b1ea"><span class="section-number-3">8.21.</span> Configure NAGIOS</h3>
<div class="outline-text-3" id="text-8-21">
<p>
Core runs a <code>nagios4</code> server to monitor "services" on institute hosts.
backrefs: yes
loop:
- { regexp: <span class="org-string">"^( *cfg_file *= *localhost.cfg)"</span>, line: <span class="org-string">"# \\1"</span> }
- - { regexp: <span class="org-string">"^( *admin_email *= *)"</span>, line: <span class="org-string">"\\1{{ ansible_user }}@localhost"</span> }
+ <span class="org-type">-</span> { regexp: <span class="org-string">"^( *admin_email *= *)"</span>,
+ line: <span class="org-string">"\\1{{ ansible_user }}@localhost"</span> }
notify: Reload NAGIOS4.
- name: Configure NAGIOS4 contacts.
</code></pre>
</div>
</div>
-<div id="outline-container-org74a0c8d" class="outline-4">
-<h4 id="org74a0c8d"><span class="section-number-4">8.21.1.</span> Configure NAGIOS Monitors for Core</h4>
+<div id="outline-container-org0d76538" class="outline-4">
+<h4 id="org0d76538"><span class="section-number-4">8.21.1.</span> Configure NAGIOS Monitors for Core</h4>
<div class="outline-text-4" id="text-8-21-1">
<p>
The first block in <q>nagios.cfg</q> specifies monitors for services on
</div>
</div>
</div>
-<div id="outline-container-orgb9ed6e7" class="outline-4">
-<h4 id="orgb9ed6e7"><span class="section-number-4">8.21.2.</span> Custom NAGIOS Monitor <code>inst_sensors</code></h4>
+<div id="outline-container-orgcdd5318" class="outline-4">
+<h4 id="orgcdd5318"><span class="section-number-4">8.21.2.</span> Custom NAGIOS Monitor <code>inst_sensors</code></h4>
<div class="outline-text-4" id="text-8-21-2">
<p>
The <code>check_sensors</code> plugin is included in the package
<span class="org-builtin">echo</span> <span class="org-string">""</span>
print_usage
<span class="org-builtin">echo</span> <span class="org-string">""</span>
- <span class="org-builtin">echo</span> <span class="org-string">"This plugin checks hardware status using the lm_sensors package."</span>
+ <span class="org-builtin">echo</span> -n <span class="org-string">"This plugin checks hardware status"</span>
+ <span class="org-builtin">echo</span> <span class="org-string">" using the lm_sensors package."</span>
<span class="org-builtin">echo</span> <span class="org-string">""</span>
support
<span class="org-keyword">exit</span> $<span class="org-variable-name">STATE_OK</span>
</div>
</div>
</div>
-<div id="outline-container-org4c8f989" class="outline-4">
-<h4 id="org4c8f989"><span class="section-number-4">8.21.3.</span> Configure NAGIOS Monitors for Remote Hosts</h4>
+<div id="outline-container-org2d1e468" class="outline-4">
+<h4 id="org2d1e468"><span class="section-number-4">8.21.3.</span> Configure NAGIOS Monitors for Remote Hosts</h4>
<div class="outline-text-4" id="text-8-21-3">
<p>
The following sections contain code blocks specifying monitors for
commands are defined in code blocks interleaved with the blocks that
monitor them. The command blocks are appended to <q>nrpe.cfg</q> and the
monitoring blocks to <q>nagios.cfg</q>. The <q>nrpe.cfg</q> file is installed
-on each campus host by the campus role's <a href="#org356ce5d">Configure NRPE</a> tasks.
+on each campus host by the campus role's <a href="#orgc703c4f">Configure NRPE</a> tasks.
</p>
</div>
</div>
-<div id="outline-container-org5f33bb4" class="outline-4">
-<h4 id="org5f33bb4"><span class="section-number-4">8.21.4.</span> Configure NAGIOS Monitors for Gate</h4>
+<div id="outline-container-org68c6c7d" class="outline-4">
+<h4 id="org68c6c7d"><span class="section-number-4">8.21.4.</span> Configure NAGIOS Monitors for Gate</h4>
<div class="outline-text-4" id="text-8-21-4">
<p>
Define the monitored host, <code>gate</code>. Monitor its response to network
</div>
</div>
</div>
-<div id="outline-container-org2323be4" class="outline-3">
-<h3 id="org2323be4"><span class="section-number-3">8.22.</span> Configure Backups</h3>
+<div id="outline-container-org89c82e3" class="outline-3">
+<h3 id="org89c82e3"><span class="section-number-3">8.22.</span> Configure Backups</h3>
<div class="outline-text-3" id="text-8-22">
<p>
The following task installs the <q>backup</q> script from <a href="private/"><q>private/</q></a>. An
-example script is provided in <a href="#org8d148e8">here</a>.
+example script is provided in <a href="#orgf388eed">here</a>.
</p>
<div class="org-src-container">
</div>
</div>
</div>
-<div id="outline-container-org640e4f7" class="outline-3">
-<h3 id="org640e4f7"><span class="section-number-3">8.23.</span> Configure Nextcloud</h3>
+<div id="outline-container-org68d4766" class="outline-3">
+<h3 id="org68d4766"><span class="section-number-3">8.23.</span> Configure Nextcloud</h3>
<div class="outline-text-3" id="text-8-23">
<p>
Core runs Nextcloud to provide a private institute cloud, as described
-in <a href="#orgd0834d2">The Cloud Service</a>. Installing, restoring (from backup), and
+in <a href="#org582454b">The Cloud Service</a>. Installing, restoring (from backup), and
upgrading Nextcloud are manual processes documented in <a href="https://docs.nextcloud.com/server/latest/admin_manual/maintenance/">The Nextcloud
Admin Manual, Maintenance</a>. However Ansible can help prepare Core
before an install or restore, and perform basic security checks
afterwards.
</p>
</div>
-<div id="outline-container-orgea15814" class="outline-4">
-<h4 id="orgea15814"><span class="section-number-4">8.23.1.</span> Prepare Core For Nextcloud</h4>
+<div id="outline-container-org2ce637f" class="outline-4">
+<h4 id="org2ce637f"><span class="section-number-4">8.23.1.</span> Prepare Core For Nextcloud</h4>
<div class="outline-text-4" id="text-8-23-1">
<p>
The Ansible code contained herein prepares Core to run Nextcloud by
The following task would work (<code>mysql_user</code> supports
<code>check_implicit_admin</code>) <i>but</i> the <code>nextcloud</code> database was not created
above. Thus both database and user are created manually, with SQL
-given in the <a href="#orgb7a7660">8.23.5</a> subsection below, before <code>occ
+given in the <a href="#org50fbfc3">8.23.5</a> subsection below, before <code>occ
maintenance:install</code> can run.
</p>
</div>
</div>
</div>
-<div id="outline-container-org3f0d821" class="outline-4">
-<h4 id="org3f0d821"><span class="section-number-4">8.23.2.</span> Configure PHP</h4>
+<div id="outline-container-org29e69fb" class="outline-4">
+<h4 id="org29e69fb"><span class="section-number-4">8.23.2.</span> Configure PHP</h4>
<div class="outline-text-4" id="text-8-23-2">
<p>
The following tasks set a number of PHP parameters for better
</div>
</div>
</div>
-<div id="outline-container-orgdb3439f" class="outline-4">
-<h4 id="orgdb3439f"><span class="section-number-4">8.23.3.</span> Create <q>/Nextcloud/</q></h4>
+<div id="outline-container-org7ba56a3" class="outline-4">
+<h4 id="org7ba56a3"><span class="section-number-4">8.23.3.</span> Create <q>/Nextcloud/</q></h4>
<div class="outline-text-4" id="text-8-23-3">
<p>
The Ansible tasks up to this point have completed Core's LAMP stack
</div>
</div>
</div>
-<div id="outline-container-org3c8ed7b" class="outline-4">
-<h4 id="org3c8ed7b"><span class="section-number-4">8.23.4.</span> Restore Nextcloud</h4>
+<div id="outline-container-orgabfc848" class="outline-4">
+<h4 id="orgabfc848"><span class="section-number-4">8.23.4.</span> Restore Nextcloud</h4>
<div class="outline-text-4" id="text-8-23-4">
<p>
Restoring Nextcloud in the newly created <q>/Nextcloud/</q> presumably
last dump was made February 20th 2022 and thus was saved in
<q>/Nextcloud/20220220.bak</q>. The database will need to be
created first as when installing Nextcloud. The appropriate SQL are
-given in <a href="#orgb7a7660">Install Nextcloud</a> below.
+given in <a href="#org50fbfc3">Install Nextcloud</a> below.
</p>
<div class="org-src-container">
</p>
</div>
</div>
-<div id="outline-container-orgb7a7660" class="outline-4">
-<h4 id="orgb7a7660"><span class="section-number-4">8.23.5.</span> Install Nextcloud</h4>
+<div id="outline-container-org50fbfc3" class="outline-4">
+<h4 id="org50fbfc3"><span class="section-number-4">8.23.5.</span> Install Nextcloud</h4>
<div class="outline-text-4" id="text-8-23-5">
<p>
Installing Nextcloud in the newly created <q>/Nextcloud/</q> starts with
</p>
</div>
</div>
-<div id="outline-container-orgdc9a64a" class="outline-4">
-<h4 id="orgdc9a64a"><span class="section-number-4">8.23.6.</span> Afterwards</h4>
+<div id="outline-container-orgb106f73" class="outline-4">
+<h4 id="orgb106f73"><span class="section-number-4">8.23.6.</span> Afterwards</h4>
<div class="outline-text-4" id="text-8-23-6">
<p>
Whether Nextcloud was restored or installed, there are a few things
</div>
</div>
</div>
-<div id="outline-container-org936487d" class="outline-2">
-<h2 id="org936487d"><span class="section-number-2">9.</span> The Gate Role</h2>
+<div id="outline-container-orgbf7ae97" class="outline-2">
+<h2 id="orgbf7ae97"><span class="section-number-2">9.</span> The Gate Role</h2>
<div class="outline-text-2" id="text-9">
<p>
The <code>gate</code> role configures the services expected at the campus gate:
access to the private Ethernet from the untrusted Ethernet (e.g. a
campus Wi-Fi AP) via VPN, and access to the Internet via NAT. The
-gate machine uses three network interfaces (see <a href="#org40481f7">The Gate Machine</a>)
+gate machine uses three network interfaces (see <a href="#org817fd99">The Gate Machine</a>)
configured with persistent names used in its firewall rules.
</p>
configurations, etc.
</p>
</div>
-<div id="outline-container-orge1f832f" class="outline-3">
-<h3 id="orge1f832f"><span class="section-number-3">9.1.</span> Include Particulars</h3>
+<div id="outline-container-org61c66db" class="outline-3">
+<h3 id="org61c66db"><span class="section-number-3">9.1.</span> Include Particulars</h3>
<div class="outline-text-3" id="text-9-1">
<p>
The following should be familiar boilerplate by now.
</div>
</div>
</div>
-<div id="outline-container-org73026e0" class="outline-3">
-<h3 id="org73026e0"><span class="section-number-3">9.2.</span> Configure Netplan <a id="orgfd4af3f"></a></h3>
+<div id="outline-container-orgf42ab1d" class="outline-3">
+<h3 id="orgf42ab1d"><span class="section-number-3">9.2.</span> Configure Netplan <a id="org2c03bc2"></a></h3>
<div class="outline-text-3" id="text-9-2">
<p>
Gate's network interfaces are configured using Netplan and two files.
addresses: [ {{ core_addr }} ]
search: [ {{ domain_priv }} ]
routes:
- - to: {{ public_vpn_net_cidr }}
+ - to: {{ public_wg_net_cidr }}
via: {{ core_addr }}
wild:
match:
</p>
</div>
</div>
-<div id="outline-container-orge89b196" class="outline-3">
-<h3 id="orge89b196"><span class="section-number-3">9.3.</span> UFW Rules</h3>
+<div id="outline-container-org5f6e57c" class="outline-3">
+<h3 id="org5f6e57c"><span class="section-number-3">9.3.</span> UFW Rules</h3>
<div class="outline-text-3" id="text-9-3">
<p>
Gate uses the Uncomplicated FireWall (UFW) to install its packet
The default policy settings in <q>/etc/default/ufw</q> are <code>ACCEPT</code> and
<code>ACCEPT</code> for input and output, and <code>DROP</code> for forwarded packets.
Forwarding was enabled in the kernel previously (when configuring
-OpenVPN) using Ansible's <code>sysctl</code> module. It does not need to be set
-in <q>/etc/ufw/sysctl.conf</q>.
+WireGuard™) using Ansible's <code>sysctl</code> module. It does not need to be
+set in <q>/etc/ufw/sysctl.conf</q>.
</p>
<p>
</p>
<div class="org-src-container">
-<code>ufw-nat</code><pre class="src src-conf" id="org2b6b37d"><code>-A POSTROUTING -s {{ private_net_cidr }} -o isp -j MASQUERADE
+<code>ufw-nat</code><pre class="src src-conf" id="org321b8da"><code>-A POSTROUTING -s {{ private_net_cidr }} -o isp -j MASQUERADE
-A POSTROUTING -s {{ wild_net_cidr }} -o isp -j MASQUERADE
</code></pre>
</div>
</p>
<div class="org-src-container">
-<code>ufw-forward-nat</code><pre class="src src-conf" id="org63ab446"><code>-A FORWARD -i lan -o isp -j ACCEPT
+<code>ufw-forward-nat</code><pre class="src src-conf" id="org8de8823"><code>-A FORWARD -i lan -o isp -j ACCEPT
-A FORWARD -i wild -o isp -j ACCEPT
-A FORWARD -i isp -o lan {{ ACCEPT_RELATED }}
-A FORWARD -i isp -o wild {{ ACCEPT_RELATED }}
<p>
Forwarding rules are also needed to route packets from the campus VPN
-(the <code>ovpn</code> tunnel device) to the institute's LAN and back. The
-public VPN on Front will also be included since its packets arrive at
-Gate's <code>lan</code> interface, coming from Core. Thus forwarding between
+(the <code>wg0</code> WireGuard™ tunnel device) to the institute's LAN and back.
+The public VPN on Front will also be included since its packets arrive
+at Gate's <code>lan</code> interface, coming from Core. Thus forwarding between
public and campus VPNs is also allowed.
</p>
<div class="org-src-container">
-<code>ufw-forward-private</code><pre class="src src-conf" id="org9b9c5a4"><code>-A FORWARD -i lan -o ovpn -j ACCEPT
--A FORWARD -i ovpn -o lan -j ACCEPT
+<code>ufw-forward-private</code><pre class="src src-conf" id="org269a64d"><code>-A FORWARD -i lan -o wg0 -j ACCEPT
+-A FORWARD -i wg0 -o lan -j ACCEPT
</code></pre>
</div>
<p>
Note that there are no forwarding rules to allow packets to pass from
-the <code>wild</code> device to the <code>lan</code> device, just the <code>ovpn</code> device.
+the <code>wild</code> device to the <code>lan</code> device, just the <code>wg0</code> device.
</p>
</div>
</div>
-<div id="outline-container-org036ac74" class="outline-3">
-<h3 id="org036ac74"><span class="section-number-3">9.4.</span> Install UFW</h3>
+<div id="outline-container-org16439c8" class="outline-3">
+<h3 id="org16439c8"><span class="section-number-3">9.4.</span> Install UFW</h3>
<div class="outline-text-3" id="text-9-4">
<p>
The following tasks install the Uncomplicated Firewall (UFW), set its
</div>
</div>
</div>
-<div id="outline-container-org43b50b8" class="outline-3">
-<h3 id="org43b50b8"><span class="section-number-3">9.5.</span> Configure DHCP For The Wild Ethernet</h3>
+<div id="outline-container-org6599d39" class="outline-3">
+<h3 id="org6599d39"><span class="section-number-3">9.5.</span> Configure DHCP For The Wild Ethernet</h3>
<div class="outline-text-3" id="text-9-5">
<p>
To accommodate commodity Wi-Fi access points, as well as wired IoT
</p>
</div>
</div>
-<div id="outline-container-org435e716" class="outline-3">
-<h3 id="org435e716"><span class="section-number-3">9.6.</span> Install Server Certificate</h3>
+<div id="outline-container-org3e594c1" class="outline-3">
+<h3 id="org3e594c1"><span class="section-number-3">9.6.</span> Configure Campus WireGuard™ Subnet</h3>
<div class="outline-text-3" id="text-9-6">
<p>
-The (OpenVPN) server on Gate uses an institute certificate (and key)
-to authenticate itself to its clients. It uses the <q>/etc/server.crt</q>
-and <q>/etc/server.key</q> files just because the other servers (on Core
-and Front) do.
+Gate uses WireGuard™ to provide a campus VPN service. Gate's routes
+and firewall rules allow packets to be forwarded to/from the
+institute's private networks: the private Ethernet and the public VPN.
+(It should <i>not</i> forward packets to/from the wild Ethernet.) The only
+additional route Gate needs is to the public VPN via Core. The rest
+(private Ethernet and campus VPN) are directly connected.
+</p>
+
+<p>
+The following example <a href="#org29145ff"><q>private/gate-wg0.conf</q></a> configuration recognizes
+a wired IoT appliance, Dick's notebook and his replacement phone,
+assigning them the host numbers 3, 4 and 6 respectively.
</p>
<div class="org-src-container">
-<a href="roles_t/gate/tasks/main.yml"><q>roles_t/gate/tasks/main.yml</q></a><pre class="src src-conf"><code>
-- name: Install server certificate/key.
- become: yes
- copy:
- src: ../Secret/CA/pki/{{ item.path }}.{{ item.typ }}
- dest: /etc/server.{{ item.typ }}
- mode: <span class="org-string">"{{ item.mode }}"</span>
- loop:
- - { path: <span class="org-string">"issued/gate.{{ domain_priv }}"</span>, typ: crt,
- mode: <span class="org-string">"u=r,g=r,o=r"</span> }
- - { path: <span class="org-string">"private/gate.{{ domain_priv }}"</span>, typ: key,
- mode: <span class="org-string">"u=r,g=,o="</span> }
- notify: Restart OpenVPN.
+<q>private/gate-wg0.conf</q><pre class="src src-conf" id="org29145ff"><code>[<span class="org-type">Interface</span>]
+<span class="org-variable-name">Address</span> = 10.84.139.1/24
+<span class="org-variable-name">ListenPort</span> = 51820
+<span class="org-variable-name">PostUp</span> = wg set %i private-key /etc/wireguard/private-key
+
+<span class="org-comment-delimiter"># </span><span class="org-comment">thing</span>
+[<span class="org-type">Peer</span>]
+<span class="org-variable-name">PublicKey</span> = LdsCsgfjKCfd5+VKS+Q/dQhWO8NRNygByDO2VxbXlSQ=
+<span class="org-variable-name">AllowedIPs</span> = 10.84.139.3
+
+<span class="org-comment-delimiter"># </span><span class="org-comment">dick</span>
+[<span class="org-type">Peer</span>]
+<span class="org-variable-name">PublicKey</span> = 4qd4xdRztZBKhFrX9jI/b4fnMzpKQ5qhg691hwYSsX8=
+<span class="org-variable-name">AllowedIPs</span> = 10.84.139.4
+
+<span class="org-comment-delimiter"># </span><span class="org-comment">dicks-razr</span>
+[<span class="org-type">Peer</span>]
+<span class="org-variable-name">PublicKey</span> = zho0qMxoLclJSQu4GeJEcMkk0hx4Q047OcNc8vOejVw=
+<span class="org-variable-name">AllowedIPs</span> = 10.84.139.6
</code></pre>
</div>
-</div>
-</div>
-<div id="outline-container-org877d1b2" class="outline-3">
-<h3 id="org877d1b2"><span class="section-number-3">9.7.</span> Configure OpenVPN</h3>
-<div class="outline-text-3" id="text-9-7">
+
<p>
-Gate uses OpenVPN to provide the institute's campus VPN service. Its
-clients are <i>not</i> configured to route <i>all</i> of their traffic through
-the VPN, so Gate pushes routes to the other institute networks. Gate
-itself is on the private Ethernet and thereby learns about the route
-to Front.
+The configuration used on <code>thing</code>, the IoT appliance, looks like this:
</p>
<div class="org-src-container">
-<code>openvpn-gate-routes</code><pre class="src src-conf" id="org20aef17"><code>push <span class="org-string">"route {{ private_net_and_mask }}"</span>
-push <span class="org-string">"route {{ public_vpn_net_and_mask }}"</span>
+WireGuard™ tunnel on an IoT appliance<pre class="src src-conf"><code>[<span class="org-type">Interface</span>]
+<span class="org-variable-name">Address</span> = 10.84.139.2
+<span class="org-variable-name">PrivateKey</span> = KIwQT5eGOl9w1qOa5I+2xx5kJH3z4xdpmirS/eGdsXY=
+
+<span class="org-comment-delimiter"># </span><span class="org-comment">Gate</span>
+[<span class="org-type">Peer</span>]
+<span class="org-variable-name">PublicKey</span> = y3cjFnvQbylmH4lGTujpqc8rusIElmJ4Gu9hh6iR7QI=
+<span class="org-variable-name">AllowedIPs</span> = 10.84.139.1
+<span class="org-variable-name">AllowedIPs</span> = 10.84.139.0/24
+<span class="org-variable-name">AllowedIPs</span> = 192.168.56.0/24
+<span class="org-variable-name">AllowedIPs</span> = 10.177.87.0/24
</code></pre>
</div>
<p>
-The complete OpenVPN configuration for Gate includes a <code>server</code>
-option, the pushed routes mentioned above, and the common options
-discussed in <a href="#org1ea6412">The VPN Services</a>.
+And the configuration used on Dick's notebook when it is on campus
+looks like this:
</p>
<div class="org-src-container">
-<code>openvpn-gate</code><pre class="src src-conf" id="orgd61aa9a"><code>server {{ campus_vpn_net_and_mask }}
-client-config-dir /etc/openvpn/ccd
-<<openvpn-gate-routes>>
-<<openvpn-dev-mode>>
-<<openvpn-keepalive>>
-<<openvpn-dns>>
-<<openvpn-drop-priv>>
-<<openvpn-crypt>>
-<<openvpn-max>>
-<<openvpn-debug>>
-ca /usr/local/share/ca-certificates/{{ domain_name }}.crt
-cert /etc/server.crt
-key /etc/server.key
-dh dh2048.pem
-tls-crypt shared.key
+WireGuard™ tunnel on Dick's notebook, used on campus<pre class="src src-conf"><code>[<span class="org-type">Interface</span>]
+<span class="org-variable-name">Address</span> = 10.84.139.3
+<span class="org-variable-name">PostUp</span> = wg set %i private-key /etc/wireguard/private-key
+<span class="org-variable-name">PostUp</span> = resolvectl dns wg0 192.168.56.1
+<span class="org-variable-name">PostUp</span> = resolvectl domain wg0 small.private
+
+<span class="org-comment-delimiter"># </span><span class="org-comment">Gate</span>
+[<span class="org-type">Peer</span>]
+<span class="org-variable-name">PublicKey</span> = y3cjFnvQbylmH4lGTujpqc8rusIElmJ4Gu9hh6iR7QI=
+<span class="org-variable-name">AllowedIPs</span> = 10.84.139.1
+<span class="org-variable-name">AllowedIPs</span> = 10.84.139.0/24
+<span class="org-variable-name">AllowedIPs</span> = 192.168.56.0/24
+<span class="org-variable-name">AllowedIPs</span> = 10.177.87.0/24
</code></pre>
</div>
<p>
-Finally, here are the tasks (and handler) required to install and
-configure the OpenVPN server on Gate.
+The following tasks install WireGuard™, configure it with
+<a href="#org29145ff"><q>private/gate-wg0.conf</q></a>, and enable the service.
</p>
<div class="org-src-container">
<a href="roles_t/gate/tasks/main.yml"><q>roles_t/gate/tasks/main.yml</q></a><pre class="src src-conf"><code>
-- name: Install OpenVPN.
- become: yes
- <span class="org-variable-name">apt: pkg</span>=openvpn
-
-- name: Enable IP forwarding.
+- name: Install WireGuard™.
become: yes
- sysctl:
- name: net.ipv4.ip_forward
- value: <span class="org-string">"1"</span>
- state: present
+ <span class="org-variable-name">apt: pkg</span>=wireguard
-- name: Create OpenVPN client configuration directory.
- become: yes
- file:
- path: /etc/openvpn/ccd
- state: directory
- notify: Restart OpenVPN.
-
-- name: Disable former VPN clients.
- become: yes
- copy:
- content: <span class="org-string">"disable\n"</span>
- dest: /etc/openvpn/ccd/{{ item }}
- loop: <span class="org-string">"{{ revoked }}"</span>
- notify: Restart OpenVPN.
- tags: accounts
-
-- name: Install OpenVPN secrets.
+- name: Configure WireGuard™.
become: yes
copy:
- src: ../Secret/{{ item.src }}
- dest: /etc/openvpn/{{ item.dest }}
+ src: ../private/gate-wg0.conf
+ dest: /etc/wireguard/wg0.conf
<span class="org-variable-name">mode: u</span>=r,g=,o=
- loop:
- - { src: gate-dh2048.pem, dest: dh2048.pem }
- - { src: gate-shared.key, dest: shared.key }
- notify: Restart OpenVPN.
+ owner: root
+ group: root
+ notify: Restart WireGuard™.
-- name: Configure OpenVPN.
+- name: Enable/Start WireGuard™ on boot.
become: yes
- copy:
- content: |
- <<openvpn-gate>>
- dest: /etc/openvpn/server.conf
- <span class="org-variable-name">mode: u</span>=r,g=r,o=
- notify: Restart OpenVPN.
+ systemd:
+ service: wg-quick@wg0
+ enabled: yes
+ state: started
</code></pre>
</div>
<div class="org-src-container">
<a href="roles_t/gate/handlers/main.yml"><q>roles_t/gate/handlers/main.yml</q></a><pre class="src src-conf"><code>
-- name: Restart OpenVPN.
+- name: Restart WireGuard™.
become: yes
systemd:
- service: openvpn@server
+ service: wg-quick@wg0
state: restarted
</code></pre>
</div>
</div>
</div>
</div>
-<div id="outline-container-orgb271096" class="outline-2">
-<h2 id="orgb271096"><span class="section-number-2">10.</span> The Campus Role</h2>
+<div id="outline-container-org5a28d65" class="outline-2">
+<h2 id="org5a28d65"><span class="section-number-2">10.</span> The Campus Role</h2>
<div class="outline-text-2" id="text-10">
<p>
The <code>campus</code> role configures generic campus server machines: network
</p>
<p>
-Wireless campus devices can get a key to the campus VPN from the
-<code>./inst client campus</code> command, but their OpenVPN client must be
-configured manually.
+Wireless campus devices register their public keys using the <code>./inst
+client</code> command which updates the WireGuard™ configuration on Gate.
</p>
</div>
-<div id="outline-container-orgeb3c56c" class="outline-3">
-<h3 id="orgeb3c56c"><span class="section-number-3">10.1.</span> Include Particulars</h3>
+<div id="outline-container-org75e6c6b" class="outline-3">
+<h3 id="org75e6c6b"><span class="section-number-3">10.1.</span> Include Particulars</h3>
<div class="outline-text-3" id="text-10-1">
<p>
The following should be familiar boilerplate by now.
</div>
</div>
</div>
-<div id="outline-container-org6c045dd" class="outline-3">
-<h3 id="org6c045dd"><span class="section-number-3">10.2.</span> Configure Hostname</h3>
+<div id="outline-container-orgb180c5e" class="outline-3">
+<h3 id="orgb180c5e"><span class="section-number-3">10.2.</span> Configure Hostname</h3>
<div class="outline-text-3" id="text-10-2">
<p>
Clients should be using the expected host name.
</div>
</div>
</div>
-<div id="outline-container-orgb5ed705" class="outline-3">
-<h3 id="orgb5ed705"><span class="section-number-3">10.3.</span> Configure Systemd Resolved</h3>
+<div id="outline-container-orgfa8d1f7" class="outline-3">
+<h3 id="orgfa8d1f7"><span class="section-number-3">10.3.</span> Configure Systemd Timesyncd</h3>
<div class="outline-text-3" id="text-10-3">
<p>
-Campus machines use the campus name server on Core (or <code>dns.google</code>),
-and include the institute's private domain in their search lists.
-</p>
-
-<div class="org-src-container">
-<a href="roles_t/campus/tasks/main.yml"><q>roles_t/campus/tasks/main.yml</q></a><pre class="src src-conf"><code>
-- name: Configure resolved.
- become: yes
- lineinfile:
- path: /etc/systemd/resolved.conf
- regexp: <span class="org-string">"{{ item.regexp }}"</span>
- line: <span class="org-string">"{{ item.line }}"</span>
- loop:
- - { regexp: <span class="org-string">'^ *DNS *='</span>, line: <span class="org-string">"DNS={{ core_addr }}"</span> }
- - { regexp: <span class="org-string">'^ *FallbackDNS *='</span>, line: <span class="org-string">"FallbackDNS=8.8.8.8"</span> }
- - { regexp: <span class="org-string">'^ *Domains *='</span>, line: <span class="org-string">"Domains={{ domain_priv }}"</span> }
- notify:
- - Reload Systemd.
- - Restart Systemd resolved.
-</code></pre>
-</div>
-
-<div class="org-src-container">
-<a href="roles_t/campus/handlers/main.yml"><q>roles_t/campus/handlers/main.yml</q></a><pre class="src src-conf"><code>---
-- name: Reload Systemd.
- become: yes
- systemd:
- daemon-reload: yes
-
-- name: Restart Systemd resolved.
- become: yes
- systemd:
- service: systemd-resolved
- state: restarted
-</code></pre>
-</div>
-</div>
-</div>
-<div id="outline-container-orgc9318c9" class="outline-3">
-<h3 id="orgc9318c9"><span class="section-number-3">10.4.</span> Configure Systemd Timesyncd</h3>
-<div class="outline-text-3" id="text-10-4">
-<p>
The institute uses a common time reference throughout the campus.
This is essential to campus security, improving the accuracy of log
and file timestamps.
</div>
<div class="org-src-container">
-<a href="roles_t/campus/handlers/main.yml"><q>roles_t/campus/handlers/main.yml</q></a><pre class="src src-conf"><code>
+<a href="roles_t/campus/handlers/main.yml"><q>roles_t/campus/handlers/main.yml</q></a><pre class="src src-conf"><code>---
- name: Restart systemd-timesyncd.
become: yes
systemd:
</div>
</div>
</div>
-<div id="outline-container-org17d2e8d" class="outline-3">
-<h3 id="org17d2e8d"><span class="section-number-3">10.5.</span> Add Administrator to System Groups</h3>
-<div class="outline-text-3" id="text-10-5">
+<div id="outline-container-orgcf78de0" class="outline-3">
+<h3 id="orgcf78de0"><span class="section-number-3">10.4.</span> Add Administrator to System Groups</h3>
+<div class="outline-text-3" id="text-10-4">
<p>
The administrator often needs to read (directories of) log files owned
by groups <code>root</code> and <code>adm</code>. Adding the administrator's account to
</div>
</div>
</div>
-<div id="outline-container-org1c6f15d" class="outline-3">
-<h3 id="org1c6f15d"><span class="section-number-3">10.6.</span> Install Unattended Upgrades</h3>
-<div class="outline-text-3" id="text-10-6">
+<div id="outline-container-org88b0bbb" class="outline-3">
+<h3 id="org88b0bbb"><span class="section-number-3">10.5.</span> Install Unattended Upgrades</h3>
+<div class="outline-text-3" id="text-10-5">
<p>
The institute prefers to install security updates as soon as possible.
</p>
</div>
</div>
</div>
-<div id="outline-container-org990a25f" class="outline-3">
-<h3 id="org990a25f"><span class="section-number-3">10.7.</span> Configure Postfix on Campus</h3>
-<div class="outline-text-3" id="text-10-7">
+<div id="outline-container-org7856e77" class="outline-3">
+<h3 id="org7856e77"><span class="section-number-3">10.6.</span> Configure Postfix on Campus</h3>
+<div class="outline-text-3" id="text-10-6">
<p>
The Postfix settings used by the campus include message size, queue
times, and the <code>relayhost</code> Core. The default Debian configuration
</div>
</div>
</div>
-<div id="outline-container-org2b656fb" class="outline-3">
-<h3 id="org2b656fb"><span class="section-number-3">10.8.</span> Set Domain Name</h3>
-<div class="outline-text-3" id="text-10-8">
+<div id="outline-container-org41d6f54" class="outline-3">
+<h3 id="org41d6f54"><span class="section-number-3">10.7.</span> Set Domain Name</h3>
+<div class="outline-text-3" id="text-10-7">
<p>
The host's fully qualified (private) domain name (FQDN) is set by an
alias in its <q>/etc/hosts</q> file, as is customary on Debian. (See "The
</div>
</div>
</div>
-<div id="outline-container-org356ce5d" class="outline-3">
-<h3 id="org356ce5d"><span class="section-number-3">10.9.</span> Configure NRPE</h3>
-<div class="outline-text-3" id="text-10-9">
+<div id="outline-container-orgc703c4f" class="outline-3">
+<h3 id="orgc703c4f"><span class="section-number-3">10.8.</span> Configure NRPE</h3>
+<div class="outline-text-3" id="text-10-8">
<p>
Each campus host runs an NRPE (a NAGIOS Remote Plugin Executor)
server so that the NAGIOS4 server on Core can collect statistics. The
-NAGIOS service is discussed in the <a href="#org356ce5d">Configure NRPE</a> section of <a href="#orge6d718e">The Core
+NAGIOS service is discussed in the <a href="#orgc703c4f">Configure NRPE</a> section of <a href="#org03278f3">The Core
Role</a>.
</p>
</div>
</div>
</div>
-<div id="outline-container-orgda31017" class="outline-2">
-<h2 id="orgda31017"><span class="section-number-2">11.</span> The Ansible Configuration</h2>
+<div id="outline-container-org9259070" class="outline-2">
+<h2 id="org9259070"><span class="section-number-2">11.</span> The Ansible Configuration</h2>
<div class="outline-text-2" id="text-11">
<p>
The small institute uses Ansible to maintain the configuration of its
role(s) to each host. Examples of these files are included here, and
are used to test the roles. The example configuration applies the
institutional roles to VirtualBox machines prepared according to
-chapter <a href="#org7832c43">Testing</a>.
+chapter <a href="#org3d2b7a1">Testing</a>.
</p>
<p>
separate revision history.
</p>
</div>
-<div id="outline-container-orgf6e5306" class="outline-3">
-<h3 id="orgf6e5306"><span class="section-number-3">11.1.</span> <q>ansible.cfg</q></h3>
+<div id="outline-container-org87df2ac" class="outline-3">
+<h3 id="org87df2ac"><span class="section-number-3">11.1.</span> <q>ansible.cfg</q></h3>
<div class="outline-text-3" id="text-11-1">
<p>
The Ansible configuration file <a href="ansible.cfg"><q>ansible.cfg</q></a> contains just a handful
of settings, some included just to create a test jig as described in
-<a href="#org7832c43">Testing</a>.
+<a href="#org3d2b7a1">Testing</a>.
</p>
<ul class="org-ul">
that Python 3 can be expected on all institute hosts.</li>
<li><code>vault_password_file</code> is set to suppress prompts for the vault
password. The institute keeps its vault password in <a href="Secret/"><q>Secret/</q></a> (as
-described in <a href="#org1c1be46">Keys</a>) and thus sets this parameter to
+described in <a href="#org8244014">Keys</a>) and thus sets this parameter to
<a href="Secret/vault-password"><q>Secret/vault-password</q></a>.</li>
<li><code>inventory</code> is set to avoid specifying it on the command line.</li>
<li><code>roles_path</code> is set to the recently tangled roles files in
</div>
</div>
</div>
-<div id="outline-container-orge7d3e33" class="outline-3">
-<h3 id="orge7d3e33"><span class="section-number-3">11.2.</span> <q>hosts</q></h3>
+<div id="outline-container-org860d201" class="outline-3">
+<h3 id="org860d201"><span class="section-number-3">11.2.</span> <q>hosts</q></h3>
<div class="outline-text-3" id="text-11-2">
<p>
The Ansible inventory file <a href="hosts"><q>hosts</q></a> describes all of the institute's
</p>
<div class="org-src-container">
-<a href="hosts"><q>hosts</q></a><pre class="src src-conf" id="org0b95976"><code>all:
+<a href="hosts"><q>hosts</q></a><pre class="src src-conf" id="org0363454"><code>all:
vars:
ansible_user: sysadm
ansible_ssh_extra_args: -i Secret/ssh_admin/id_rsa
</p>
</div>
</div>
-<div id="outline-container-org723c67b" class="outline-3">
-<h3 id="org723c67b"><span class="section-number-3">11.3.</span> <q>playbooks/site.yml</q></h3>
+<div id="outline-container-orgf62dd32" class="outline-3">
+<h3 id="orgf62dd32"><span class="section-number-3">11.3.</span> <q>playbooks/site.yml</q></h3>
<div class="outline-text-3" id="text-11-3">
<p>
The example <a href="playbooks/site.yml"><q>playbooks/site.yml</q></a> playbook (below) applies the
</div>
</div>
</div>
-<div id="outline-container-org696cc09" class="outline-3">
-<h3 id="org696cc09"><span class="section-number-3">11.4.</span> <q>Secret/vault-password</q></h3>
+<div id="outline-container-org2ed44a5" class="outline-3">
+<h3 id="org2ed44a5"><span class="section-number-3">11.4.</span> <q>Secret/vault-password</q></h3>
<div class="outline-text-3" id="text-11-4">
<p>
As already mentioned, the small institute keeps its Ansible vault
</p>
<div class="org-src-container">
-<a href="Secret/vault-password"><q>Secret/vault-password</q></a><pre class="src src-conf" id="org442ebbb"><code>alitysortstagess
+<a href="Secret/vault-password"><q>Secret/vault-password</q></a><pre class="src src-conf" id="org6485306"><code>alitysortstagess
</code></pre>
</div>
</div>
</div>
-<div id="outline-container-org93d3d06" class="outline-3">
-<h3 id="org93d3d06"><span class="section-number-3">11.5.</span> Creating A Working Ansible Configuration</h3>
+<div id="outline-container-org36f0380" class="outline-3">
+<h3 id="org36f0380"><span class="section-number-3">11.5.</span> Creating A Working Ansible Configuration</h3>
<div class="outline-text-3" id="text-11-5">
<p>
A working Ansible configuration can be "tangled" from this document to
-produce the test configuration described in the <a href="#org7832c43">Testing</a> chapter. The
+produce the test configuration described in the <a href="#org3d2b7a1">Testing</a> chapter. The
tangling is done by Emacs's <code>org-babel-tangle</code> function and has
already been performed with the resulting tangle included in the
distribution with this document.
<q>public/</q> and <q>private/</q>.</li>
<li><q>~/net/Secret</q> would be a symbolic link to the (auto-mounted?)
location of the administrator's encrypted USB drive, as described in
-section <a href="#org1c1be46">Keys</a>.</li>
+section <a href="#org8244014">Keys</a>.</li>
</ul>
<p>
</div>
</div>
</div>
-<div id="outline-container-orgb3fb614" class="outline-3">
-<h3 id="orgb3fb614"><span class="section-number-3">11.6.</span> Maintaining A Working Ansible Configuration</h3>
+<div id="outline-container-org29e9a55" class="outline-3">
+<h3 id="org29e9a55"><span class="section-number-3">11.6.</span> Maintaining A Working Ansible Configuration</h3>
<div class="outline-text-3" id="text-11-6">
<p>
The Ansible roles currently tangle into the <a href="roles_t/"><q>roles_t/</q></a> directory to
</div>
</div>
</div>
-<div id="outline-container-org5db480b" class="outline-2">
-<h2 id="org5db480b"><span class="section-number-2">12.</span> The Institute Commands</h2>
+<div id="outline-container-org1eb4766" class="outline-2">
+<h2 id="org1eb4766"><span class="section-number-2">12.</span> The Institute Commands</h2>
<div class="outline-text-2" id="text-12">
<p>
The institute's administrator uses a convenience script to reliably
to get their defaults from <q>./ansible.cfg</q>.
</p>
</div>
-<div id="outline-container-orgad96cbc" class="outline-3">
-<h3 id="orgad96cbc"><span class="section-number-3">12.1.</span> Sub-command Blocks</h3>
+<div id="outline-container-org521c9c6" class="outline-3">
+<h3 id="org521c9c6"><span class="section-number-3">12.1.</span> Sub-command Blocks</h3>
<div class="outline-text-3" id="text-12-1">
<p>
The code blocks in this chapter tangle into the <a href="inst"><q>inst</q></a> script. Each
</div>
</div>
</div>
-<div id="outline-container-org3b3c0fd" class="outline-3">
-<h3 id="org3b3c0fd"><span class="section-number-3">12.2.</span> Sanity Check</h3>
+<div id="outline-container-org2762906" class="outline-3">
+<h3 id="org2762906"><span class="section-number-3">12.2.</span> Sanity Check</h3>
<div class="outline-text-3" id="text-12-2">
<p>
The next code block does not implement a sub-command; it implements
</div>
</div>
</div>
-<div id="outline-container-org1a212b5" class="outline-3">
-<h3 id="org1a212b5"><span class="section-number-3">12.3.</span> Importing Ansible Variables</h3>
+<div id="outline-container-org356264c" class="outline-3">
+<h3 id="org356264c"><span class="section-number-3">12.3.</span> Importing Ansible Variables</h3>
<div class="outline-text-3" id="text-12-3">
<p>
To ensure that Ansible and <code>./inst</code> are sympatico vis-a-vi certain
mysystem <span class="org-string">"ansible-playbook playbooks/check-inst-vars.yml >/dev/null"</span>;
-our ($domain_name, $domain_priv, $front_addr, $gate_wild_addr);
+our ($domain_name, $domain_priv, $private_net_cidr,
+ $front_addr, $front_wg_pubkey,
+ $public_wg_net_cidr, $public_wg_port,
+ $gate_wild_addr, $gate_wg_pubkey,
+ $campus_wg_net_cidr, $campus_wg_port,
+ $core_addr, $core_wg_pubkey);
do <span class="org-string">"./private/vars.pl"</span>;
</code></pre>
</div>
content: |
<span class="org-variable-name">$domain_name</span> = <span class="org-string">"{{ domain_name }}"</span>;
<span class="org-variable-name">$domain_priv</span> = <span class="org-string">"{{ domain_priv }}"</span>;
+ <span class="org-variable-name">$private_net_cidr</span> = <span class="org-string">"{{ private_net_cidr }}"</span>;
+
<span class="org-variable-name">$front_addr</span> = <span class="org-string">"{{ front_addr }}"</span>;
+ <span class="org-variable-name">$front_wg_pubkey</span> = <span class="org-string">"{{ front_wg_pubkey }}"</span>;
+
+ <span class="org-variable-name">$public_wg_net_cidr</span> = <span class="org-string">"{{ public_wg_net_cidr }}"</span>;
+
+ <span class="org-variable-name">$public_wg_port</span> = <span class="org-string">"{{ public_wg_port }}"</span>;
+
<span class="org-variable-name">$gate_wild_addr</span> = <span class="org-string">"{{ gate_wild_addr }}"</span>;
+ <span class="org-variable-name">$gate_wg_pubkey</span> = <span class="org-string">"{{ gate_wg_pubkey }}"</span>;
+
+ <span class="org-variable-name">$campus_wg_net_cidr</span> = <span class="org-string">"{{ campus_wg_net_cidr }}"</span>;
+ <span class="org-variable-name">$campus_wg_port</span> = <span class="org-string">"{{ campus_wg_port }}"</span>;
+
+ <span class="org-variable-name">$core_addr</span> = <span class="org-string">"{{ core_addr }}"</span>;
+ <span class="org-variable-name">$core_wg_pubkey</span> = <span class="org-string">"{{ core_wg_pubkey }}"</span>;
dest: ../private/vars.pl
<span class="org-variable-name">mode: u</span>=rw,g=,o=
</code></pre>
</div>
+
+<p>
+Most of these settings are already in <q>private/vars.yml</q>. The
+following few provide the servers' public keys and ports.
+</p>
+
+<div class="org-src-container">
+<a href="private/vars.yml">=private/vars.yml</a><pre class="src src-conf"><code><span class="org-variable-name">front_wg_pubkey: S+6HaTnOwwhWgUGXjSBcPAvifKw+j8BDTRfq534gNW4</span>=
+public_wg_port: 39608
+
+<span class="org-variable-name">gate_wg_pubkey: y3cjFnvQbylmH4lGTujpqc8rusIElmJ4Gu9hh6iR7QI</span>=
+campus_wg_port: 51820
+
+<span class="org-variable-name">core_wg_pubkey: lGhC51IBgZtlq4H2bsYFuKvPtV0VAEwUvVIn5fW7D0c</span>=
+</code></pre>
+</div>
+
+<p>
+All of the private keys used in the example/test configuration are
+listed in the following table. The first three are copied to
+<q>/etc/wireguard/private-key</q> on each of the corresponding test
+machines: <code>front</code>, <code>gate</code> and <code>core</code>. The rest are installed on
+the test client to give it different personae.
+</p>
+
+<table border="2" cellspacing="0" cellpadding="6" rules="groups" frame="hsides">
+
+
+<colgroup>
+<col class="org-left" />
+
+<col class="org-left" />
+</colgroup>
+<thead>
+<tr>
+<th scope="col" class="org-left">Test Host</th>
+<th scope="col" class="org-left">WireGuard™ Private Key</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="org-left"><code>front</code></td>
+<td class="org-left">AJkzVxfTm/KvRjzTN/9X2jYy+CAugiwZfN5F3JTegms=</td>
+</tr>
+
+<tr>
+<td class="org-left"><code>gate</code></td>
+<td class="org-left">yOBdLbXh6KBwYQvvb5mhiku8Fxkqc5Cdyz6gNgjc/2U=</td>
+</tr>
+
+<tr>
+<td class="org-left"><code>core</code></td>
+<td class="org-left">AI+KhwnsHzSPqyIyAObx7EBBTBXFZPiXb2/Qcts8zEI=</td>
+</tr>
+
+<tr>
+<td class="org-left"><code>thing</code></td>
+<td class="org-left">KIwQT5eGOl9w1qOa5I+2xx5kJH3z4xdpmirS/eGdsXY=</td>
+</tr>
+
+<tr>
+<td class="org-left"><code>dick</code></td>
+<td class="org-left">WAhrlGccPf/BaFS5bRtBE4hEyt3kDxCavmwZfVTsfGs=</td>
+</tr>
+
+<tr>
+<td class="org-left"><code>dicks-phone</code></td>
+<td class="org-left">oG/Kou9HOBCBwHAZGypPA1cZWUL6nR6WoxBiXc/OQWQ=</td>
+</tr>
+
+<tr>
+<td class="org-left"><code>dicks-razr</code></td>
+<td class="org-left">IGNcF0VpkIBcJQAcLZ9jgRmk0SYyUr/WwSNXZoXXUWQ=</td>
+</tr>
+</tbody>
+</table>
</div>
</div>
-<div id="outline-container-org3fe9e37" class="outline-3">
-<h3 id="org3fe9e37"><span class="section-number-3">12.4.</span> The CA Command</h3>
+<div id="outline-container-orgd14a881" class="outline-3">
+<h3 id="orgd14a881"><span class="section-number-3">12.4.</span> The CA Command</h3>
<div class="outline-text-3" id="text-12-4">
<p>
The next code block implements the <code>CA</code> sub-command, which creates a
<span class="org-keyword">my</span> $<span class="org-variable-name">dom</span> = $<span class="org-variable-name">domain_name</span>;
<span class="org-keyword">my</span> $<span class="org-variable-name">pvt</span> = $<span class="org-variable-name">domain_priv</span>;
mysystem <span class="org-string">"cd Secret/CA; ./easyrsa build-server-full $dom nopass"</span>;
- mysystem <span class="org-string">"cd Secret/CA; ./easyrsa build-server-full gate.$pvt nopass"</span>;
mysystem <span class="org-string">"cd Secret/CA; ./easyrsa build-server-full core.$pvt nopass"</span>;
- mysystem <span class="org-string">"cd Secret/CA; ./easyrsa build-client-full core nopass"</span>;
umask 077;
- mysystem <span class="org-string">"openvpn --genkey secret Secret/front-shared.key"</span>;
- mysystem <span class="org-string">"openvpn --genkey secret Secret/gate-shared.key"</span>;
- mysystem <span class="org-string">"openssl dhparam -out Secret/front-dh2048.pem 2048"</span>;
- mysystem <span class="org-string">"openssl dhparam -out Secret/gate-dh2048.pem 2048"</span>;
mysystem <span class="org-string">"mkdir --mode=700 Secret/root.gnupg"</span>;
mysystem (<span class="org-string">"gpg --homedir Secret/root.gnupg"</span>,
</div>
</div>
</div>
-<div id="outline-container-orge93c2cb" class="outline-3">
-<h3 id="orge93c2cb"><span class="section-number-3">12.5.</span> The Config Command</h3>
+<div id="outline-container-orgb6ae049" class="outline-3">
+<h3 id="orgb6ae049"><span class="section-number-3">12.5.</span> The Config Command</h3>
<div class="outline-text-3" id="text-12-5">
<p>
The next code block implements the <code>config</code> sub-command, which
provisions network services by running the <q>site.yml</q> playbook
-described in <a href="#org723c67b"><q>playbooks/site.yml</q></a>. It recognizes an optional <code>-n</code>
+described in <a href="playbooks/site.yml"><q>playbooks/site.yml</q></a>. It recognizes an optional <code>-n</code>
flag indicating that the service configurations should just be
checked. Given an optional host name, it provisions (or checks) just
the named host.
</div>
</div>
</div>
-<div id="outline-container-org6138e49" class="outline-3">
-<h3 id="org6138e49"><span class="section-number-3">12.6.</span> Account Management</h3>
+<div id="outline-container-org944d8f7" class="outline-3">
+<h3 id="org944d8f7"><span class="section-number-3">12.6.</span> Account Management</h3>
<div class="outline-text-3" id="text-12-6">
<p>
For general information about members and their Unix accounts, see
-<a href="#orga38c5ed">Accounts</a>. The account management sub-commands maintain a mapping
+<a href="#org03cae01">Accounts</a>. 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
<q>private/members.yml</q> as the value associated with the key <code>members</code>.
key value <code>current</code>. That key gets value <code>former</code> when the member
leaves.<sup><a id="fnr.3" class="footref" href="#fn.3" role="doc-backlink">3</a></sup> 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.
</p>
<p>
The example file (below) contains a membership roll with one
-membership record, for an account named <code>dick</code>, which was issued
-client certificates for devices named <code>dick-note</code>, <code>dick-phone</code> and
-<code>dick-razr</code>. <code>dick-phone</code> 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 <code>dick</code>, which registered the
+public keys of devices named <code>dick</code>, <code>dicks-phone</code> and <code>dicks-razr</code>.
+<code>dicks-razr</code> is presumably a replacement for <code>dicks-phone</code>, 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.)
</p>
<div class="org-src-container">
dick:
status: current
clients:
- - dick-note
- - dick-phone
- - dick-razr
+ <span class="org-variable-name">- dick 4 4qd4xdRztZBKhFrX9jI/b4fnMzpKQ5qhg691hwYSsX8</span>=
+ <span class="org-variable-name">- dicks-phone 5 --WFbTSff17QiYObXoU+7mjaEUCqKjgvLqA49pAxqVeWg</span>=
+ <span class="org-variable-name">- dicks-razr 6 zho0qMxoLclJSQu4GeJEcMkk0hx4Q047OcNc8vOejVw</span>=
password_front:
$6$17h49U76$c7TsH6eMVmoKElNANJU1F1LrRrqzYVDreNu.QarpCoSt9u0gTHgiQ
password_core:
6535633263656434393030333032343533626235653332626330666166613833
usernames:
- dick
-revoked:
-- dick-phone
+clients:
+<span class="org-variable-name">- thing 3 LdsCsgfjKCfd5+VKS+Q/dQhWO8NRNygByDO2VxbXlSQ</span>=
</code></pre>
</div>
<a href="private/members-empty.yml"><q>private/members-empty.yml</q></a><pre class="src src-conf"><code>---
members:
usernames: []
-revoked: []
+clients: []
</code></pre>
</div>
<span class="org-keyword">if</span> (keys %{$<span class="org-variable-name">yaml</span>->{<span class="org-string">"members"</span>}}) {
print $<span class="org-variable-name">O</span> <span class="org-string">"members:\n"</span>;
<span class="org-keyword">for</span> <span class="org-keyword">my</span> $<span class="org-variable-name">user</span> (sort keys %{$<span class="org-variable-name">yaml</span>->{<span class="org-string">"members"</span>}}) {
- print_member ($<span class="org-variable-name">O</span>, $<span class="org-variable-name">yaml</span>->{<span class="org-string">"members"</span>}->{$<span class="org-variable-name">user</span>});
+ print_member ($<span class="org-variable-name">O</span>, $<span class="org-variable-name">user</span>, $<span class="org-variable-name">yaml</span>->{<span class="org-string">"members"</span>}->{$<span class="org-variable-name">user</span>});
}
print $<span class="org-variable-name">O</span> <span class="org-string">"usernames:\n"</span>;
<span class="org-keyword">for</span> <span class="org-keyword">my</span> $<span class="org-variable-name">user</span> (sort keys %{$<span class="org-variable-name">yaml</span>->{<span class="org-string">"members"</span>}}) {
print $<span class="org-variable-name">O</span> <span class="org-string">"members:\n"</span>;
print $<span class="org-variable-name">O</span> <span class="org-string">"usernames: []\n"</span>;
}
- <span class="org-keyword">if</span> (@{$<span class="org-variable-name">yaml</span>->{<span class="org-string">"revoked"</span>}}) {
- print $<span class="org-variable-name">O</span> <span class="org-string">"revoked:\n"</span>;
- <span class="org-keyword">for</span> <span class="org-keyword">my</span> $<span class="org-variable-name">name</span> (@{$<span class="org-variable-name">yaml</span>->{<span class="org-string">"revoked"</span>}}) {
+ <span class="org-keyword">if</span> (@{$<span class="org-variable-name">yaml</span>->{<span class="org-string">"clients"</span>}}) {
+ print $<span class="org-variable-name">O</span> <span class="org-string">"clients:\n"</span>;
+ <span class="org-keyword">for</span> <span class="org-keyword">my</span> $<span class="org-variable-name">name</span> (@{$<span class="org-variable-name">yaml</span>->{<span class="org-string">"clients"</span>}}) {
print $<span class="org-variable-name">O</span> <span class="org-string">"- $name\n"</span>;
}
} <span class="org-keyword">else</span> {
- print $<span class="org-variable-name">O</span> <span class="org-string">"revoked: []\n"</span>;
+ print $<span class="org-variable-name">O</span> <span class="org-string">"clients: []\n"</span>;
}
close $<span class="org-variable-name">O</span> or <span class="org-keyword">die</span> <span class="org-string">"Could not close $pathname: $!\n"</span>;
}
<div class="org-src-container">
<a href="inst"><q>inst</q></a><pre class="src src-perl"><code>
-<span class="org-keyword">sub</span> <span class="org-function-name">print_member</span> ($$) {
- <span class="org-keyword">my</span> ($<span class="org-variable-name">out</span>, $<span class="org-variable-name">member</span>) = @<span class="org-perl-non-scalar-variable">_</span>;
- print $<span class="org-variable-name">out</span> <span class="org-string">" "</span>, $<span class="org-variable-name">member</span>->{<span class="org-string">"username"</span>}, <span class="org-string">":\n"</span>;
- print $<span class="org-variable-name">out</span> <span class="org-string">" username: "</span>, $<span class="org-variable-name">member</span>->{<span class="org-string">"username"</span>}, <span class="org-string">"\n"</span>;
+<span class="org-keyword">sub</span> <span class="org-function-name">print_member</span> ($$$) {
+ <span class="org-keyword">my</span> ($<span class="org-variable-name">out</span>, $<span class="org-variable-name">username</span>, $<span class="org-variable-name">member</span>) = @<span class="org-perl-non-scalar-variable">_</span>;
+ print $<span class="org-variable-name">out</span> <span class="org-string">" "</span>, $<span class="org-variable-name">username</span>, <span class="org-string">":\n"</span>;
print $<span class="org-variable-name">out</span> <span class="org-string">" status: "</span>, $<span class="org-variable-name">member</span>->{<span class="org-string">"status"</span>}, <span class="org-string">"\n"</span>;
<span class="org-keyword">if</span> (@{$<span class="org-variable-name">member</span>->{<span class="org-string">"clients"</span>} || []}) {
print $<span class="org-variable-name">out</span> <span class="org-string">" clients:\n"</span>;
print $<span class="org-variable-name">out</span> <span class="org-string">" $line\n"</span>;
}
}
- <span class="org-keyword">my</span> @<span class="org-perl-non-scalar-variable">standard_keys</span> = ( <span class="org-string">"username"</span>, <span class="org-string">"status"</span>, <span class="org-string">"clients"</span>,
+ <span class="org-keyword">my</span> @<span class="org-perl-non-scalar-variable">standard_keys</span> = ( <span class="org-string">"status"</span>, <span class="org-string">"clients"</span>,
<span class="org-string">"password_front"</span>, <span class="org-string">"password_core"</span>,
<span class="org-string">"password_fetchmail"</span> );
<span class="org-keyword">my</span> @<span class="org-perl-non-scalar-variable">other_keys</span> = (sort
</div>
</div>
</div>
-<div id="outline-container-org541678b" class="outline-3">
-<h3 id="org541678b"><span class="section-number-3">12.7.</span> The New Command</h3>
+<div id="outline-container-org7a31407" class="outline-3">
+<h3 id="org7a31407"><span class="section-number-3">12.7.</span> The New Command</h3>
<div class="outline-text-3" id="text-12-7">
<p>
The next code block implements the <code>new</code> sub-command. It adds a new
mysystem (<span class="org-string">"ansible-playbook -e \@Secret/become.yml"</span>,
<span class="org-string">" playbooks/nextcloud-new.yml"</span>,
<span class="org-string">" -e user=$user"</span>, <span class="org-string">" -e pass=\"$epass\""</span>);
- $<span class="org-variable-name">members</span>->{$<span class="org-variable-name">user</span>} = { <span class="org-string">"username"</span> => $<span class="org-variable-name">user</span>,
- <span class="org-string">"status"</span> => <span class="org-string">"current"</span>,
+ $<span class="org-variable-name">members</span>->{$<span class="org-variable-name">user</span>} = { <span class="org-string">"status"</span> => <span class="org-string">"current"</span>,
<span class="org-string">"password_front"</span> => $<span class="org-variable-name">front</span>,
<span class="org-string">"password_core"</span> => $<span class="org-variable-name">core</span>,
<span class="org-string">"password_fetchmail"</span> => $<span class="org-variable-name">vault</span> };
- write_members_yaml
- { <span class="org-string">"members"</span> => $<span class="org-variable-name">members</span>,
- <span class="org-string">"revoked"</span> => $<span class="org-variable-name">yaml</span>->{<span class="org-string">"revoked"</span>} };
+ write_members_yaml $<span class="org-variable-name">yaml</span>;
mysystem (<span class="org-string">"ansible-playbook -e \@Secret/become.yml"</span>,
<span class="org-string">" -t accounts -l core,front playbooks/site.yml"</span>);
<span class="org-keyword">exit</span>;
</div>
</div>
</div>
-<div id="outline-container-org5e1c62d" class="outline-3">
-<h3 id="org5e1c62d"><span class="section-number-3">12.8.</span> The Pass Command</h3>
+<div id="outline-container-org25a613b" class="outline-3">
+<h3 id="org25a613b"><span class="section-number-3">12.8.</span> The Pass Command</h3>
<div class="outline-text-3" id="text-12-8">
<p>
The institute's <code>passwd</code> command on Core securely emails <code>root</code> with a
message is sent to <code>member@core</code>.
</p>
</div>
-<div id="outline-container-orgdf2b1f0" class="outline-4">
-<h4 id="orgdf2b1f0"><span class="section-number-4">12.8.1.</span> Less Aggressive passwd.</h4>
+<div id="outline-container-orgbd3ef07" class="outline-4">
+<h4 id="orgbd3ef07"><span class="section-number-4">12.8.1.</span> Less Aggressive passwd.</h4>
<div class="outline-text-4" id="text-12-8-1">
<p>
The next code block implements the less aggressive <code>passwd</code> command.
</div>
</div>
</div>
-<div id="outline-container-org02d38d2" class="outline-4">
-<h4 id="org02d38d2"><span class="section-number-4">12.8.2.</span> Less Aggressive Pass Command</h4>
+<div id="outline-container-orgf5b491e" class="outline-4">
+<h4 id="orgf5b491e"><span class="section-number-4">12.8.2.</span> Less Aggressive Pass Command</h4>
<div class="outline-text-4" id="text-12-8-2">
<p>
The following code block implements the <code>./inst pass</code> command, used by
</div>
</div>
</div>
-<div id="outline-container-org77d62a8" class="outline-4">
-<h4 id="org77d62a8"><span class="section-number-4">12.8.3.</span> Installing the Less Aggressive passwd</h4>
+<div id="outline-container-org597c350" class="outline-4">
+<h4 id="org597c350"><span class="section-number-4">12.8.3.</span> Installing the Less Aggressive passwd</h4>
<div class="outline-text-4" id="text-12-8-3">
<p>
The following Ansible tasks install the less aggressive <code>passwd</code>
</div>
</div>
</div>
-<div id="outline-container-org2db4274" class="outline-3">
-<h3 id="org2db4274"><span class="section-number-3">12.9.</span> The Old Command</h3>
+<div id="outline-container-org1f39111" class="outline-3">
+<h3 id="org1f39111"><span class="section-number-3">12.9.</span> The Old Command</h3>
<div class="outline-text-3" id="text-12-9">
<p>
-The <code>old</code> command disables a member's accounts and clients.
+The <code>old</code> command disables a member's account (and thus their clients).
</p>
<div class="org-src-container">
mysystem (<span class="org-string">"ansible-playbook -e \@Secret/become.yml"</span>,
<span class="org-string">"playbooks/nextcloud-old.yml -e user=$user"</span>);
$<span class="org-variable-name">member</span>->{<span class="org-string">"status"</span>} = <span class="org-string">"former"</span>;
- write_members_yaml { <span class="org-string">"members"</span> => $<span class="org-variable-name">members</span>,
- <span class="org-string">"revoked"</span> => [ sort @{$<span class="org-variable-name">member</span>->{<span class="org-string">"clients"</span>}},
- @{$<span class="org-variable-name">yaml</span>->{<span class="org-string">"revoked"</span>}} ] };
+ write_members_yaml $<span class="org-variable-name">yaml</span>;
mysystem (<span class="org-string">"ansible-playbook -e \@Secret/become.yml"</span>,
<span class="org-string">"-t accounts playbooks/site.yml"</span>);
<span class="org-keyword">exit</span>;
</div>
</div>
</div>
-<div id="outline-container-orgdc36b90" class="outline-3">
-<h3 id="orgdc36b90"><span class="section-number-3">12.10.</span> The Client Command</h3>
+<div id="outline-container-org69b4800" class="outline-3">
+<h3 id="org69b4800"><span class="section-number-3">12.10.</span> The Client Command</h3>
<div class="outline-text-3" id="text-12-10">
<p>
-The <code>client</code> command creates an OpenVPN configuration (<q>.ovpn</q>) file
-authorizing wireless devices to connect to the institute's VPNs. The
-command uses the EasyRSA CA in <a href="Secret/"><q>Secret/</q></a>. The generated configuration
-is slightly different depending on the type of host, given as the
-first argument to the command.
+The <code>client</code> 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 <q>front-wg0.conf</q> and <q>gate-wg0.conf</q>. 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).
</p>
-<ul class="org-ul">
-<li><code>./inst client android NEW USER</code> <br />
-An <code>android</code> host runs OpenVPN for Android or work-alike. Two files
-are generated. <q>campus.ovpn</q> configures a campus VPN connection,
-and <q>public.ovpn</q> configures a connection to the institute's public
-VPN.</li>
-
-<li><code>./inst client debian NEW USER</code> <br />
-A <code>debian</code> host runs a Debian desktop with Network Manager. Again
-two files are generated, for the campus and public VPNs.</li>
-
-<li><code>./inst client campus NEW</code> <br />
-A <code>campus</code> host is a Debian host (with or without desktop) that is
-used by the institute generally, is <i>not</i> the property of a member,
-never roams off campus, and so is remotely administered with
-Ansible. One file is generated, <q>campus.ovpn</q>.</li>
-</ul>
-
<p>
-The administrator uses encrypted email to send <q>.ovpn</q> files to new
-members. New members install the <code>network-manager-openvpn-gnome</code> and
-<code>openvpn-systemd-resolved</code> packages, and import the <q>.ovpn</q> files into
-Network Manager on their desktops. The <q>.ovpn</q> files for an
-Android device are transferred by USB stick and should automatically
-install when "opened". On campus hosts, the system administrator
-copies the <q>campus.ovpn</q> file to <q>/etc/openvpn/campus.conf</q>.
+The <code>client</code> command also generates template WireGuard™ configuration
+files for the client. They contain the necessary parameters <i>except</i>
+the client's <code>PrivateKey</code>, which in most cases should be found in the
+local <q>/etc/wireguard/private-key</q>, <i>not</i> 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.).
</p>
<p>
-The OpenVPN configurations generated for Debian hosts specify an <code>up</code>
-script, <code>update-systemd-resolved</code>, installed in <q>/etc/openvpn/</q> by the
-<code>openvpn-systemd-resolved</code> 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. <q>campus.conf</q> contains
+the client's campus VPN configuration, and <q>public.conf</q> the client's
+public VPN configuration.
</p>
-<div class="org-src-container">
-<code>openvpn-up</code><pre class="src src-conf" id="org87ee394"><code>script-security 2
-up /etc/openvpn/update-systemd-resolved
-up-restart
-</code></pre>
-</div>
+<ul class="org-ul">
+<li><code>./inst client android NAME USER PUBKEY</code> <br />
+An <code>android</code> client runs WireGuard for Android™ or work-alike.</li>
+
+<li><code>./inst client debian NAME USER PUBKEY</code> <br />
+A <code>debian</code> client runs a Debian/Linux desktop with Network Manager
+(though <code>wg-quick</code> is currently used).</li>
+
+<li><code>./inst client campus NAME PUBKEY</code> <br />
+A <code>campus</code> client is an institute machine (with or without desktop)
+that is used by the institute generally, is <i>not</i> the property of a
+member, never roams off campus, and so is remotely administered with
+Ansible. Just one configuration file is generated: <q>campus.conf</q>.</li>
+</ul>
+
+<p>
+The administrator emails the template <q>.conf</q> files to new members.
+(They contain no secrets.) The members will have already installed
+the <code>wireguard</code> package in order to run the <code>wg genkey</code> and <code>wg
+pubkey</code> commands. After receiving the <q>.conf</q> templates, they paste
+in their private keys and install the resulting files in
+e.g. <q>/etc/wireguard/wg0.conf</q> and <q>wg1.conf</q>. To connect, members
+run a command like <code>systemctl start wg-quick@wg0</code>. (There may be
+better support in Network Manager soon.)
+</p>
<div class="org-src-container">
-<a href="inst"><q>inst</q></a><pre class="src src-perl"><code><span class="org-keyword">sub</span> <span class="org-function-name">write_template</span> ($$$$$$$$$);
-<span class="org-keyword">sub</span> <span class="org-function-name">read_file</span> ($);
-<span class="org-keyword">sub</span> <span class="org-function-name">add_client</span> ($$$);
+<a href="inst"><q>inst</q></a><pre class="src src-perl"><code><span class="org-keyword">sub</span> <span class="org-function-name">write_wg_server</span> ($$$$$);
+<span class="org-keyword">sub</span> <span class="org-function-name">write_wg_client</span> ($$$$$$);
+<span class="org-keyword">sub</span> <span class="org-function-name">hostnum_to_ipaddr</span> ($$);
+<span class="org-keyword">sub</span> <span class="org-function-name">hostnum_to_ipaddr_cidr</span> ($$);
<span class="org-keyword">if</span> (defined $<span class="org-variable-name">ARGV</span>[0] && $<span class="org-variable-name">ARGV</span>[0] eq <span class="org-string">"client"</span>) {
- <span class="org-keyword">die</span> <span class="org-string">"Secret/CA/easyrsa: not found\n"</span> <span class="org-keyword">if</span> ! -x <span class="org-string">"Secret/CA/easyrsa"</span>;
<span class="org-keyword">my</span> $<span class="org-variable-name">type</span> = $<span class="org-variable-name">ARGV</span>[1]||<span class="org-string">""</span>;
<span class="org-keyword">my</span> $<span class="org-variable-name">name</span> = $<span class="org-variable-name">ARGV</span>[2]||<span class="org-string">""</span>;
<span class="org-keyword">my</span> $<span class="org-variable-name">user</span> = $<span class="org-variable-name">ARGV</span>[3]||<span class="org-string">""</span>;
- <span class="org-keyword">if</span> ($<span class="org-variable-name">type</span> eq <span class="org-string">"campus"</span>) {
- <span class="org-keyword">die</span> <span class="org-string">"usage: $0 client campus NAME\n"</span> <span class="org-keyword">if</span> @<span class="org-perl-non-scalar-variable">ARGV</span> != 3;
+ <span class="org-keyword">my</span> $<span class="org-variable-name">pubkey</span> = $<span class="org-variable-name">ARGV</span>[4]||<span class="org-string">""</span>;
+ <span class="org-keyword">if</span> ($<span class="org-variable-name">type</span> eq <span class="org-string">"android"</span> || $<span class="org-variable-name">type</span> eq <span class="org-string">"debian"</span>) {
+ <span class="org-keyword">die</span> <span class="org-string">"usage: $0 client $type NAME USER PUBKEY\n"</span> <span class="org-keyword">if</span> @<span class="org-perl-non-scalar-variable">ARGV</span> != 5;
<span class="org-keyword">die</span> <span class="org-string">"$name: invalid host name\n"</span> <span class="org-keyword">if</span> $<span class="org-variable-name">name</span> !~ <span class="org-string">/^[a-z][-a-z0-9]+$/</span>;
- } <span class="org-keyword">elsif</span> ($<span class="org-variable-name">type</span> eq <span class="org-string">"android"</span> || $<span class="org-variable-name">type</span> eq <span class="org-string">"debian"</span>) {
- <span class="org-keyword">die</span> <span class="org-string">"usage: $0 client $type NAME USER\n"</span> <span class="org-keyword">if</span> @<span class="org-perl-non-scalar-variable">ARGV</span> != 4;
+ } <span class="org-keyword">elsif</span> ($<span class="org-variable-name">type</span> eq <span class="org-string">"campus"</span>) {
+ <span class="org-keyword">die</span> <span class="org-string">"usage: $0 client campus NAME PUBKEY\n"</span> <span class="org-keyword">if</span> @<span class="org-perl-non-scalar-variable">ARGV</span> != 4;
<span class="org-keyword">die</span> <span class="org-string">"$name: invalid host name\n"</span> <span class="org-keyword">if</span> $<span class="org-variable-name">name</span> !~ <span class="org-string">/^[a-z][-a-z0-9]+$/</span>;
+ $<span class="org-variable-name">pubkey</span> = $<span class="org-variable-name">user</span>;
+ $<span class="org-variable-name">user</span> = <span class="org-string">""</span>;
} <span class="org-keyword">else</span> {
- <span class="org-keyword">die</span> <span class="org-string">"usage: $0 client [debian|android|campus]\n"</span> <span class="org-keyword">if</span> @<span class="org-perl-non-scalar-variable">ARGV</span> != 4;
+ <span class="org-keyword">die</span> <span class="org-string">"usage: $0 client [debian|android|campus]\n"</span>;
}
<span class="org-keyword">my</span> $<span class="org-variable-name">yaml</span>;
- <span class="org-keyword">my</span> $<span class="org-variable-name">member</span>;
- <span class="org-keyword">if</span> ($<span class="org-variable-name">type</span> ne <span class="org-string">"campus"</span>) {
- $<span class="org-variable-name">yaml</span> = read_members_yaml;
- <span class="org-keyword">my</span> $<span class="org-variable-name">members</span> = $<span class="org-variable-name">yaml</span>->{<span class="org-string">"members"</span>};
- <span class="org-keyword">if</span> (@<span class="org-perl-non-scalar-variable">ARGV</span> == 4) {
- $<span class="org-variable-name">member</span> = $<span class="org-variable-name">members</span>->{$<span class="org-variable-name">user</span>};
- <span class="org-keyword">die</span> <span class="org-string">"$user: does not exist\n"</span> <span class="org-keyword">if</span> ! defined $<span class="org-variable-name">member</span>;
- }
- <span class="org-keyword">if</span> (defined $<span class="org-variable-name">member</span>) {
- <span class="org-keyword">my</span> ($<span class="org-variable-name">owner</span>) = grep { grep { $<span class="org-variable-name">_</span> eq $<span class="org-variable-name">name</span> } @{$<span class="org-variable-name">_</span>->{<span class="org-string">"clients"</span>}} }
- values %{$<span class="org-variable-name">members</span>};
- <span class="org-keyword">die</span> <span class="org-string">"$name: owned by $owner->{username}\n"</span>
- <span class="org-keyword">if</span> defined $<span class="org-variable-name">owner</span> && $<span class="org-variable-name">owner</span>->{username} ne $<span class="org-variable-name">member</span>->{username};
- }
- }
+ $<span class="org-variable-name">yaml</span> = read_members_yaml;
+ <span class="org-keyword">my</span> $<span class="org-variable-name">members</span> = $<span class="org-variable-name">yaml</span>->{<span class="org-string">"members"</span>};
+ <span class="org-keyword">my</span> $<span class="org-variable-name">member</span> = $<span class="org-variable-name">members</span>->{$<span class="org-variable-name">user</span>};
+ <span class="org-keyword">die</span> <span class="org-string">"$user: does not exist\n"</span>
+ <span class="org-keyword">if</span> !defined $<span class="org-variable-name">member</span> && $<span class="org-variable-name">type</span> ne <span class="org-string">"campus"</span>;
- <span class="org-keyword">die</span> <span class="org-string">"Secret/CA: no certificate authority found"</span>
- <span class="org-keyword">if</span> ! -d <span class="org-string">"Secret/CA/pki/issued"</span>;
+ <span class="org-keyword">my</span> @<span class="org-perl-non-scalar-variable">campus_peers</span> <span class="org-comment"># [ name, hostnum, type, pubkey, user|"" ]</span>
+ = map { [ (split <span class="org-string">/ /</span>), <span class="org-string">""</span> ] } @{$<span class="org-variable-name">yaml</span>->{<span class="org-string">"clients"</span>}};
- <span class="org-keyword">if</span> (! -f <span class="org-string">"Secret/CA/pki/issued/$name.crt"</span>) {
- mysystem <span class="org-string">"cd Secret/CA; ./easyrsa build-client-full $name nopass"</span>;
- } <span class="org-keyword">else</span> {
- print <span class="org-string">"Using existing key/cert...\n"</span>;
+ <span class="org-keyword">my</span> @<span class="org-perl-non-scalar-variable">member_peers</span> = ();
+ <span class="org-keyword">for</span> <span class="org-keyword">my</span> $<span class="org-variable-name">u</span> (sort keys %$<span class="org-variable-name">members</span>) {
+ push @<span class="org-perl-non-scalar-variable">member_peers</span>,
+ map { [ (split <span class="org-string">/ /</span>), $<span class="org-variable-name">u</span> ] } @{$<span class="org-variable-name">members</span>->{$<span class="org-variable-name">u</span>}->{<span class="org-string">"clients"</span>}};
}
- <span class="org-keyword">if</span> ($<span class="org-variable-name">type</span> ne <span class="org-string">"campus"</span>) {
- <span class="org-keyword">my</span> $<span class="org-variable-name">clients</span> = $<span class="org-variable-name">member</span>->{<span class="org-string">"clients"</span>};
- <span class="org-keyword">if</span> (! grep { $<span class="org-variable-name">_</span> eq $<span class="org-variable-name">name</span> } @$<span class="org-variable-name">clients</span>) {
- $<span class="org-variable-name">member</span>->{<span class="org-string">"clients"</span>} = [ $<span class="org-variable-name">name</span>, @$<span class="org-variable-name">clients</span> ];
- write_members_yaml $<span class="org-variable-name">yaml</span>;
- }
+ <span class="org-keyword">my</span> @<span class="org-perl-non-scalar-variable">all_peers</span> = sort { $<span class="org-variable-name">a</span>->[1] <=> $<span class="org-variable-name">b</span>->[1] }
+ (@<span class="org-perl-non-scalar-variable">campus_peers</span>, @<span class="org-perl-non-scalar-variable">member_peers</span>);
+
+ <span class="org-keyword">for</span> <span class="org-keyword">my</span> $<span class="org-variable-name">p</span> (@<span class="org-perl-non-scalar-variable">all_peers</span>) {
+ <span class="org-keyword">my</span> ($<span class="org-variable-name">n</span>, $<span class="org-variable-name">h</span>, $<span class="org-variable-name">t</span>, $<span class="org-variable-name">k</span>, $<span class="org-variable-name">u</span>) = @$<span class="org-variable-name">p</span>;
+ <span class="org-keyword">die</span> <span class="org-string">"$n: name already in use by $u\n"</span>
+ <span class="org-keyword">if</span> $<span class="org-variable-name">name</span> eq $<span class="org-variable-name">n</span> && $<span class="org-variable-name">u</span> ne <span class="org-string">""</span>;
+ <span class="org-keyword">die</span> <span class="org-string">"$n: name already in use on campus\n"</span>
+ <span class="org-keyword">if</span> $<span class="org-variable-name">name</span> eq $<span class="org-variable-name">n</span> && $<span class="org-variable-name">u</span> eq <span class="org-string">""</span>;
}
+ <span class="org-keyword">my</span> $<span class="org-variable-name">hostnum</span> = (@<span class="org-perl-non-scalar-variable">all_peers</span>
+ ? 1 + $<span class="org-variable-name">all_peers</span>[$#<span class="org-perl-non-scalar-variable">all_peers</span>][1]
+ : 3);
+
+ push @{$<span class="org-variable-name">type</span> eq <span class="org-string">"campus"</span>
+ ? $<span class="org-variable-name">yaml</span>->{<span class="org-string">"clients"</span>}
+ : $<span class="org-variable-name">member</span>->{<span class="org-string">"clients"</span>}},
+ <span class="org-string">"$name $hostnum $type $pubkey"</span>;
+
umask 077;
- <span class="org-keyword">my</span> $<span class="org-variable-name">DEV</span> = $<span class="org-variable-name">type</span> eq <span class="org-string">"android"</span> ? <span class="org-string">"tun"</span> : <span class="org-string">"ovpn"</span>;
- <span class="org-keyword">my</span> $<span class="org-variable-name">CA</span> = read_file <span class="org-string">"Secret/CA/pki/ca.crt"</span>;
- <span class="org-keyword">my</span> $<span class="org-variable-name">CRT</span> = read_file <span class="org-string">"Secret/CA/pki/issued/$name.crt"</span>;
- <span class="org-keyword">my</span> $<span class="org-variable-name">KEY</span> = read_file <span class="org-string">"Secret/CA/pki/private/$name.key"</span>;
- <span class="org-keyword">my</span> $<span class="org-variable-name">UP</span> = $<span class="org-variable-name">type</span> eq <span class="org-string">"android"</span> ? <span class="org-string">""</span> : <span class="org-string">"</span>
-<span class="org-string"><<openvpn-up>>"</span>;
-
- <span class="org-keyword">if</span> ($<span class="org-variable-name">type</span> ne <span class="org-string">"campus"</span>) {
- <span class="org-keyword">my</span> $<span class="org-variable-name">TC</span> = read_file <span class="org-string">"Secret/front-shared.key"</span>;
- write_template ($<span class="org-variable-name">DEV</span>,$<span class="org-variable-name">UP</span>,$<span class="org-variable-name">CA</span>,$<span class="org-variable-name">CRT</span>,$<span class="org-variable-name">KEY</span>,$<span class="org-variable-name">TC</span>, $<span class="org-variable-name">front_addr</span>,
- $<span class="org-variable-name">domain_name</span>, <span class="org-string">"public.ovpn"</span>);
- print <span class="org-string">"Wrote public VPN configuration to public.ovpn.\n"</span>;
+ write_members_yaml $<span class="org-variable-name">yaml</span>;
+
+ <span class="org-keyword">if</span> ($<span class="org-variable-name">type</span> eq <span class="org-string">"campus"</span>) {
+ push @<span class="org-perl-non-scalar-variable">all_peers</span>, [ $<span class="org-variable-name">name</span>, $<span class="org-variable-name">hostnum</span>, $<span class="org-variable-name">type</span>, $<span class="org-variable-name">pubkey</span>, <span class="org-string">""</span> ];
+ } <span class="org-keyword">else</span> {
+ push @<span class="org-perl-non-scalar-variable">member_peers</span>, [ $<span class="org-variable-name">name</span>, $<span class="org-variable-name">hostnum</span>, $<span class="org-variable-name">type</span>, $<span class="org-variable-name">pubkey</span>, $<span class="org-variable-name">user</span> ];
+ push @<span class="org-perl-non-scalar-variable">all_peers</span>, [ $<span class="org-variable-name">name</span>, $<span class="org-variable-name">hostnum</span>, $<span class="org-variable-name">type</span>, $<span class="org-variable-name">pubkey</span>, $<span class="org-variable-name">user</span> ];
}
- <span class="org-keyword">my</span> $<span class="org-variable-name">TC</span> = read_file <span class="org-string">"Secret/gate-shared.key"</span>;
- write_template ($<span class="org-variable-name">DEV</span>,$<span class="org-variable-name">UP</span>,$<span class="org-variable-name">CA</span>,$<span class="org-variable-name">CRT</span>,$<span class="org-variable-name">KEY</span>,$<span class="org-variable-name">TC</span>, $<span class="org-variable-name">gate_wild_addr</span>,
- <span class="org-string">"gate.$domain_priv"</span>, <span class="org-string">"campus.ovpn"</span>);
- print <span class="org-string">"Wrote campus VPN configuration to campus.ovpn.\n"</span>;
- <span class="org-keyword">exit</span>;
+ <span class="org-keyword">my</span> $<span class="org-variable-name">core_wg_addr</span> = hostnum_to_ipaddr (2, $<span class="org-variable-name">public_wg_net_cidr</span>);
+ <span class="org-keyword">my</span> $<span class="org-variable-name">extra_front_config</span> = <span class="org-string">"</span>
+<span class="org-string">PostUp = resolvectl dns %i $core_addr</span>
+<span class="org-string">PostUp = resolvectl domain %i $domain_priv</span>
+
+<span class="org-string"># Core</span>
+<span class="org-string">[Peer]</span>
+<span class="org-string">PublicKey = $core_wg_pubkey</span>
+<span class="org-string">AllowedIPs = $core_wg_addr</span>
+<span class="org-string">AllowedIPs = $private_net_cidr</span>
+<span class="org-string">AllowedIPs = $campus_wg_net_cidr\n"</span>;
+
+ write_wg_server (<span class="org-string">"private/front-wg0.conf"</span>, \@<span class="org-perl-non-scalar-variable">member_peers</span>,
+ hostnum_to_ipaddr_cidr (1, $<span class="org-variable-name">public_wg_net_cidr</span>),
+ $<span class="org-variable-name">public_wg_port</span>, $<span class="org-variable-name">extra_front_config</span>)
+ <span class="org-keyword">if</span> $<span class="org-variable-name">type</span> ne <span class="org-string">"campus"</span>;
+ write_wg_server (<span class="org-string">"private/gate-wg0.conf"</span>, \@<span class="org-perl-non-scalar-variable">all_peers</span>,
+ hostnum_to_ipaddr_cidr (1, $<span class="org-variable-name">campus_wg_net_cidr</span>),
+ $<span class="org-variable-name">campus_wg_port</span>, <span class="org-string">"\n"</span>);
+
+ write_wg_client (<span class="org-string">"public.conf"</span>,
+ hostnum_to_ipaddr ($<span class="org-variable-name">hostnum</span>, $<span class="org-variable-name">public_wg_net_cidr</span>),
+ $<span class="org-variable-name">type</span>,
+ $<span class="org-variable-name">front_wg_pubkey</span>,
+ <span class="org-string">"$front_addr:$public_wg_port"</span>,
+ hostnum_to_ipaddr (1, $<span class="org-variable-name">public_wg_net_cidr</span>))
+ <span class="org-keyword">if</span> $<span class="org-variable-name">type</span> ne <span class="org-string">"campus"</span>;
+ write_wg_client (<span class="org-string">"campus.conf"</span>,
+ hostnum_to_ipaddr ($<span class="org-variable-name">hostnum</span>, $<span class="org-variable-name">campus_wg_net_cidr</span>),
+ $<span class="org-variable-name">type</span>,
+ $<span class="org-variable-name">gate_wg_pubkey</span>,
+ <span class="org-string">"$gate_wild_addr:$campus_wg_port"</span>,
+ hostnum_to_ipaddr (1, $<span class="org-variable-name">campus_wg_net_cidr</span>));
}
-<span class="org-keyword">sub</span> <span class="org-function-name">write_template</span> ($$$$$$$$$) {
- <span class="org-keyword">my</span> ($<span class="org-variable-name">DEV</span>,$<span class="org-variable-name">UP</span>,$<span class="org-variable-name">CA</span>,$<span class="org-variable-name">CRT</span>,$<span class="org-variable-name">KEY</span>,$<span class="org-variable-name">TC</span>,$<span class="org-variable-name">ADDR</span>,$<span class="org-variable-name">NAME</span>,$<span class="org-variable-name">FILE</span>) = @<span class="org-perl-non-scalar-variable">_</span>;
+<span class="org-keyword">sub</span> <span class="org-function-name">write_wg_server</span> ($$$$$) {
+ <span class="org-keyword">my</span> ($<span class="org-variable-name">file</span>, $<span class="org-variable-name">peers</span>, $<span class="org-variable-name">addr_cidr</span>, $<span class="org-variable-name">port</span>, $<span class="org-variable-name">extra</span>) = @<span class="org-perl-non-scalar-variable">_</span>;
<span class="org-keyword">my</span> $<span class="org-variable-name">O</span> = new IO::File;
- open ($<span class="org-variable-name">O</span>, <span class="org-string">">$FILE.tmp"</span>) or <span class="org-keyword">die</span> <span class="org-string">"Could not open $FILE.tmp: $!\n"</span>;
- print $<span class="org-variable-name">O</span> <span class="org-string">"client</span>
-<span class="org-string">dev-type tun</span>
-<span class="org-string">dev $DEV</span>
-<span class="org-string">remote $ADDR</span>
-<span class="org-string">nobind</span>
-<span class="org-string"><<openvpn-drop-priv>></span>
-<span class="org-string">remote-cert-tls server</span>
-<span class="org-string">verify-x509-name $NAME name</span>
-<span class="org-string"><<openvpn-crypt>>$UP</span>
-<span class="org-string">verb 3</span>
-<span class="org-string">key-direction 1</span>
-<span class="org-string"><ca>\n$CA</ca></span>
-<span class="org-string"><cert>\n$CRT</cert></span>
-<span class="org-string"><key>\n$KEY</key></span>
-<span class="org-string"><tls-crypt>\n$TC</tls-crypt>\n"</span>;
- close $<span class="org-variable-name">O</span> or <span class="org-keyword">die</span> <span class="org-string">"Could not close $FILE.tmp: $!\n"</span>;
- rename (<span class="org-string">"$FILE.tmp"</span>, $<span class="org-variable-name">FILE</span>)
- or <span class="org-keyword">die</span> <span class="org-string">"Could not rename $FILE.tmp: $!\n"</span>;
+ open ($<span class="org-variable-name">O</span>, <span class="org-string">">$file.tmp"</span>) or <span class="org-keyword">die</span> <span class="org-string">"Could not open $file.tmp: $!\n"</span>;
+ print $<span class="org-variable-name">O</span> <span class="org-string">"[Interface]</span>
+<span class="org-string">Address = $addr_cidr</span>
+<span class="org-string">ListenPort = $port</span>
+<span class="org-string">PostUp = wg set %i private-key /etc/wireguard/private-key$extra"</span>;
+ <span class="org-keyword">for</span> <span class="org-keyword">my</span> $<span class="org-variable-name">p</span> (@$<span class="org-variable-name">peers</span>) {
+ <span class="org-keyword">my</span> ($<span class="org-variable-name">n</span>, $<span class="org-variable-name">h</span>, $<span class="org-variable-name">t</span>, $<span class="org-variable-name">k</span>, $<span class="org-variable-name">u</span>) = @$<span class="org-variable-name">p</span>;
+ <span class="org-keyword">next</span> <span class="org-keyword">if</span> $<span class="org-variable-name">k</span> =~ <span class="org-string">/^-/</span>;
+ <span class="org-keyword">my</span> $<span class="org-variable-name">ip</span> = hostnum_to_ipaddr ($<span class="org-variable-name">h</span>, $<span class="org-variable-name">addr_cidr</span>);
+ print $<span class="org-variable-name">O</span> <span class="org-string">"</span>
+<span class="org-string"># $n</span>
+<span class="org-string">[Peer]</span>
+<span class="org-string">PublicKey = $k</span>
+<span class="org-string">AllowedIPs = $ip\n"</span>;
+ }
+ close $<span class="org-variable-name">O</span> or <span class="org-keyword">die</span> <span class="org-string">"Could not close $file.tmp: $!\n"</span>;
+ rename (<span class="org-string">"$file.tmp"</span>, $<span class="org-variable-name">file</span>)
+ or <span class="org-keyword">die</span> <span class="org-string">"Could not rename $file.tmp: $!\n"</span>;
}
-<span class="org-keyword">sub</span> <span class="org-function-name">read_file</span> ($) {
- <span class="org-keyword">my</span> ($<span class="org-variable-name">path</span>) = @<span class="org-perl-non-scalar-variable">_</span>;
- <span class="org-keyword">my</span> $<span class="org-variable-name">I</span> = new IO::File;
- open ($<span class="org-variable-name">I</span>, <span class="org-string">"<$path"</span>) or <span class="org-keyword">die</span> <span class="org-string">"$path: could not read: $!\n"</span>;
- <span class="org-keyword">local</span> $/;
- <span class="org-keyword">my</span> $<span class="org-variable-name">c</span> = <$<span class="org-variable-name">I</span>>;
- close $<span class="org-variable-name">I</span> or <span class="org-keyword">die</span> <span class="org-string">"$path: could not close: $!\n"</span>;
- <span class="org-keyword">return</span> $<span class="org-variable-name">c</span>;
+<span class="org-keyword">sub</span> <span class="org-function-name">write_wg_client</span> ($$$$$$) {
+ <span class="org-keyword">my</span> ($<span class="org-variable-name">file</span>, $<span class="org-variable-name">addr</span>, $<span class="org-variable-name">type</span>, $<span class="org-variable-name">pubkey</span>, $<span class="org-variable-name">endpt</span>, $<span class="org-variable-name">server_addr</span>) = @<span class="org-perl-non-scalar-variable">_</span>;
+ <span class="org-keyword">my</span> $<span class="org-variable-name">O</span> = new IO::File;
+ <span class="org-keyword">my</span> $<span class="org-variable-name">DNS</span> = ($<span class="org-variable-name">type</span> eq <span class="org-string">"android"</span>
+ ? <span class="org-string">"</span>
+<span class="org-string">DNS=$core_addr\nDomain=$domain_priv"</span>
+ : <span class="org-string">"</span>
+<span class="org-string">PostUp = resolvectl dns %i $core_addr</span>
+<span class="org-string">PostUp = resolvectl domain %i $domain_priv"</span>);
+ open ($<span class="org-variable-name">O</span>, <span class="org-string">">$file.tmp"</span>) or <span class="org-keyword">die</span> <span class="org-string">"Could not open $file.tmp: $!\n"</span>;
+ print $<span class="org-variable-name">O</span> <span class="org-string">"[Interface]</span>
+<span class="org-string">Address = $addr</span>
+<span class="org-string">PostUp = wg set %i private-key /etc/wireguard/private-key$DNS</span>
+
+<span class="org-string">[Peer]</span>
+<span class="org-string">PublicKey = $pubkey</span>
+<span class="org-string">EndPoint = $endpt</span>
+<span class="org-string">AllowedIPs = $server_addr</span>
+<span class="org-string">AllowedIPs = $private_net_cidr</span>
+<span class="org-string">AllowedIPs = $public_wg_net_cidr</span>
+<span class="org-string">AllowedIPs = $campus_wg_net_cidr\n"</span>;
+ close $<span class="org-variable-name">O</span> or <span class="org-keyword">die</span> <span class="org-string">"Could not close $file.tmp: $!\n"</span>;
+ rename (<span class="org-string">"$file.tmp"</span>, $<span class="org-variable-name">file</span>)
+ or <span class="org-keyword">die</span> <span class="org-string">"Could not rename $file.tmp: $!\n"</span>;
+
+ <span class="org-keyword">exit</span>;
}
+
+<span class="org-keyword">sub</span> <span class="org-function-name">hostnum_to_ipaddr</span> ($$)
+{
+ <span class="org-keyword">my</span> ($<span class="org-variable-name">hostnum</span>, $<span class="org-variable-name">net_cidr</span>) = @<span class="org-perl-non-scalar-variable">_</span>;
+
+ <span class="org-comment"># Assume 24bit subnet, 8bit hostnum.</span>
+ <span class="org-comment"># Find a Perl library for more generality?</span>
+ <span class="org-keyword">die</span> <span class="org-string">"$hostnum: hostnum too large\n"</span> <span class="org-keyword">if</span> $<span class="org-variable-name">hostnum</span> > 255;
+ <span class="org-keyword">my</span> ($<span class="org-variable-name">prefix</span>) = $<span class="org-variable-name">net_cidr</span> =~ m<span class="org-string">"^(\d+\.\d+\.\d+)\.\d+/24$";</span>
+<span class="org-string"> die if !$prefix;</span>
+<span class="org-string"> return "</span>$<span class="org-variable-name">prefix</span>.$<span class="org-variable-name">hostnum</span><span class="org-string">";</span>
+<span class="org-string">}</span>
+
+<span class="org-string">sub hostnum_to_ipaddr_cidr ($$)</span>
+<span class="org-string">{</span>
+<span class="org-string"> my ($hostnum, $net_cidr) = @_;</span>
+
+<span class="org-string"> # Assume 24bit subnet, 8bit hostnum.</span>
+<span class="org-string"> # Find a Perl library for more generality?</span>
+<span class="org-string"> die "</span>$<span class="org-variable-name">hostnum</span>: hostnum too large\n<span class="org-string">" if $hostnum > 255;</span>
+<span class="org-string"> my ($prefix) = $net_cidr =~ m"</span>^(\d+\.\d+\.\d+)\.\d+<span class="org-string">/24$";</span>
+<span class="org-string"> die if !$prefix;</span>
+<span class="org-string"> return "$prefix.$hostnum/</span>24<span class="org-string">";</span>
+<span class="org-string">}</span>
</code></pre>
</div>
</div>
</div>
-<div id="outline-container-orgb98a15e" class="outline-3">
-<h3 id="orgb98a15e"><span class="section-number-3">12.11.</span> Institute Command Help</h3>
+<div id="outline-container-org01141e3" class="outline-3">
+<h3 id="org01141e3"><span class="section-number-3">12.11.</span> Institute Command Help</h3>
<div class="outline-text-3" id="text-12-11">
<p>
This should be the last block tangled into the <a href="inst"><q>inst</q></a> script. It
</div>
</div>
</div>
-<div id="outline-container-org7832c43" class="outline-2">
-<h2 id="org7832c43"><span class="section-number-2">13.</span> Testing</h2>
+<div id="outline-container-org3d2b7a1" class="outline-2">
+<h2 id="org3d2b7a1"><span class="section-number-2">13.</span> Testing</h2>
<div class="outline-text-2" id="text-13">
<p>
The example files in this document, <a href="ansible.cfg"><q>ansible.cfg</q></a> and <a href="hosts"><q>hosts</q></a> as well
<p>
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 <a href="#orgbf29081">The (Actual) Hardware</a>, but
+The process is similar to that described in <a href="#org5af003d">The (Actual) Hardware</a>, 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
site at <a href="https://www.virtualbox.org/manual/UserManual.html">https://www.virtualbox.org/manual/UserManual.html</a>.
</p>
</div>
-<div id="outline-container-orgf8057e6" class="outline-3">
-<h3 id="orgf8057e6"><span class="section-number-3">13.1.</span> The Test Networks</h3>
+<div id="outline-container-org7da169b" class="outline-3">
+<h3 id="org7da169b"><span class="section-number-3">13.1.</span> The Test Networks</h3>
<div class="outline-text-3" id="text-13-1">
<p>
The networks used in the test:
</p>
</div>
</div>
-<div id="outline-container-orgd7a9abd" class="outline-3">
-<h3 id="orgd7a9abd"><span class="section-number-3">13.2.</span> The Test Machines</h3>
+<div id="outline-container-org78ae748" class="outline-3">
+<h3 id="org78ae748"><span class="section-number-3">13.2.</span> The Test Machines</h3>
<div class="outline-text-3" id="text-13-2">
<p>
The virtual machines are created by <code>VBoxManage</code> command lines in the
following sub-sections. They each start with a recent Debian release
(e.g. <q>debian-12.5.0-amd64-netinst.iso</q>) in their simulated DVD
-drives. As in <a href="#orgbf29081">The Hardware</a> preparation process being simulated, a few
-additional software packages are installed. Unlike in <a href="#orgbf29081">The Hardware</a>
+drives. As in <a href="#org5af003d">The Hardware</a> preparation process being simulated, a few
+additional software packages are installed. Unlike in <a href="#org5af003d">The Hardware</a>
preparation, machines are moved to their final networks and <i>then</i>
remote access is authorized. (They are not accessible via <code>ssh</code> on
the VirtualBox NAT network where they first boot.)
configuration by Ansible.
</p>
</div>
-<div id="outline-container-org310a70b" class="outline-4">
-<h4 id="org310a70b"><span class="section-number-4">13.2.1.</span> A Test Machine</h4>
+<div id="outline-container-org02a5366" class="outline-4">
+<h4 id="org02a5366"><span class="section-number-4">13.2.1.</span> A Test Machine</h4>
<div class="outline-text-4" id="text-13-2-1">
<p>
The following shell function contains most of the <code>VBoxManage</code>
</p>
</div>
</div>
-<div id="outline-container-org28db7d4" class="outline-4">
-<h4 id="org28db7d4"><span class="section-number-4">13.2.2.</span> The Test Front Machine</h4>
+<div id="outline-container-org944111f" class="outline-4">
+<h4 id="org944111f"><span class="section-number-4">13.2.2.</span> The Test Front Machine</h4>
<div class="outline-text-4" id="text-13-2-2">
<p>
The <code>front</code> machine is created with 512MiB of RAM, 4GiB of disk, and
its primary network interface moved to the simulated Internet, the NAT
network <code>premises</code>. <code>front</code> also gets a second network interface, on
the host-only network <code>vboxnet1</code>, to make it directly accessible to
-the administrator's notebook (as described in <a href="#orgf8057e6">The Test Networks</a>).
+the administrator's notebook (as described in <a href="#org7da169b">The Test Networks</a>).
</p>
<div class="org-src-container">
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: <a href="#orgab73a63">Ansible
+access by following the instructions in the final section: <a href="#org8af5300">Ansible
Test Authorization</a>.
</p>
</div>
</div>
-<div id="outline-container-org4da3a01" class="outline-4">
-<h4 id="org4da3a01"><span class="section-number-4">13.2.3.</span> The Test Gate Machine</h4>
+<div id="outline-container-org667e79f" class="outline-4">
+<h4 id="org667e79f"><span class="section-number-4">13.2.3.</span> The Test Gate Machine</h4>
<div class="outline-text-4" id="text-13-2-3">
<p>
The <code>gate</code> machine is created with the same amount of RAM and disk as
</div>
<p>
-After Debian is installed (as detailed in <a href="#org310a70b">A Test Machine</a>) and the
+After Debian is installed (as detailed in <a href="#org02a5366">A Test Machine</a>) and the
machine rebooted, the administrator logs in and installs several
additional software packages.
</p>
<div class="org-src-container">
<pre class="src src-sh"><code>sudo apt install netplan.io systemd-resolved unattended-upgrades <span class="org-sh-escaped-newline">\</span>
- ufw isc-dhcp-server postfix openvpn
+ ufw isc-dhcp-server postfix wireguard
</code></pre>
</div>
<p>
Finally, the administrator authorizes remote access by following the
-instructions in the final section: <a href="#orgab73a63">Ansible Test Authorization</a>.
+instructions in the final section: <a href="#org8af5300">Ansible Test Authorization</a>.
</p>
</div>
</div>
-<div id="outline-container-org1f40acc" class="outline-4">
-<h4 id="org1f40acc"><span class="section-number-4">13.2.4.</span> The Test Core Machine</h4>
+<div id="outline-container-orge993c12" class="outline-4">
+<h4 id="orge993c12"><span class="section-number-4">13.2.4.</span> The Test Core Machine</h4>
<div class="outline-text-4" id="text-13-2-4">
<p>
The <code>core</code> machine is created with 1GiB of RAM and 6GiB of disk.
</div>
<p>
-After Debian is installed (as detailed in <a href="#org310a70b">A Test Machine</a>) and the
+After Debian is installed (as detailed in <a href="#org02a5366">A Test Machine</a>) and the
machine rebooted, the administrator logs in and installs several
additional software packages.
</p>
<div class="org-src-container">
<pre class="src src-sh"><code>sudo apt install netplan.io systemd-resolved unattended-upgrades <span class="org-sh-escaped-newline">\</span>
- ntp isc-dhcp-server bind9 apache2 openvpn <span class="org-sh-escaped-newline">\</span>
+ ntp isc-dhcp-server bind9 apache2 wireguard <span class="org-sh-escaped-newline">\</span>
postfix dovecot-imapd fetchmail expect rsync <span class="org-sh-escaped-newline">\</span>
gnupg
sudo apt install mariadb-server php php-{apcu,bcmath,curl,gd,gmp}<span class="org-sh-escaped-newline">\</span>
<p>
Finally, the administrator authorizes remote access by following the
-instructions in the next section: <a href="#orgab73a63">Ansible Test Authorization</a>.
+instructions in the next section: <a href="#org8af5300">Ansible Test Authorization</a>.
</p>
</div>
</div>
-<div id="outline-container-orgab73a63" class="outline-4">
-<h4 id="orgab73a63"><span class="section-number-4">13.2.5.</span> Ansible Test Authorization</h4>
+<div id="outline-container-org8af5300" class="outline-4">
+<h4 id="org8af5300"><span class="section-number-4">13.2.5.</span> Ansible Test Authorization</h4>
<div class="outline-text-4" id="text-13-2-5">
<p>
To authorize Ansible's access to the three test machines, they must
</div>
</div>
</div>
-<div id="outline-container-org870180a" class="outline-3">
-<h3 id="org870180a"><span class="section-number-3">13.3.</span> Configure Test Machines</h3>
+<div id="outline-container-org885ec79" class="outline-3">
+<h3 id="org885ec79"><span class="section-number-3">13.3.</span> Configure Test Machines</h3>
<div class="outline-text-3" id="text-13-3">
<p>
At this point the three test machines <code>core</code>, <code>gate</code>, and <code>front</code> are
</p>
</div>
</div>
-<div id="outline-container-orga849e1c" class="outline-3">
-<h3 id="orga849e1c"><span class="section-number-3">13.4.</span> Test Basics</h3>
+<div id="outline-container-org340680b" class="outline-3">
+<h3 id="org340680b"><span class="section-number-3">13.4.</span> Test Basics</h3>
<div class="outline-text-3" id="text-13-4">
<p>
At this point the test institute is just <code>core</code>, <code>gate</code> and <code>front</code>,
</p>
</div>
</div>
-<div id="outline-container-org560f2c4" class="outline-3">
-<h3 id="org560f2c4"><span class="section-number-3">13.5.</span> The Test Nextcloud</h3>
+<div id="outline-container-org4fd6e9c" class="outline-3">
+<h3 id="org4fd6e9c"><span class="section-number-3">13.5.</span> The Test Nextcloud</h3>
<div class="outline-text-3" id="text-13-5">
<p>
Further tests involve Nextcloud account management. Nextcloud is
-installed on <code>core</code> as described in <a href="#org640e4f7">Configure Nextcloud</a>. Once
+installed on <code>core</code> as described in <a href="#org68d4766">Configure Nextcloud</a>. Once
<q>/Nextcloud/</q> is created, <code>./inst config core</code> will validate
or update its configuration files.
</p>
<p>
The process starts with enrolling the first member of the institute
-using the <code>./inst new</code> command and issuing client VPN keys with the
-<code>./inst client</code> command.
+using the <code>./inst new</code> command and registering a client's public key
+with the <code>./inst client</code> command.
</p>
</div>
</div>
-<div id="outline-container-orgd08a549" class="outline-3">
-<h3 id="orgd08a549"><span class="section-number-3">13.6.</span> Test New Command</h3>
+<div id="outline-container-org3a8cb8b" class="outline-3">
+<h3 id="org3a8cb8b"><span class="section-number-3">13.6.</span> Test New Command</h3>
<div class="outline-text-3" id="text-13-6">
<p>
A member must be enrolled so that a member's client machine can be
</p>
</div>
</div>
-<div id="outline-container-orgdab6eef" class="outline-3">
-<h3 id="orgdab6eef"><span class="section-number-3">13.7.</span> The Test Member Notebook</h3>
+<div id="outline-container-orgca38551" class="outline-3">
+<h3 id="orgca38551"><span class="section-number-3">13.7.</span> The Test Member Notebook</h3>
<div class="outline-text-3" id="text-13-7">
<p>
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
<code>gate</code> and <code>front</code>, and the Nextcloud installation on <code>core</code>.
</p>
</p>
<p>
-Debian is installed much as detailed in <a href="#org310a70b">A Test Machine</a> <i>except</i> that
+Debian is installed much as detailed in <a href="#org02a5366">A Test Machine</a> <i>except</i> that
the SSH server option is <i>not</i> needed and the GNOME desktop option
<i>is</i>. When the machine reboots, the administrator logs into the
desktop and installs a couple additional software packages (which
</p>
<div class="org-src-container">
-<pre class="src src-nil"><code>sudo apt install network-manager-openvpn-gnome \
- openvpn-systemd-resolved \
- nextcloud-desktop evolution
+<pre class="src src-nil"><code>sudo apt install wireguard nextcloud-desktop evolution
</code></pre>
</div>
</div>
</div>
-<div id="outline-container-orgec34fb6" class="outline-3">
-<h3 id="orgec34fb6"><span class="section-number-3">13.8.</span> Test Client Command</h3>
+<div id="outline-container-org8af30e9" class="outline-3">
+<h3 id="org8af30e9"><span class="section-number-3">13.8.</span> Test Client Command</h3>
<div class="outline-text-3" id="text-13-8">
<p>
-The <code>./inst client</code> command is used to issue keys for the institute's
-VPNs. The following command generates two <q>.ovpn</q> (OpenVPN
-configuration) files, <q>small.ovpn</q> and <q>campus.ovpn</q>, authorizing
-access by the holder, identified as <code>dick</code>, owned by member <code>dick</code>, to
-the test VPNs.
+The <code>./inst client</code> 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, <code>dick</code>, to the institute
+VPNs. First he generates a pair of WireGuard™ keys by running the
+following commands on Dick's notebook.
</p>
<div class="org-src-container">
-<pre class="src src-sh"><code>./inst client debian dick dick
+<pre class="src src-sh"><code>( <span class="org-builtin">umask</span> 077; wg genkey >private)
+wg pubkey <private >public
+</code></pre>
+</div>
+
+<p>
+The administrator uses the key in <q>public</q> to run the following
+command, generating <q>campus.conf</q> and <q>public.conf</q> files.
+</p>
+
+<div class="org-src-container">
+<pre class="src src-sh"><code>./inst client debian dick dick <span class="org-sh-escaped-newline">\</span>
+ 4qd4xdRztZBKhFrX9jI/b4fnMzpKQ5qhg691hwYSsX8=
</code></pre>
</div>
</div>
</div>
-<div id="outline-container-org788a72a" class="outline-3">
-<h3 id="org788a72a"><span class="section-number-3">13.9.</span> Test Campus VPN</h3>
+<div id="outline-container-orgc52b7a2" class="outline-3">
+<h3 id="orgc52b7a2"><span class="section-number-3">13.9.</span> Test Campus WireGuard™ Subnet</h3>
<div class="outline-text-3" id="text-13-9">
<p>
-The <q>campus.ovpn</q> OpenVPN configuration file (generated in <a href="#orgec34fb6">Test Client
-Command</a>) is transferred to <code>dick</code>, which is at the Wi-Fi access
-point's <code>wifi_wan_addr</code>.
+The <q>campus.conf</q> WireGuard™ configuration file (generated in <a href="#org8af30e9">Test
+Client Command</a>) is transferred to <code>dick</code>, which is at the Wi-Fi access
+point's IP address, host 2 on the wild Ethernet.
</p>
<div class="org-src-container">
-<pre class="src src-sh"><code>scp *.ovpn sysadm@192.168.57.2:
+<pre class="src src-sh"><code>scp *.conf sysadm@192.168.57.2:
</code></pre>
</div>
<p>
-The file is installed using the Network tab of the desktop Settings
-app. The administrator uses the "+" button, chooses "Import from
-file…" and the <q>campus.ovpn</q> file. <i>Importantly</i> 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 <q>small.ovpn</q> file, for use on the simulated Internet.
+Dick then pastes his notebook's private key into the template
+<q>campus.conf</q> file and installs the result in
+<q>/etc/wireguard/wg0.conf</q>, doing the same to complete <q>public.conf</q>
+and install it in <q>/etc/wireguard/wg1.conf</q>.
</p>
<p>
-The administrator turns on the campus VPN on <code>dick</code> (which connects
-instantly) and does a few basic tests in a terminal.
+To connect to the campus VPN, the following command is run.
+</p>
+
+<div class="org-src-container">
+<pre class="src src-sh"><code>systemctl start wg-quick@wg0
+</code></pre>
+</div>
+
+<p>
+A few basic tests are then performed in a terminal.
</p>
<div class="org-src-container">
<pre class="src src-sh"><code>systemctl status
-ping -c 1 8.8.4.4 <span class="org-comment-delimiter"># </span><span class="org-comment">dns.google</span>
+ping -c 1 8.8.8.8 <span class="org-comment-delimiter"># </span><span class="org-comment">dns.google</span>
ping -c 1 192.168.56.1 <span class="org-comment-delimiter"># </span><span class="org-comment">core</span>
host dns.google
host core.small.private
</div>
</div>
</div>
-<div id="outline-container-org6342178" class="outline-3">
-<h3 id="org6342178"><span class="section-number-3">13.10.</span> Test Web Pages</h3>
+<div id="outline-container-org6257673" class="outline-3">
+<h3 id="org6257673"><span class="section-number-3">13.10.</span> Test Web Pages</h3>
<div class="outline-text-3" id="text-13-10">
<p>
Next, the administrator copies <a href="Backup/WWW/"><q>Backup/WWW/</q></a> (included in the
</p>
</div>
</div>
-<div id="outline-container-org9931f74" class="outline-3">
-<h3 id="org9931f74"><span class="section-number-3">13.11.</span> Test Web Update</h3>
+<div id="outline-container-org2959fda" class="outline-3">
+<h3 id="org2959fda"><span class="section-number-3">13.11.</span> Test Web Update</h3>
<div class="outline-text-3" id="text-13-11">
<p>
Modify <q>/WWW/live/index.html</q> on <code>core</code> and wait 15 minutes for it to
</p>
</div>
</div>
-<div id="outline-container-org0145aad" class="outline-3">
-<h3 id="org0145aad"><span class="section-number-3">13.12.</span> Test Nextcloud</h3>
+<div id="outline-container-org99a0083" class="outline-3">
+<h3 id="org99a0083"><span class="section-number-3">13.12.</span> Test Nextcloud</h3>
<div class="outline-text-3" id="text-13-12">
<p>
Nextcloud is typically installed and configured <i>after</i> the first
installation directory <q>/Nextcloud/nextcloud/</q> appears, the Ansible
code skips parts of the Nextcloud configuration. The same
installation (or restoration) process used on Core is used on <code>core</code>
-to create <q>/Nextcloud/</q>. The process starts with <a href="#orgdb3439f">Create
-<q>/Nextcloud/</q></a>, involves <a href="#org3c8ed7b">Restore Nextcloud</a> or <a href="#orgb7a7660">Install Nextcloud</a>,
-and runs <code>./inst config core</code> again <a href="#orgdc9a64a">8.23.6</a>. When the <code>./inst
+to create <q>/Nextcloud/</q>. The process starts with <a href="#org7ba56a3">Create
+<q>/Nextcloud/</q></a>, involves <a href="#orgabfc848">Restore Nextcloud</a> or <a href="#org50fbfc3">Install Nextcloud</a>,
+and runs <code>./inst config core</code> again <a href="#orgb106f73">8.23.6</a>. When the <code>./inst
config core</code> command is happy with the Nextcloud configuration on
<code>core</code>, the administrator uses Dick's notebook to test it, performing
the following tests on <code>dick</code>'s desktop.
</ul>
</div>
</div>
-<div id="outline-container-org89efb80" class="outline-3">
-<h3 id="org89efb80"><span class="section-number-3">13.13.</span> Test Email</h3>
+<div id="outline-container-orge632edc" class="outline-3">
+<h3 id="orge632edc"><span class="section-number-3">13.13.</span> Test Email</h3>
<div class="outline-text-3" id="text-13-13">
<p>
With Evolution running on the member notebook <code>dick</code>, one second email
</p>
</div>
</div>
-<div id="outline-container-orgd3f059a" class="outline-3">
-<h3 id="orgd3f059a"><span class="section-number-3">13.14.</span> Test Public VPN</h3>
+<div id="outline-container-org6bd435a" class="outline-3">
+<h3 id="org6bd435a"><span class="section-number-3">13.14.</span> Test Public VPN</h3>
<div class="outline-text-3" id="text-13-14">
<p>
At this point, <code>dick</code> can move abroad, from the campus Wi-Fi
</div>
<p>
-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 <code>campus</code> VPN should
-disconnect. After it does, the administrator turns on the <code>small</code>
-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.
+</p>
+
+<div class="org-src-container">
+<pre class="src src-sh"><code>systemctl stop wg-quick@wg0
+systemctl start wg-quick@wg1
+</code></pre>
+</div>
+
+<p>
+Again, some basics are tested in a terminal.
</p>
<div class="org-src-container">
</p>
</div>
</div>
-<div id="outline-container-org1fd1f8b" class="outline-3">
-<h3 id="org1fd1f8b"><span class="section-number-3">13.15.</span> Test Pass Command</h3>
+<div id="outline-container-orge9a81e6" class="outline-3">
+<h3 id="orge9a81e6"><span class="section-number-3">13.15.</span> Test Pass Command</h3>
<div class="outline-text-3" id="text-13-15">
<p>
To test the <code>./inst pass</code> command, the administrator logs in to <code>core</code>
</p>
</div>
</div>
-<div id="outline-container-org567770b" class="outline-3">
-<h3 id="org567770b"><span class="section-number-3">13.16.</span> Test Old Command</h3>
+<div id="outline-container-org2b49661" class="outline-3">
+<h3 id="org2b49661"><span class="section-number-3">13.16.</span> Test Old Command</h3>
<div class="outline-text-3" id="text-13-16">
<p>
One more institute command is left to exercise. The administrator
<p>
The administrator tests Dick's access to <code>core</code>, <code>front</code> and
-Nextcloud, and attempts to re-connect the <code>small</code> VPN. All of these
-should fail.
+Nextcloud, and attempts to access the campus VPN. All of these should
+fail.
</p>
</div>
</div>
</div>
-<div id="outline-container-orgf6f9793" class="outline-2">
-<h2 id="orgf6f9793"><span class="section-number-2">14.</span> Future Work</h2>
+<div id="outline-container-orgc795c6a" class="outline-2">
+<h2 id="orgc795c6a"><span class="section-number-2">14.</span> Future Work</h2>
<div class="outline-text-2" id="text-14">
<p>
The small institute's network, as currently defined in this doocument,
is lacking in a number of respects.
</p>
</div>
-<div id="outline-container-org26639c5" class="outline-3">
-<h3 id="org26639c5"><span class="section-number-3">14.1.</span> Deficiencies</h3>
+<div id="outline-container-orgf084076" class="outline-3">
+<h3 id="orgf084076"><span class="section-number-3">14.1.</span> Deficiencies</h3>
<div class="outline-text-3" id="text-14-1">
<p>
The current network monitoring is rudimentary. It could use some
</p>
<p>
-The institute's private domain names (e.g. <code>www.small.private</code>) are
-not resolvable on Front. Reverse domains (<code>86.177.10.in-addr.arpa</code>)
-mapping institute network addresses back to names in the private
-domain <code>small.private</code> work only on the campus Ethernet. These nits
-might be picked when OpenVPN supports the DHCP option
-<code>rdnss-selection</code> (RFC6731), or with hard-coded <code>resolvectl</code> commands.
-</p>
-
-<p>
-The <code>./inst old dick</code> 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.
-</p>
-
-<p>
-The <code>./inst client android dick-phone dick</code> command generates <q>.ovpn</q>
-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.
-</p>
-
-<p>
-The VPN service is overly complex. The OpenVPN 2.4.7 clients allow
-multiple server addresses, but the <code>openvpn(8)</code> manual page suggests
-per connection parameters are restricted to a set that does <i>not</i>
-include the essential <code>verify-x509-name</code>. 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. <code>86.177.10.in-addr.arpa</code>) are
+not available on Front, yet.
</p>
</div>
</div>
-<div id="outline-container-orgfe73858" class="outline-3">
-<h3 id="orgfe73858"><span class="section-number-3">14.2.</span> More Tests</h3>
+<div id="outline-container-org86db359" class="outline-3">
+<h3 id="org86db359"><span class="section-number-3">14.2.</span> More Tests</h3>
<div class="outline-text-3" id="text-14-2">
<p>
The testing process described in the previous chapter is far from
complete. Additional tests are needed.
</p>
</div>
-<div id="outline-container-orgc61d0e9" class="outline-4">
-<h4 id="orgc61d0e9"><span class="section-number-4">14.2.1.</span> Backup</h4>
+<div id="outline-container-orgf829be6" class="outline-4">
+<h4 id="orgf829be6"><span class="section-number-4">14.2.1.</span> Backup</h4>
<div class="outline-text-4" id="text-14-2-1">
<p>
The <code>backup</code> command has not been tested. It needs an encrypted
</p>
</div>
</div>
-<div id="outline-container-org6d144a5" class="outline-4">
-<h4 id="org6d144a5"><span class="section-number-4">14.2.2.</span> Restore</h4>
+<div id="outline-container-org90cf226" class="outline-4">
+<h4 id="org90cf226"><span class="section-number-4">14.2.2.</span> Restore</h4>
<div class="outline-text-4" id="text-14-2-2">
<p>
The restore process has not been tested. It might just copy <a href="Backup/"><q>Backup/</q></a>
</p>
</div>
</div>
-<div id="outline-container-org876c9cb" class="outline-4">
-<h4 id="org876c9cb"><span class="section-number-4">14.2.3.</span> Campus Disconnect</h4>
+<div id="outline-container-org9298478" class="outline-4">
+<h4 id="org9298478"><span class="section-number-4">14.2.3.</span> Campus Disconnect</h4>
<div class="outline-text-4" id="text-14-2-3">
<p>
Email access (IMAPS) on <code>front</code> is… difficult to test unless
</div>
</div>
</div>
-<div id="outline-container-orgb988f90" class="outline-2">
-<h2 id="orgb988f90"><span class="section-number-2">15.</span> Appendix: The Bootstrap</h2>
+<div id="outline-container-orgd76b9d9" class="outline-2">
+<h2 id="orgd76b9d9"><span class="section-number-2">15.</span> Appendix: The Bootstrap</h2>
<div class="outline-text-2" id="text-15">
<p>
Creating the private network from whole cloth (machines with recent
get to the additional packages.
</p>
</div>
-<div id="outline-container-org7fe37c6" class="outline-3">
-<h3 id="org7fe37c6"><span class="section-number-3">15.1.</span> The Current Strategy</h3>
+<div id="outline-container-org65958ab" class="outline-3">
+<h3 id="org65958ab"><span class="section-number-3">15.1.</span> The Current Strategy</h3>
<div class="outline-text-3" id="text-15-1">
<p>
-The strategy pursued in <a href="#orgbf29081">The Hardware</a> is two phase: prepare the servers
+The strategy pursued in <a href="#org5af003d">The Hardware</a> 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
</p>
</div>
</div>
-<div id="outline-container-org94b8b56" class="outline-3">
-<h3 id="org94b8b56"><span class="section-number-3">15.2.</span> Starting With Gate</h3>
+<div id="outline-container-org55fb799" class="outline-3">
+<h3 id="org55fb799"><span class="section-number-3">15.2.</span> Starting With Gate</h3>
<div class="outline-text-3" id="text-15-2">
<p>
The strategy of Starting With Gate concentrates on configuring Gate's
</ul>
</div>
</div>
-<div id="outline-container-org1c34ede" class="outline-3">
-<h3 id="org1c34ede"><span class="section-number-3">15.3.</span> Pre-provision With Ansible</h3>
+<div id="outline-container-orgcc3f919" class="outline-3">
+<h3 id="orgcc3f919"><span class="section-number-3">15.3.</span> Pre-provision With Ansible</h3>
<div class="outline-text-3" id="text-15-3">
<p>
A refinement of the current strategy might avoid the need to maintain
</div></div>
<div id="postamble" class="status">
<p class="author">Author: Matt Birkholz</p>
-<p class="date">Created: 2025-05-31 Sat 22:27</p>
+<p class="date">Created: 2025-06-15 Sun 12:23</p>
<p class="validation"><a href="https://validator.w3.org/check?uri=referer">Validate</a></p>
</div>
</body>