summaryrefslogtreecommitdiff
path: root/live/blog/a/vpn_server_with_openvpn.html
blob: 845635253ce3fb9950f6b0f9d9313ef44417e769 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
<!DOCTYPE html>
<html class="theme-dark" lang="en
"
  prefix="og: https://ogp.me/ns#">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="icon" href="https://static.luevano.xyz/images/icons/favicon.ico">
<title>Set up a VPN server with OpenVPN -- Luévano's Blog</title>
  <meta name="description" content="How to set up a VPN server using OpenVPN on a server running Nginx, on Arch. Only for IPv4."/>
<link rel="alternate" type="application/rss+xml" href="https://blog.luevano.xyz/rss.xml" title="Luévano's Blog RSS">
    <!-- general style -->
    <link rel="stylesheet" type="text/css" href="https://static.luevano.xyz/css/style.css">
    <link rel="stylesheet" type="text/css" href="https://static.luevano.xyz/fork-awesome/css/fork-awesome.min.css">
    <link rel="stylesheet" type="text/css" href="https://static.luevano.xyz/font-awesome/css/all.min.css">
    <!-- theme related -->
    <script type="text/javascript" src="https://static.luevano.xyz/scripts/theme.js"></script>
    <link id="theme-css" rel="stylesheet" type="text/css" href="https://static.luevano.xyz/css/theme.css">
    <!-- misc functions-->
    <script type="text/javascript" src="https://static.luevano.xyz/scripts/return_top.js"></script>
    <!-- extra -->
    <!-- highlight support for code blocks -->
<script type="text/javascript" src="https://static.luevano.xyz/hl/highlight.min.js"></script>
<script type="text/javascript">
  hljs.initHighlightingOnLoad();
</script>
<link id="code-theme-css" rel="stylesheet" type="text/css" href="https://static.luevano.xyz/hl/styles/nord.min.css">






    <!-- og meta -->
  <meta property="og:title" content="Set up a VPN server with OpenVPN -- Luévano's Blog"/>
  <meta property="og:type" content="article"/>
  <meta property="og:url" content="https://blog.luevano.xyz/a/vpn_server_with_openvpn.md"/>
  <meta property="og:image" content="https://static.luevano.xyz/images/b/default.png"/>
  <meta property="og:description" content="How to set up a VPN server using OpenVPN on a server running Nginx, on Arch. Only for IPv4."/>
  <meta property="og:locale" content="en"/>
  <meta property="og:site_name" content="Luévano's Blog"/>
  </head>

  <body>
    <header>
<nav>
  <ul>
    <li>
      <a href="https://luevano.xyz/"><i class="fas fa-home" alt="Home"></i><span>Home</span></a>
    </li>

    <li>
      <a href="https://blog.luevano.xyz/"><i class="fas fa-book-open" alt="Blog"></i><span>Blog</span></a>
    </li>

    <li>
      <a href="https://art.luevano.xyz/"><i class="fas fa-paint-brush" alt="Art"></i><span>Art</span></a>
    </li>

    <li><i class="fab fa-git" alt="Git"></i><span>Git</span>
      <ul>
        <li><a href="https://git.luevano.xyz/" target="_blank"><i class="fab fa-git-alt" alt="Git-alt"></i></a></li>

        <li><a href="https://github.com/luevano" target="_blank"><i class="fab fa-github" alt="Github"></i></a></li>

        <li><a href="https://gitlab.com/dluevano" target="_blank"><i class="fab fa-gitlab" alt="Gitlab"></i></a></li>
      </ul>
    </li>

    <li><i class="fas fa-box-open" alt="Stuff"></i><span>Stuff</span>
      <ul>
        <li><a href="https://gb.luevano.xyz/"><i class="fas fa-gamepad" alt="Gameboy"></i><span>Gameboy</span></a></li>
      </ul>
    </li>
  </ul>
</nav>

<button class="theme-switcher" onclick="toggleTheme()"><i class="fas fa-moon"></i><i class="fas fa-sun"></i></button>

    </header>

    <main>
      <div class="return-top">
        <button class="return-top" onclick="returnTop()" id="returnTopButton">
        <i class="fas fa-arrow-up" alt="Return to top"></i>
        </button>
      </div>
  <h1>Set up a VPN server with OpenVPN</h1>

  <p>I&rsquo;ve been wanting to do this entry, but had no time to do it since I also have to set up the VPN service as well to make sure what I&rsquo;m writing makes sense, today is the day.</p>
<p>Like with any other of my entries I based my setup on the <a href="https://wiki.archlinux.org/title/OpenVPN">Arch Wiki</a>, <a href="https://github.com/Nyr/openvpn-install">this install script</a> and <a href="https://github.com/graysky2/ovpngen">this profile generator script</a>.</p>
<p>This will be installed and working alongside the other stuff I&rsquo;ve wrote about on other posts (see the <a href="https://blog.luevano.xyz/tag/@server.html">server</a> tag). All commands here are executes as root unless specified otherwise. Also, this is intended only for IPv4 (it&rsquo;s not that hard to include IPv6, but meh). As always, all commands are executed as root unless stated otherwise.</p>
<h2 id="table-of-contents">Table of contents<a class="headerlink" href="#table-of-contents" title="Permanent link">&para;</a></h2>
<div class="toc">
<ul>
<li><a href="#table-of-contents">Table of contents</a></li>
<li><a href="#prerequisites">Prerequisites</a></li>
<li><a href="#create-pki-from-scratch">Create PKI from scratch</a></li>
<li><a href="#openvpn">OpenVPN</a><ul>
<li><a href="#enable-forwarding">Enable forwarding</a></li>
<li><a href="#create-client-configurations">Create client configurations</a></li>
</ul>
</li>
</ul>
</div>
<h2 id="prerequisites">Prerequisites<a class="headerlink" href="#prerequisites" title="Permanent link">&para;</a></h2>
<p>Pretty simple:</p>
<ul>
<li>Working server with root access, and with <code>ufw</code> as the firewall.</li>
<li>Open port <code>1194</code> (default), or as a fallback on <code>443</code> (click <a href="https://openvpn.net/vpn-server-resources/advanced-option-settings-on-the-command-line/">here</a> for more). I will do mine on port <code>1194</code> but it&rsquo;s just a matter of changing 2 lines of configuration and one <code>ufw</code> rule.</li>
</ul>
<h2 id="create-pki-from-scratch">Create PKI from scratch<a class="headerlink" href="#create-pki-from-scratch" title="Permanent link">&para;</a></h2>
<p>PKI stands for <em>Public Key Infrastructure</em> and basically it&rsquo;s required for certificates, private keys and more. This is supposed to work between two servers and one client: a server in charge of creating, signing and verifying the certificates, a server with the OpenVPN service running and the client making the request.</p>
<p>In a nutshel, this is supposed to work something like: 1) a client wants to use the VPN service, so it creates a requests and sends it to the signing server, 2) this server checks the requests and signs the request, returning the certificates to both the VPN service and the client and 3) the client can now connect to the VPN service using the signed certificate which the OpenVPN server knows about.</p>
<p>That&rsquo;s how the it should be st up&hellip; but, to be honest, all of this is a hassle and (in my case) I want something simple to use and manage. So I&rsquo;m gonna do all on one server and then just give away the configuration file for the clients, effectively generating files that anyone can run and will work, meaning that you need to be careful who you give this files (it also comes with a revoking mechanism, so no worries).</p>
<p>This is done with <a href="https://wiki.archlinux.org/title/Easy-RSA">Easy-RSA</a>.</p>
<p>Install the <code>easy-rsa</code> package:</p>
<pre><code class="language-sh">pacman -S easy-rsa
</code></pre>
<p>Initialize the PKI and generate the CA keypair:</p>
<pre><code class="language-sh">cd /etc/easy-rsa
easyrsa init-pki
easyrsa build-ca nopass
</code></pre>
<p>Create the server certificate and private key (while in the same directory):</p>
<pre><code class="language-sh">EASYRSA_CERT_EXPIRE=3650 easyrsa build-server-full server nopass
</code></pre>
<p>Where <code>server</code> is just a name to identify your server certificate keypair, I just use <code>server</code> but could be anything (like <code>luevano.xyz</code> in my case).</p>
<p>Create the client revocation list AKA CRL (will be used later, but might as well have it now):</p>
<pre><code class="language-sh">EASYRSA_CRL_DAYS=3650 easyrsa gen-crl
</code></pre>
<p>After this we should have 6 new files:</p>
<pre><code>/etc/easy-rsa/pki/ca.crt
/etc/easy-rsa/pki/private/ca.key
/etc/easy-rsa/pki/issued/server.crt
/etc/easy-rsa/pki/reqs/server.req
/etc/easy-rsa/pki/private/server.key
/etc/easy-rsa/pki/crl.pem
</code></pre>
<p>It is recommended to copy some of these files over to the <code>openvpn</code> directory, but I prefer to keep them here and just change some of the permissions:</p>
<pre><code class="language-sh">chmod o+rx pki
chmod o+rx pki/ca.crt
chmod o+rx pki/issued
chmod o+rx pki/issued/server.crt
chmod o+rx pki/private
chmod o+rx pki/private/server.key
chown nobody:nobody pki/crl.pem
chmod o+r pki/crl.pem
</code></pre>
<p>Finally, go to the <code>openvpn</code> directory and create the required files there:</p>
<pre><code class="language-sh">cd /etc/openvpn/server
openssl dhparam -out dh.pem 2048
openvpn --genkey secret ta.key
</code></pre>
<h2 id="openvpn">OpenVPN<a class="headerlink" href="#openvpn" title="Permanent link">&para;</a></h2>
<p><a href="https://wiki.archlinux.org/title/OpenVPN">OpenVPN</a> is a robust and highly flexible VPN daemon, that&rsquo;s pretty complete feature-wise.</p>
<p>Install the <code>openvpn</code> package:</p>
<pre><code class="language-sh">pacman -S openvpn
</code></pre>
<p>Now, most of the stuff is going to be handled by (each, if you have more than one) server configuration. This might be the hardest thing to configure, but I&rsquo;ve used a basic configuration file that worked a lot to me, which is a compilation of stuff that I found on the internet while configuring the file a while back.</p>
<pre><code># Server ip addres (ipv4).
local 1.2.3.4 # your server public ip

# Port.
port 1194 # Might want to change it to 443

# TCP or UDP.
;proto tcp
proto udp # If ip changes to 443, you should change this to tcp, too

# &quot;dev tun&quot; will create a routed IP tunnel,
# &quot;dev tap&quot; will create an ethernet tunnel.
;dev tap
dev tun

# Server specific certificates and more.
ca /etc/easy-rsa/pki/ca.crt
cert /etc/easy-rsa/pki/issued/server.crt
key /etc/easy-rsa/pki/private/server.key  # This file should be kept secret.
dh /etc/openvpn/server/dh.pem
auth SHA512
tls-crypt /etc/openvpn/server/ta.key 0 # This file is secret.
crl-verify /etc/easy-rsa/pki/crl.pem

# Network topology.
topology subnet

# Configure server mode and supply a VPN subnet
# for OpenVPN to draw client addresses from.
server 10.8.0.0 255.255.255.0

# Maintain a record of client &lt;-&gt; virtual IP address
# associations in this file.
ifconfig-pool-persist ipp.txt

# Push routes to the client to allow it
# to reach other private subnets behind
# the server.
;push &quot;route 192.168.10.0 255.255.255.0&quot;
;push &quot;route 192.168.20.0 255.255.255.0&quot;

# If enabled, this directive will configure
# all clients to redirect their default
# network gateway through the VPN, causing
# all IP traffic such as web browsing and
# and DNS lookups to go through the VPN
push &quot;redirect-gateway def1 bypass-dhcp&quot;

# Certain Windows-specific network settings
# can be pushed to clients, such as DNS
# or WINS server addresses.
# Google DNS.
;push &quot;dhcp-option DNS 8.8.8.8&quot;
;push &quot;dhcp-option DNS 8.8.4.4&quot;

# The keepalive directive causes ping-like
# messages to be sent back and forth over
# the link so that each side knows when
# the other side has gone down.
keepalive 10 120

# The maximum number of concurrently connected
# clients we want to allow.
max-clients 5

# It's a good idea to reduce the OpenVPN
# daemon's privileges after initialization.
user nobody
group nobody

# The persist options will try to avoid
# accessing certain resources on restart
# that may no longer be accessible because
# of the privilege downgrade.
persist-key
persist-tun

# Output a short status file showing
# current connections, truncated
# and rewritten every minute.
status openvpn-status.log

# Set the appropriate level of log
# file verbosity.
#
# 0 is silent, except for fatal errors
# 4 is reasonable for general usage
# 5 and 6 can help to debug connection problems
# 9 is extremely verbose
verb 3

# Notify the client that when the server restarts so it
# can automatically reconnect.
# Only usable with udp.
explicit-exit-notify 1
</code></pre>
<p><code>#</code> and <code>;</code> are comments. Read each and every line, you might want to change some stuff (like the logging), specially the first line which is your server public IP.</p>
<h4 id="enable-forwarding">Enable forwarding<a class="headerlink" href="#enable-forwarding" title="Permanent link">&para;</a></h4>
<p>Now, we need to enable <em>packet forwarding</em> (so we can access the web while connected to the VPN), which can be enabled on the interface level or globally (you can check the different options with <code>sysctl -a | grep forward</code>). I&rsquo;ll do it globally, run:</p>
<pre><code class="language-sh">sysctl net.ipv4.ip_forward=1
</code></pre>
<p>And create/edit the file <code>/etc/sysctl.d/30-ipforward.conf</code>:</p>
<pre><code>net.ipv4.ip_forward=1
</code></pre>
<p>Now we need to configure <code>ufw</code> to forward traffic through the VPN. Append the following to <code>/etc/default/ufw</code> (or edit the existing line):</p>
<pre><code>...
DEFAULT_FORWARD_POLICY=&quot;ACCEPT&quot;
...
</code></pre>
<p>And change the <code>/etc/ufw/before.rules</code>, appending the following lines after the header <strong>but before the *filter line</strong>:</p>
<pre><code>...
# NAT (Network Address Translation) table rules
*nat
:POSTROUTING ACCEPT [0:0]

# Allow traffic from clients to the interface
-A POSTROUTING -s 10.8.0.0/24 -o interface -j MASQUERADE

# do not delete the &quot;COMMIT&quot; line or the NAT table rules above will not be processed
COMMIT

# Don't delete these required lines, otherwise there will be errors
*filter
...
</code></pre>
<p>Where <code>interface</code> must be changed depending on your system (in my case it&rsquo;s <code>ens3</code>, another common one is <code>eth0</code>); I always check this by running <code>ip addr</code> which gives you a list of interfaces (the one containing your server public IP is the one you want, or whatever interface your server uses to connect to the internet):</p>
<pre><code>...
2: ens3: &lt;SOMETHING,SOMETHING&gt; bla bla
    link/ether bla:bla
    altname enp0s3
    inet my.public.ip.addr bla bla
...
</code></pre>
<p>And also make sure the <code>10.8.0.0/24</code> matches the subnet mask specified in the <code>server.conf</code> file (in this example it matches). You should check this very carefully, because I just spent a good 2 hours debugging why my configuration wasn&rsquo;t working, and this was te reason (I could connect to the VPN, but had no external connection to the web).</p>
<p>Finally, allow the OpenVPN port you specified (in this example its <code>1194/udp</code>) and reload <code>ufw</code>:</p>
<pre><code class="language-sh">ufw allow 1194/udp comment &quot;OpenVPN&quot;
ufw reload
</code></pre>
<p>At this point, the server-side configuration is done and you can start and enable the service:</p>
<pre><code class="language-sh">systemctl start openvpn-server@server.service
systemctl enable openvpn-server@server.service
</code></pre>
<p>Where the <code>server</code> after <code>@</code> is the name of your configuration, <code>server.conf</code> without the <code>.conf</code> in my case.</p>
<h3 id="create-client-configurations">Create client configurations<a class="headerlink" href="#create-client-configurations" title="Permanent link">&para;</a></h3>
<p>You might notice that I didn&rsquo;t specify how to actually connect the VPN. For that we need a configuration file similar to the <code>server.conf</code> file that we created.</p>
<p>The real way of doing this would be to run similar steps as the ones with <code>easy-rsa</code> locally, send them to the server, sign them, and retrieve them. Fuck all that, we&rsquo;ll just create all configuration files on the server as I was mentioning earlier.</p>
<p>Also, the client configuration file has to match the server one (to some degree), to make this easier you can create a <code>client-common</code> file in <code>/etc/openvpn/server</code> with the following content:</p>
<pre><code>client
dev tun
remote 1.2.3.4 1194 udp # change this to match your ip and port
resolv-retry infinite
nobind
persist-key
persist-tun
remote-cert-tls server
auth SHA512
verb 3
</code></pre>
<p>Where you should make any changes necessary, depending on your configuration.</p>
<p>Now, we need a way to create and revoke new configuration files. For this I created a script, heavily based on one of the links I mentioned at the beginning. You can place these scripts anywhere you like, and you should take a look before running them because you&rsquo;ll be running them with elevated privileges (sudo).</p>
<p>In a nutshell, what it does is: generate a new client certificate keypair, update the CRL and create a new <code>.ovpn</code> configuration file that consists on the <code>client-common</code> data and all of the required certificates; or, revoke an existing client and refresh the CRL. The file is placed under <code>~/ovpn</code>.</p>
<p>Create a new file with the following content (name it whatever you like) and don&rsquo;t forget to make it executable (<code>chmod +x vpn_script</code>):</p>
<pre><code class="language-sh">#!/bin/sh
# Client ovpn configuration creation and revoking.
MODE=$1
if [ ! &quot;$MODE&quot; = &quot;new&quot; -a ! &quot;$MODE&quot; = &quot;rev&quot; ]; then
    echo &quot;$1 is not a valid mode, using default 'new'&quot;
    MODE=new
fi

CLIENT=${2:-guest}
if [ -z $2 ];then
    echo &quot;there was no client name passed as second argument, using 'guest' as default&quot;
fi

# Expiration config.
EASYRSA_CERT_EXPIRE=3650
EASYRSA_CRL_DAYS=3650

# Current PWD.
CPWD=$PWD
cd /etc/easy-rsa/

if [ &quot;$MODE&quot; = &quot;rev&quot; ]; then
    easyrsa --batch revoke $CLIENT

    echo &quot;$CLIENT revoked.&quot;
elif [ &quot;$MODE&quot; = &quot;new&quot; ]; then
    easyrsa build-client-full $CLIENT nopass

    # This is what actually generates the config file.
    {
    cat /etc/openvpn/server/client-common
    echo &quot;&lt;ca&gt;&quot;
    cat /etc/easy-rsa/pki/ca.crt
    echo &quot;&lt;/ca&gt;&quot;
    echo &quot;&lt;cert&gt;&quot;
    sed -ne '/BEGIN CERTIFICATE/,$ p' /etc/easy-rsa/pki/issued/$CLIENT.crt
    echo &quot;&lt;/cert&gt;&quot;
    echo &quot;&lt;key&gt;&quot;
    cat /etc/easy-rsa/pki/private/$CLIENT.key
    echo &quot;&lt;/key&gt;&quot;
    echo &quot;&lt;tls-crypt&gt;&quot;
    sed -ne '/BEGIN OpenVPN Static key/,$ p' /etc/openvpn/server/ta.key
    echo &quot;&lt;/tls-crypt&gt;&quot;
    } &gt; &quot;$(eval echo ~${SUDO_USER:-$USER}/ovpn/$CLIENT.ovpn)&quot;

    eval echo &quot;~${SUDO_USER:-$USER}/ovpn/$CLIENT.ovpn file generated.&quot;
fi

# Finish up, re-generates the crl
easyrsa gen-crl
chown nobody:nobody pki/crl.pem
chmod o+r pki/crl.pem
cd $CPWD
</code></pre>
<p>And the way to use is to run <code>bash vpn_script &lt;mode&gt; &lt;client_name&gt;</code> where <code>mode</code> is <code>new</code> or <code>rev</code> (revoke) as sudo (when revoking, it doesn&rsquo;t actually delete the <code>.ovpn</code> file in <code>~/ovpn</code>). Again, this is a little script that I put together, so you should check it out, it may need tweaks (specially depending on your directory structure for <code>easy-rsa</code>).</p>
<p>Now, just get the <code>.ovpn</code> file generated, import it to OpenVPN in your client of preference and you should have a working VPN service.</p>

  <div class="page-nav">
    <span class="next">
      <a href="https://blog.luevano.xyz/a/volviendo_a_usar_la_pagina.html" alt="Next">
        <i class="fas fa-arrow-left" alt="Arrow left"></i>
        <span>Next</span>
      </a>
    </span>

    <span class="index">
      <a href="https://blog.luevano.xyz" alt="Index">
        <i class="fas fa-home" alt="Home"></i>
        <span>Index</span>
      </a>
    </span>

    <span class="previous">
      <a href="https://blog.luevano.xyz/a/hoy_toco_desarrollo_personaje.html" alt="Previous">
        <i class="fas fa-arrow-right" alt="Arrow right"></i>
        <span>Previous</span>
      </a>
    </span>
</div>


  <hr>
  <div class="article-info">
    <p>By David Luévano</p>
    <p>Created: Sun, Aug 01, 2021 @ 09:27 UTC</p>
      <p>Modified: Tue, Jun 13, 2023 @ 09:50 UTC</p>
    <div class="article-tags">
  <p>Tags:
<a href="https://blog.luevano.xyz/tag/@code.html">code</a>, <a href="https://blog.luevano.xyz/tag/@english.html">english</a>, <a href="https://blog.luevano.xyz/tag/@server.html">server</a>, <a href="https://blog.luevano.xyz/tag/@tools.html">tools</a>, <a href="https://blog.luevano.xyz/tag/@tutorial.html">tutorial</a>  </p>
</div>

  </div>
    </main>

    <footer>
<span>
  <i class="fas fa-address-card" alt="Contact"></i>
  <a href="https://blog.luevano.xyz/contact.html">Contact</a>
</span>

<span>
  <i class="fas fa-donate" alt="Donate"></i>
  <a href="https://blog.luevano.xyz/donate.html">Donate</a>
</span>

<span>
  <i class="fas fa-rss" alt="RSS"></i>
  <a target="_blank" href="https://blog.luevano.xyz/rss.xml">RSS</a>
</span>

<br>
<span class="created-with">
  <i class="fas fa-hammer" alt="Hammer"></i>
  Created with <a href="https://github.com/luevano/pyssg">pyssg</a>
</span>

<br>
<span class="copyright">
  Copyright <i class="far fa-copyright" alt="Copyright"></i> 2023 David Luévano Alvarado
</span>

    </footer>
  </body>
</html>