Merge branch 'v2' into develop
7
.gitignore
vendored
@ -1,4 +1,3 @@
|
||||
html/pages/blog
|
||||
public/**/*.html
|
||||
hot-reload
|
||||
renderer
|
||||
# hugo
|
||||
public/
|
||||
.hugo_build.lock
|
||||
|
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
[submodule "themes/holy"]
|
||||
path = themes/holy
|
||||
url = git@github.com:ekzyis/holy.git
|
21
LICENSE
@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 ekzyis
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
33
Makefile
@ -1,33 +0,0 @@
|
||||
.PHONY: build render all dev
|
||||
|
||||
MARKDOWN=$(wildcard blog/*.md)
|
||||
TARGETS= \
|
||||
$(patsubst blog/%,public/blog/%,$(wildcard blog/*.html)) \
|
||||
$(patsubst blog/%,public/blog/%,$(MARKDOWN:.md=.html)) \
|
||||
$(patsubst html/pages/%,public/%,$(wildcard html/pages/*.html))
|
||||
|
||||
all: build render
|
||||
|
||||
build: renderer
|
||||
|
||||
dev:
|
||||
bash sync-dev.sh
|
||||
|
||||
render: $(TARGETS)
|
||||
|
||||
renderer: *.go
|
||||
go build -o renderer .
|
||||
|
||||
public/blog/index.html: blog/index.html $(MARKDOWN) renderer
|
||||
mkdir -p public/blog html/pages/blog
|
||||
./renderer -src $< > html/pages/$<
|
||||
./renderer -src html/pages/$< > $@
|
||||
|
||||
public/blog/%.html: blog/%.md renderer
|
||||
mkdir -p public/blog html/pages
|
||||
./renderer -src $< > html/pages/$(<:.md=.html)
|
||||
./renderer -src html/pages/$(<:.md=.html) > $@
|
||||
|
||||
public/%.html: html/pages/%.html renderer
|
||||
mkdir -p public
|
||||
./renderer -src $< > $@
|
39
WEB.md
@ -1,39 +0,0 @@
|
||||
# ekzyis.com
|
||||
|
||||
My personal website including blog.
|
||||
|
||||
## Development
|
||||
|
||||
This site consists of only static HTML, CSS, JS in public/.
|
||||
|
||||
The files are built (or "rendered") with the golang [`text/template`](https://pkg.go.dev/text/template) standard package. It doesn't use [`html/template`](https://pkg.go.dev/html/template) since I had problems including HTML like a common header, navigation menu, footer for a reusable layout. But this shouldn't be a problem since there is no user-generated content (yet?).
|
||||
|
||||
To build the files, a [Makefile](./Makefile) is used.
|
||||
|
||||
Run `make build` to create the `renderer` binary.
|
||||
|
||||
Run `make render` to render all files in public/.
|
||||
|
||||
Deployment is done by rendering all files in production mode and then copying them where a webserver like `nginx` can serve them.
|
||||
|
||||
I use [`deploy.sh`](./deploy.sh) for this.
|
||||
|
||||
## How to create new blog post
|
||||
|
||||
1. Create new Markdown file in blog/
|
||||
2. It needs to have this header:
|
||||
|
||||
```
|
||||
Title: title
|
||||
Date: date
|
||||
ReadingTime: time
|
||||
Sats: 0
|
||||
Comments: comments
|
||||
|
||||
---
|
||||
```
|
||||
|
||||
3. Update `ComputeTitle` in `html.go` (TODO: make this no longer required)
|
||||
4. Run `make render`.
|
||||
|
||||
Done!
|
5
archetypes/default.md
Normal file
@ -0,0 +1,5 @@
|
||||
+++
|
||||
title = '{{ replace .File.ContentBaseName "-" " " | title }}'
|
||||
date = {{ .Date }}
|
||||
draft = true
|
||||
+++
|
BIN
assets/psychedelic-digital-sky.jpg
Normal file
After Width: | Height: | Size: 6.1 MiB |
@ -1,969 +0,0 @@
|
||||
Title: Demystifying WireGuard and iptables
|
||||
Date: 2023-08-09
|
||||
ReadingTime: 15 minutes
|
||||
Sats: 11623
|
||||
Comments: https://stacker.news/items/221471
|
||||
|
||||
---
|
||||
|
||||
<details open>
|
||||
<summary><b><a name="index">index</a></b></summary>
|
||||
<div class="flex flex-column">
|
||||
<a href="#introduction">0. introduction</a>
|
||||
<a href="#iptables-primer">1. iptables primer</a>
|
||||
<a href="#wireguard">2. wireguard</a>
|
||||
<a class="subh" href="#wireguard-installation"><i>2.1 installation</i></a>
|
||||
<a class="subh" href="#wireguard-configuration"><i>2.1 configuration</i></a>
|
||||
<a class="subh" href="#wireguard-interface-control"><i>2.2 interface control</i></a>
|
||||
<a href="#firewall">3. firewall configuration</a>
|
||||
<a class="subh" href="#firewall-initial-configuration"><i>3.1 initial</i></a>
|
||||
<a class="subh" href="#firewall-local-output-configuration"><i>3.1 local output</i></a>
|
||||
<a class="subh" href="#firewall-remote-input-configuration"><i>3.2 remote input</i></a>
|
||||
<a class="subh" href="#firewall-remote-output-configuration"><i>3.3 remote output</i></a>
|
||||
<a class="subh" href="#firewall-local-input-configuration"><i>3.4 local input</i></a>
|
||||
<a class="subh" href="#firewall-final-configuration"><i>3.5 final</i></a>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
<a class="header" name="introduction">
|
||||
# introduction
|
||||
</a>
|
||||
|
||||
In this blog post, I will show you how to setup [WireGuard](https://wireguard.com) and
|
||||
configure your Linux firewall with [`iptables`](https://wiki.archlinux.org/title/iptables).
|
||||
|
||||
We will establish a point-to-point connection between two machines across the internet.<br />
|
||||
One machine ("the local machine") sits behind a [NAT](https://en.wikipedia.org/wiki/Network_address_translation) router and therefore is not reachable from the public internet.
|
||||
The other machine ("the remote machine") has a static public IPv4 address and sits in a data center
|
||||
(for example, if you rent a server from cloud providers).
|
||||
This is the simplest network topology and therefore useful to get the basics down first.
|
||||
|
||||
In following blog articles, I will show you how to create more advanced network topologies
|
||||
which include VPN gateways and port forwarding [[0]](#ft-0).
|
||||
VPN gateways connect multiple devices together and port forwarding is usually used to
|
||||
expose internal services to the internet.
|
||||
|
||||
It is important to me that you get a good understanding of `iptables`
|
||||
and how to use it from this post since I think there is a lack of good guides about it.
|
||||
I believe it is way more helpful to explain fundamentals well compared to just handing out instructions to follow.
|
||||
With a good understanding, you will be able to help yourself a lot better in case you run into problems.
|
||||
|
||||
---
|
||||
|
||||
<a class="header" name="iptables-primer">
|
||||
# iptables primer
|
||||
</a>
|
||||
|
||||
`iptables` is a command line utility for configuring Linux kernel firewalls.
|
||||
|
||||
It acts upon _tables_ which consist of _chains_ which in turn consist of _rules_ and a _policy_.
|
||||
The rules have criteria for packets and _targets_ like _ACCEPT_ or _DROP_.
|
||||
These targets are basically actions which are executed if a packet matches.
|
||||
For example, a rule could check if a packet comes from a specific IP address range and use the ACCEPT
|
||||
target to accept it and let it pass through the firewall. Chain policies are used as fallback targets in case no
|
||||
rule matches.
|
||||
|
||||
Each table has a specific purpose.
|
||||
For example, the default table (which is used if you don't specify a table in a command) is the _filter_
|
||||
table which contains an _INPUT_, _FORWARD_ and _OUTPUT_ chain and is used to accept or drop IP packets.
|
||||
This is the only table we will need in this post. There is also the _nat_ table
|
||||
(which we will use for port forwarding in a future blog post) and [three others](https://wiki.archlinux.org/title/iptables#Tables), but they are very specialized and thus not needed in the vast majority of use cases.
|
||||
|
||||
During the lifetime of a packet inside the Linux firewall,
|
||||
the chains of tables are consulted to decide the fate of a packet.
|
||||
The chains traverse through their rules in order until they find a matching rule whose target terminates the chain
|
||||
[[1]](#ft-1). If no matching rule was found, then the chain's policy target is used.
|
||||
|
||||
The order of the chains is defined in this (simplified) flow chart [[2]](#ft-2):
|
||||
|
||||
<img class="flex m-auto" src="/blog/img/iptables_flowchart.png" alt="" />
|
||||
|
||||
Packets that come in on any network interface enter this flow chart at the top
|
||||
and thus go first through the PREROUTING chain of the nat table.
|
||||
However, not all packets originate from outside a network interface or reach the bottom of this flow chart.
|
||||
Packets for local processes stop at <code class="bg-transparent">[local process]</code> and packets which are generated by local
|
||||
processes enter this flow chart at <code class="bg-transparent">[local process]</code>.
|
||||
|
||||
The routing decision after the PREROUTING chain involves deciding if the final destination of the packet is the
|
||||
local machine (in which case the packet traverses to the INPUT chain at the left)
|
||||
or another machine in case we act as a router (in which case the packet traverses to the FORWARD chain at the right).<br />
|
||||
The routing decision after <code class="bg-transparent">[local process]</code> involves assigning a source address, which outgoing
|
||||
interface to use and other necessary information.
|
||||
Since the packet may have changed inside the nat table which could affect routing,
|
||||
there is a final routing decision just before the POSTROUTING chain to consider these changes.
|
||||
|
||||
With `iptables -S` (short for `iptables --list-rules`), we can lookup the current firewall configuration for a table.
|
||||
You can specify a table like this: `iptables -t nat -S`.
|
||||
For example, the filter table of a host inside a home network could look like this
|
||||
_(as mentioned, if you don't specify a table, the filter table is used)_:
|
||||
|
||||
```
|
||||
$ iptables -S
|
||||
-P INPUT DROP
|
||||
-P FORWARD DROP
|
||||
-P OUTPUT DROP
|
||||
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
|
||||
-A INPUT -j LOG --log-prefix "[INPUT:DROP] " --log-level 6
|
||||
-A OUTPUT -p udp -m udp --sport 67:68 --dport 67:68 -m comment --comment DHCP -j ACCEPT
|
||||
-A OUTPUT -p udp -m udp --dport 53 -m comment --comment DNS -j ACCEPT
|
||||
-A OUTPUT -p udp -m udp --sport 123 --dport 123 -m comment --comment NTP -j ACCEPT
|
||||
-A OUTPUT -p tcp -m tcp --dport 80 -m comment --comment HTTP -j ACCEPT
|
||||
-A OUTPUT -p tcp -m tcp --dport 443 -m comment --comment HTTPS -j ACCEPT
|
||||
-A OUTPUT -p tcp -m tcp --dport 22 -m comment --comment SSH -j ACCEPT
|
||||
-A OUTPUT -j LOG --log-prefix "[OUTPUT:DROP] " --log-level 6
|
||||
```
|
||||
|
||||
The first three statements show that the chain policy for the INPUT, FORWARD and OUTPUT chains is to drop packets.
|
||||
This means that any packet not matched by any (terminating) rule will be dropped.
|
||||
|
||||
|
||||
The OUTPUT chain contains rules to allow outgoing packets for various common application layer protocols
|
||||
like DHCP, DNS, NTP, HTTP, HTTPS and SSH.
|
||||
The INPUT chain contains a rule which only allows packets for _related_ or _established_ connections.
|
||||
This makes the firewall _stateful_.
|
||||
|
||||
|
||||
Additionally, packets are logged before they are dropped by the chain policies. This uses the _LOG_ target
|
||||
which is a non-terminating target. Logging packets is very useful for debugging
|
||||
or can be used before applying new firewall rules to make sure you don't lock yourself out accidentally.
|
||||
|
||||
|
||||
A packet is considered to be part of an established connection if it is part of a response to outgoing or incoming
|
||||
packets. For example, if you're browsing a website, the incoming packets carrying the requested web page
|
||||
content would be considered as part of an established connection. This makes the firewall stateful since it needs
|
||||
to keep an internal state of outgoing and incoming packets.
|
||||
|
||||
|
||||
A connection is considered related when it is related to another established connection.
|
||||
A good example of related connections are data connections in FTP since FTP creates new connections for data
|
||||
transfers instead of reusing the current established connection to the server. Kernel modules like
|
||||
[this](https://github.com/torvalds/linux/blob/master/net/netfilter/nf_conntrack_ftp.c)
|
||||
implement connection tracking for individual protocols such that related connections can be found by the firewall.
|
||||
|
||||
|
||||
The syntax in the output of `iptables -S` is the same syntax you would use to configure the firewall.<br />
|
||||
This means that we can set the INPUT chain policy to ACCEPT with
|
||||
|
||||
```
|
||||
$ iptables -P INPUT ACCEPT
|
||||
```
|
||||
|
||||
whereas with
|
||||
|
||||
```
|
||||
$ iptables -A INPUT -p tcp --dport 3000 -j ACCEPT
|
||||
```
|
||||
|
||||
we could append a rule to the INPUT chain to open port 3000 for TCP packets.
|
||||
|
||||
|
||||
Other useful commands are
|
||||
|
||||
```
|
||||
$ iptables -D <chain> <rulenum>
|
||||
```
|
||||
|
||||
to delete rules or
|
||||
|
||||
```
|
||||
$ iptables -R <chain> <rulenum> <rule-specification>
|
||||
```
|
||||
|
||||
to replace rules with a rule number and a rule specification like
|
||||
|
||||
`-p <proto> --dport <destination port> -j <target>`.
|
||||
|
||||
You can look up rule numbers with `--line-numbers`:
|
||||
|
||||
```
|
||||
$ iptables -S --line-numbers
|
||||
```
|
||||
|
||||
These were all commands we will use in this blog post.
|
||||
|
||||
If anything is still unclear, don't hesitate to refer to the [manual](https://man.archlinux.org/man/iptables.8.en)
|
||||
or ask a question in the [comments]({{- .Comments -}}).
|
||||
|
||||
---
|
||||
|
||||
<a class="header" name="wireguard">
|
||||
# wireguard
|
||||
</a>
|
||||
|
||||
<a class="header" name="wireguard-installation">
|
||||
## installation
|
||||
</a>
|
||||
|
||||
Follow the instructions [here](https://www.wireguard.com/install) to install WireGuard on your local and remote machine.<br />
|
||||
When you are done, you should be able to run the following command:
|
||||
|
||||
```
|
||||
$ wg --version
|
||||
wireguard-tools v1.0.20210914 - https://git.zx2c4.com/wireguard-tools/
|
||||
```
|
||||
|
||||
<a class="header" name="wireguard-configuration">
|
||||
## configuration
|
||||
</a>
|
||||
|
||||
WireGuard is a peer-to-peer (P2P) protocol like Bitcoin.
|
||||
This means that by default, the protocol does not distinguish between servers and clients.
|
||||
To create VPN gateways or any other network topology, you will have to configure your peers accordingly.
|
||||
In this blog post, we will only connect two peers together so we can keep the configuration simple.
|
||||
|
||||
### key generation
|
||||
|
||||
WireGuard uses asymmetric cryptography for its encryption.
|
||||
Therefore, you need to generate a key pair using the commands `genkey` and `pubkey`
|
||||
on your local and remote machine.
|
||||
As mentioned in [`man wg`](https://man.archlinux.org/man/wg.8#pubkey),
|
||||
you can generate a key pair with secure file permissions (handled by [`umask`](https://wiki.archlinux.org/title/umask))
|
||||
like this:
|
||||
|
||||
```
|
||||
$ umask 077
|
||||
$ wg genkey | tee /etc/wireguard/wg_private.key | wg pubkey > /etc/wireguard/wg_public.key
|
||||
```
|
||||
|
||||
This will generate a private key at _/etc/wireguard/wg\_private.key_
|
||||
and a public key at _/etc/wireguard/wg\_public.key_ which are only readable and writeable by the current user
|
||||
[[3]](#ft-3).
|
||||
|
||||
_local machine keys:_
|
||||
|
||||
```
|
||||
$ cat /etc/wireguard/wg_private.key
|
||||
l0c4l+s3cR37+RDr+dJdgX/ACeRQLANiduQRJK9O23A=
|
||||
$ cat /etc/wireguard/wg_public.key
|
||||
/wH4OzafBUJVvRGzK8itUweV/GpwoUzn7OS99lr7gHI=
|
||||
```
|
||||
|
||||
_remote machine keys:_
|
||||
|
||||
```
|
||||
$ cat /etc/wireguard/wg_private.key
|
||||
r3M073+s3cR37+fouaQZbP5QqfgwypHjKGBNmztxNEc=
|
||||
$ cat /etc/wireguard/wg_public.key
|
||||
GL33DRrI8/2yAT6+r5mTtBLd7CoErAAsio3yNqQ3K1M=
|
||||
```
|
||||
|
||||
### ip range selection
|
||||
|
||||
You need to decide which IP range you want to use for your virtual private network (VPN).
|
||||
This will be the IP range from which you will assign IP addresses to hosts inside the VPN.
|
||||
**The important part is to not pick an IP range which is already in use.**
|
||||
Fortunately, the IPv4 specification reserved following IP ranges for use in private networks [[4]](#ft-4):
|
||||
|
||||
- 10.0.0.0/8
|
||||
- 172.16.0.0/12
|
||||
- 192.168.0.0/16
|
||||
|
||||
IP addresses in these ranges are not routable in the public internet since they are ignored by all public routers.
|
||||
For example, my local area network (LAN) uses 192.168.178.0/24 [[5]](#ft-5):
|
||||
|
||||
```
|
||||
$ ip address
|
||||
... other output ...
|
||||
2: enp3s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
|
||||
link/ether 9c:6b:00:06:a7:54 brd ff:ff:ff:ff:ff:ff
|
||||
inet 192.168.178.146/24 brd 192.168.178.255 scope global dynamic noprefixroute enp3s0
|
||||
valid_lft 804365sec preferred_lft 804365sec
|
||||
inet6 fe80::6de5:ba8f:c52b:52bd/64 scope link noprefixroute
|
||||
valid_lft forever preferred_lft forever
|
||||
```
|
||||
|
||||
If you are already part of other private networks (company or university VPN for example),
|
||||
you can check the IP ranges they use by connecting and running `ip address` afterwards as above.<br />
|
||||
In this blog post, we will assume that **10.172.16.0/24** is still free to use and thus can be selected for our VPN.
|
||||
|
||||
### peer configuration
|
||||
|
||||
We can configure our peers via the file _/etc/wireguard/wg0.conf_ [[6]](#ft-6).
|
||||
As with the keys, it makes sense to run `umask 077` before creating the files.
|
||||
The files will then be created with read and write access only given to the current user.
|
||||
|
||||
|
||||
For every peer, we need to define the interface by specifying the private key and IP address.
|
||||
For the remote machine, we also need to set `ListenPort` to specify on which port the machine should
|
||||
listen for incoming WireGuard UDP packets.
|
||||
We don't set it for the local machine since we don't need a fixed port.
|
||||
We only need a fixed port if peers need to know the port in advance to initiate connections.
|
||||
However, the local machine is not reachable from the internet so it is not possible to initiate connections to it.
|
||||
Therefore, we rely on the local machine initiating connections.
|
||||
WireGuard will pick a random free port when the interface is brought up.
|
||||
|
||||
|
||||
We also need to define the peers of every peer in the configuration.
|
||||
This is done by adding a peer section which starts with <code class="bg-transparent">[Peer]</code> per peer.
|
||||
Since we only have one peer per peer, there will only be a single <code class="bg-transparent">[Peer]</code> section per configuration.<br />
|
||||
We need to set the public key of every peer such that WireGuard can use this public key to encrypt the packets.
|
||||
The peers can then decrypt the packets using their private key. We also need to set which IP addresses we want to
|
||||
route to each peer via <code class="bg-transparent">AllowedIPs</code>.
|
||||
In our case here, this will just be the IP address of each peer. When we setup a VPN gateway,
|
||||
this will be more interesting since there, we will route packets through multiple peers.
|
||||
|
||||
|
||||
For the local machine, we will also set <code class="bg-transparent">Endpoint</code> to the **public** IP address of the remote machine
|
||||
and port as used in <code class="bg-transparent">ListenPort</code>.
|
||||
This lets WireGuard know how to reach the peer to establish a VPN connection.
|
||||
|
||||
|
||||
To keep the connection alive, we will also use <code class="bg-transparent">PersistentKeepalive</code> in the local machine configuration.
|
||||
This specifies the interval in seconds in which keep-alive packets are sent.
|
||||
Without this, stateful firewalls may kill the VPN connection after some time since
|
||||
WireGuard is not a chatty protocol by itself.
|
||||
Additionally, our local machine is behind NAT which is another reason to use <code class="bg-transparent">PersistentKeepalive</code>
|
||||
to keep NAT mappings valid.
|
||||
|
||||
|
||||
_local machine configuration_:
|
||||
|
||||
```
|
||||
[Interface]
|
||||
Address = 10.172.16.2/32
|
||||
PrivateKey = l0c4l+s3cR37+RDr+dJdgX/ACeRQLANiduQRJK9O23A=
|
||||
|
||||
[Peer]
|
||||
AllowedIPs = 10.172.16.1/32
|
||||
PublicKey = GL33DRrI8/2yAT6+r5mTtBLd7CoErAAsio3yNqQ3K1M=
|
||||
Endpoint = 139.162.153.133:51913
|
||||
PersistentKeepalive = 25
|
||||
```
|
||||
|
||||
_remote machine configuration_:
|
||||
|
||||
```
|
||||
[Interface]
|
||||
Address = 10.172.16.1/32
|
||||
PrivateKey = r3M073+s3cR37+fouaQZbP5QqfgwypHjKGBNmztxNEc=
|
||||
ListenPort = 51913
|
||||
|
||||
[Peer]
|
||||
AllowedIPs = 10.172.16.2/32
|
||||
PublicKey = /wH4OzafBUJVvRGzK8itUweV/GpwoUzn7OS99lr7gHI=
|
||||
```
|
||||
|
||||
As a last step, make sure that the file permissions are correctly set and the owner of all created files is root:
|
||||
|
||||
```
|
||||
$ ls -l /etc/wireguard
|
||||
-rw------- 1 root root 1155 Aug 03 15:38 wg0.conf
|
||||
-rw------- 1 root root 45 Aug 03 15:31 wg_private.key
|
||||
-rw-r--r-- 1 root root 45 Aug 03 15:31 wg_public.key
|
||||
```
|
||||
|
||||
If the public key is readable by other users, that's fine.
|
||||
If there is something wrong with your file permissions, run these commands:
|
||||
|
||||
```
|
||||
$ chown root:root -R /etc/wireguard
|
||||
$ chmod 600 -R /etc/wireguard
|
||||
```
|
||||
|
||||
<a class="header" name="wireguard-interface-control">
|
||||
## interface control
|
||||
</a>
|
||||
|
||||
We are now done with all WireGuard configuration.
|
||||
Run this on the local and remote machine to bring the interfaces up:
|
||||
|
||||
```
|
||||
$ wg-quick up wg0
|
||||
```
|
||||
|
||||
If you use `systemd`, you can run `wg-quick up wg0` on boot using a systemd service:
|
||||
|
||||
```
|
||||
$ systemctl enable wg-quick@wg0
|
||||
```
|
||||
|
||||
To take the interface down, run this:
|
||||
|
||||
```
|
||||
$ wg-quick down wg0
|
||||
```
|
||||
|
||||
To see the configuration and peer information of WireGuard interfaces, run `wg`:
|
||||
|
||||
```
|
||||
$ wg
|
||||
interface: wg0
|
||||
public key: /wH4OzafBUJVvRGzK8itUweV/GpwoUzn7OS99lr7gHI=
|
||||
private key: (hidden)
|
||||
listening port: 60646
|
||||
|
||||
peer: GL33DRrI8/2yAT6+r5mTtBLd7CoErAAsio3yNqQ3K1M=
|
||||
endpoint: 139.162.153.133:51913
|
||||
allowed ips: 10.172.16.1/32
|
||||
transfer: 0 B received, 444 B sent
|
||||
persistent keepalive: every 25 seconds
|
||||
```
|
||||
|
||||
As you can see in the line beginning with `transfer`, we did not receive any packets yet.
|
||||
This is because we did not properly configure our firewalls yet.
|
||||
|
||||
---
|
||||
|
||||
<a class="header" name="firewall">
|
||||
# firewall configuration with iptables
|
||||
</a>
|
||||
|
||||
<a class="header" name="firewall-initial-configuration">
|
||||
## initial configuration
|
||||
</a>
|
||||
|
||||
We are starting with the following minimal set of firewall rules for the local machine:
|
||||
|
||||
```
|
||||
(local) $ iptables -S
|
||||
-P INPUT DROP
|
||||
-P FORWARD DROP
|
||||
-P OUTPUT DROP
|
||||
-A INPUT -m state --state ESTABLISHED -j ACCEPT
|
||||
-A OUTPUT -d 139.162.153.133/32 -p tcp -m tcp --dport 22 -j ACCEPT
|
||||
```
|
||||
|
||||
and remote machine:
|
||||
|
||||
```
|
||||
(remote) $ iptables -S
|
||||
-P INPUT DROP
|
||||
-P FORWARD DROP
|
||||
-P OUTPUT DROP
|
||||
-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
|
||||
-A OUTPUT -m state --state ESTABLISHED -j ACCEPT
|
||||
```
|
||||
|
||||
You should be able to see that these firewall rules only allow SSH access from
|
||||
the local machine to the remote machine with IP address 139.162.153.133.
|
||||
Since WireGuard uses UDP, it therefore makes sense that we currently don't have a VPN
|
||||
connection. We can check if we have a VPN connection with <code class="code">wg</code>
|
||||
(check for latest handshake or received bytes)
|
||||
or by trying to ping one machine from the other. Therefore, we run
|
||||
|
||||
```
|
||||
(local) $ ping 10.172.16.1
|
||||
```
|
||||
|
||||
at the local machine and
|
||||
|
||||
```
|
||||
(remote) $ ping 10.172.16.2
|
||||
```
|
||||
|
||||
at the remote machine.
|
||||
|
||||
|
||||
We need to run both commands since it is not guaranteed that the other direction also works if one machine can
|
||||
reach the other as you will later see. We will also keep these commands running until the end so we can
|
||||
immediately see if a VPN connection is up or was lost.
|
||||
|
||||
|
||||
To fully understand which rules are required and why, we will configure the firewall in four steps:
|
||||
|
||||
1. Configure local OUTPUT chain with remote INPUT chain policy set to ACCEPT
|
||||
2. Keep VPN connection up with remote INPUT chain policy switched back to DROP
|
||||
3. Configure remote OUTPUT chain with local INPUT chain policy set to ACCEPT
|
||||
4. Keep VPN connection up with local INPUT chain policy switched back to DROP
|
||||
|
||||
By first setting the INPUT chain policy to ACCEPT in the receiving machine,
|
||||
we can focus on a single machine at every step since we know
|
||||
that only the OUTPUT rules can currently be responsible for any connection failure.
|
||||
|
||||
<a class="header" name="firewall-local-output-configuration">
|
||||
## local OUTPUT chain configuration
|
||||
</a>
|
||||
|
||||
As mentioned, we will set the INPUT chain policy of the remote filter table to ACCEPT first:
|
||||
|
||||
```
|
||||
(remote) $ iptables -P INPUT ACCEPT
|
||||
```
|
||||
|
||||
```diff
|
||||
- -P INPUT DROP
|
||||
+ -P INPUT ACCEPT
|
||||
-P FORWARD DROP
|
||||
-P OUTPUT DROP
|
||||
-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
|
||||
-A OUTPUT -m state --state ESTABLISHED -j ACCEPT
|
||||
```
|
||||
|
||||
We know that `ping` uses ICMP packets so we need to allow ICMP in our local firewall:
|
||||
|
||||
```
|
||||
(local) $ iptables -A OUTPUT -p icmp -j ACCEPT
|
||||
```
|
||||
|
||||
We also know that WireGuard uses UDP. This mean we need to also allow outgoing UDP packets:
|
||||
|
||||
```
|
||||
(local) $ iptables -A OUTPUT -p udp -j ACCEPT
|
||||
```
|
||||
|
||||
We have made these changes locally now:
|
||||
|
||||
```diff
|
||||
-P INPUT DROP
|
||||
-P FORWARD DROP
|
||||
-P OUTPUT DROP
|
||||
-A INPUT -m state --state ESTABLISHED -j ACCEPT
|
||||
-A OUTPUT -d 139.162.153.133/32 -p tcp -m tcp --dport 22 -j ACCEPT
|
||||
+ -A OUTPUT -p icmp -j ACCEPT
|
||||
+ -A OUTPUT -p udp -j ACCEPT
|
||||
```
|
||||
|
||||
This is sufficient for a ping from the local to the remote machine:
|
||||
|
||||
```
|
||||
(local) $ ping 10.172.16.1
|
||||
PING 10.172.16.1 (10.172.16.1) 56(84) bytes of data.
|
||||
64 bytes from 10.172.16.1: icmp_seq=18 ttl=64 time=9.28 ms
|
||||
64 bytes from 10.172.16.1: icmp_seq=19 ttl=64 time=8.88 ms
|
||||
64 bytes from 10.172.16.1: icmp_seq=20 ttl=64 time=9.25 ms
|
||||
```
|
||||
|
||||
The current rules are very broad however. This is bad for security. We will fix this now.
|
||||
|
||||
|
||||
However, if we limit UDP packets to only the `wg0` interface, the ping stops working:
|
||||
|
||||
|
||||
```
|
||||
(local) $ iptables -R OUTPUT 3 -o wg0 -p udp -j ACCEPT
|
||||
```
|
||||
|
||||
```diff
|
||||
-P INPUT DROP
|
||||
-P FORWARD DROP
|
||||
-P OUTPUT DROP
|
||||
-A INPUT -m state --state ESTABLISHED -j ACCEPT
|
||||
-A OUTPUT -d 139.162.153.133/32 -p tcp -m tcp --dport 22 -j ACCEPT
|
||||
-A OUTPUT -p icmp -j ACCEPT
|
||||
- -A OUTPUT -p udp -j ACCEPT
|
||||
+ -A OUTPUT -o wg0 -p udp -j ACCEPT
|
||||
```
|
||||
|
||||
This is because <code class="bg-transparent">wg0</code> is the _virtual network interface_, not the actual physical network
|
||||
interface that sends the UDP packets. WireGuard works by wrapping all packets (like ICMP here) in UDP packets
|
||||
before sending them out "over the wire". The following chart should make more clear what this means:
|
||||
|
||||
![](/blog/img/wireguard_layering.png)
|
||||
|
||||
If we use the physical network interface
|
||||
(which is `enp3s0` for the local machine as can be seen in `ip address`), the ping works again:
|
||||
|
||||
```
|
||||
(local) $ iptables -R OUTPUT 3 -o enp3s0 -p udp -j ACCEPT
|
||||
```
|
||||
|
||||
```diff
|
||||
-P INPUT DROP
|
||||
-P FORWARD DROP
|
||||
-P OUTPUT DROP
|
||||
-A INPUT -m state --state ESTABLISHED -j ACCEPT
|
||||
-A OUTPUT -d 139.162.153.133/32 -p tcp -m tcp --dport 22 -j ACCEPT
|
||||
-A OUTPUT -p icmp -j ACCEPT
|
||||
- -A OUTPUT -o wg0 -p udp -j ACCEPT
|
||||
+ -A OUTPUT -o enp3s0 -p udp -j ACCEPT
|
||||
```
|
||||
|
||||
We can also limit the UDP packets to port 51913 of our remote machine:
|
||||
|
||||
```
|
||||
(local) $ iptables -R OUTPUT 3 -o enp3s0 -p udp -d 139.162.153.133 --dport 51913 -j ACCEPT
|
||||
```
|
||||
|
||||
```diff
|
||||
-P INPUT DROP
|
||||
-P FORWARD DROP
|
||||
-P OUTPUT DROP
|
||||
-A INPUT -m state --state ESTABLISHED -j ACCEPT
|
||||
-A OUTPUT -d 139.162.153.133/32 -p tcp -m tcp --dport 22 -j ACCEPT
|
||||
-A OUTPUT -p icmp -j ACCEPT
|
||||
- -A OUTPUT -o enp3s0 -p udp -j ACCEPT
|
||||
+ -A OUTPUT -d 139.162.153.133/32 -o enp3s0 -p udp -m udp --dport 51913 -j ACCEPT
|
||||
```
|
||||
|
||||
What would not work is to limit the UDP packets using internal IPs
|
||||
since the physical network interface is unaware of our VPN:
|
||||
|
||||
```
|
||||
(local) $ iptables -R OUTPUT 3 -o enp3s0 -p udp -d 10.172.16.1 -j ACCEPT
|
||||
```
|
||||
|
||||
```diff
|
||||
-P INPUT DROP
|
||||
-P FORWARD DROP
|
||||
-P OUTPUT DROP
|
||||
-A INPUT -m state --state ESTABLISHED -j ACCEPT
|
||||
-A OUTPUT -d 139.162.153.133/32 -p tcp -m tcp --dport 22 -j ACCEPT
|
||||
-A OUTPUT -p icmp -j ACCEPT
|
||||
- -A OUTPUT -o enp3s0 -p udp -j ACCEPT
|
||||
+ -A OUTPUT -d 10.172.16.1/32 -o enp3s0 -p udp -m udp --dport 51913 -j ACCEPT
|
||||
```
|
||||
|
||||
To confirm our understanding, we can limit the ICMP packets to only the `wg0` interface.
|
||||
The ping should continue to work:
|
||||
|
||||
```
|
||||
(local) $ iptables -R OUTPUT 2 -o wg0 -p icmp -j ACCEPT
|
||||
```
|
||||
|
||||
```diff
|
||||
-P INPUT DROP
|
||||
-P FORWARD DROP
|
||||
-P OUTPUT DROP
|
||||
-A INPUT -m state --state ESTABLISHED -j ACCEPT
|
||||
-A OUTPUT -d 139.162.153.133/32 -p tcp -m tcp --dport 22 -j ACCEPT
|
||||
- -A OUTPUT -p icmp -j ACCEPT
|
||||
+ -A OUTPUT -o wg0 -p icmp -j ACCEPT
|
||||
-A OUTPUT -d 139.162.153.133/32 -o enp3s0 -p udp -m udp --dport 51913 -j ACCEPT
|
||||
```
|
||||
|
||||
And it indeed does:
|
||||
|
||||
```
|
||||
64 bytes from 10.172.16.1: icmp_seq=50 ttl=64 time=9.05 ms
|
||||
64 bytes from 10.172.16.1: icmp_seq=51 ttl=64 time=8.78 ms
|
||||
64 bytes from 10.172.16.1: icmp_seq=52 ttl=64 time=9.12 ms
|
||||
```
|
||||
|
||||
Usually, all traffic is allowed inside a VPN. Therefore, this rule is commonly used:
|
||||
|
||||
```
|
||||
(local) $ iptables -R OUTPUT 2 -o wg0 -j ACCEPT
|
||||
```
|
||||
|
||||
Done. The changes we applied to the local firewall configuration are:
|
||||
|
||||
```diff
|
||||
-P INPUT DROP
|
||||
-P FORWARD DROP
|
||||
-P OUTPUT DROP
|
||||
-A INPUT -m state --state ESTABLISHED -j ACCEPT
|
||||
-A OUTPUT -d 139.162.153.133/32 -p tcp -m tcp --dport 22 -j ACCEPT
|
||||
+ -A OUTPUT -o wg0 -j ACCEPT
|
||||
+ -A OUTPUT -d 139.162.153.133/32 -o enp3s0 -p udp -m udp --dport 51913 -j ACCEPT
|
||||
```
|
||||
|
||||
<a class="header" name="firewall-remote-input-configuration">
|
||||
## switch remote INPUT chain policy back to DROP
|
||||
</a>
|
||||
|
||||
We will set the remote INPUT chain policy back to DROP now.
|
||||
|
||||
```diff
|
||||
- -P INPUT ACCEPT
|
||||
+ -P INPUT DROP
|
||||
-P FORWARD DROP
|
||||
-P OUTPUT DROP
|
||||
-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
|
||||
-A OUTPUT -m state --state ESTABLISHED -j ACCEPT
|
||||
```
|
||||
|
||||
The ping stopped working but we know that the local OUTPUT chain is properly configured.
|
||||
|
||||
After allowing inbound ICMP _and_ UDP packets, the ping from the local machine to the remote machine works again:
|
||||
|
||||
```diff
|
||||
-P INPUT DROP
|
||||
-P FORWARD DROP
|
||||
-P OUTPUT DROP
|
||||
-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
|
||||
+ -A INPUT -p icmp -j ACCEPT
|
||||
+ -A INPUT -p udp -j ACCEPT
|
||||
-A OUTPUT -m state --state ESTABLISHED -j ACCEPT
|
||||
```
|
||||
|
||||
We will limit the UDP packets to only port 51913 and the physical network interface.
|
||||
The physical network interface of the remote machine is `eth0`:
|
||||
|
||||
```
|
||||
(remote) $ iptables -R INPUT 3 -i eth0 -p udp --dport 51913 -j ACCEPT
|
||||
```
|
||||
|
||||
To actually enable all VPN traffic from the local to the remote machine,
|
||||
we also need to allow it on the remote machine:
|
||||
|
||||
```
|
||||
(remote) $ iptables -R INPUT 2 -i wg0 -j ACCEPT
|
||||
```
|
||||
|
||||
Done. We applied the following changes to the remote firewall:
|
||||
|
||||
```diff
|
||||
- -P INPUT ACCEPT
|
||||
+ -P INPUT DROP
|
||||
-P FORWARD DROP
|
||||
-P OUTPUT DROP
|
||||
-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
|
||||
+ -A INPUT -i wg0 -j ACCEPT
|
||||
+ -A INPUT -i eth0 -p udp -m udp --dport 51913 -j ACCEPT
|
||||
-A OUTPUT -m state --state ESTABLISHED -j ACCEPT
|
||||
```
|
||||
|
||||
<a class="header" name="firewall-remote-output-configuration">
|
||||
## remote OUTPUT chain configuration
|
||||
</a>
|
||||
|
||||
We will take care of pinging the local machine from the remote machine now. As you can see, having a connection
|
||||
from one direction does not mean that the other direction works, too (even though response packets arrive).
|
||||
|
||||
|
||||
To focus on the OUTPUT chain configuration, we will set the local INPUT chain policy to ACCEPT:
|
||||
|
||||
```diff
|
||||
- -P INPUT DROP
|
||||
+ -P INPUT ACCEPT
|
||||
-P FORWARD DROP
|
||||
-P OUTPUT DROP
|
||||
-A INPUT -m state --state ESTABLISHED -j ACCEPT
|
||||
-A OUTPUT -d 139.162.153.133/32 -p tcp -m tcp --dport 22 -j ACCEPT
|
||||
-A OUTPUT -o wg0 -j ACCEPT
|
||||
-A OUTPUT -d 139.162.153.133/32 -o enp3s0 -p udp -m udp --dport 51913 -j ACCEPT
|
||||
```
|
||||
|
||||
And start with allowing outgoing ICMP packets:
|
||||
|
||||
```diff
|
||||
-P INPUT DROP
|
||||
-P FORWARD DROP
|
||||
-P OUTPUT DROP
|
||||
-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
|
||||
-A INPUT -i wg0 -j ACCEPT
|
||||
-A INPUT -i eth0 -p udp -m udp --dport 51913 -j ACCEPT
|
||||
-A OUTPUT -m state --state ESTABLISHED -j ACCEPT
|
||||
+ -A OUTPUT -p icmp -j ACCEPT
|
||||
```
|
||||
|
||||
However, this time, we notice that the ping already works even without allowing UDP packets:
|
||||
|
||||
```
|
||||
(remote) $ ping 10.172.16.2
|
||||
PING 10.172.16.2 (10.172.16.2) 56(84) bytes of data.
|
||||
64 bytes from 10.172.16.2: icmp_seq=8 ttl=64 time=9.16 ms
|
||||
64 bytes from 10.172.16.2: icmp_seq=9 ttl=64 time=8.74 ms
|
||||
64 bytes from 10.172.16.2: icmp_seq=10 ttl=64 time=8.95 ms
|
||||
```
|
||||
|
||||
The explanation is that the UDP packets are able to use an established connection.
|
||||
Only allowing TCP packets kills the connection [[7]](#ft-7):
|
||||
|
||||
```diff
|
||||
-P INPUT DROP
|
||||
-P FORWARD DROP
|
||||
-P OUTPUT DROP
|
||||
-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
|
||||
-A INPUT -i wg0 -j ACCEPT
|
||||
-A INPUT -i eth0 -p udp -m udp --dport 51913 -j ACCEPT
|
||||
- -A OUTPUT -m state --state ESTABLISHED -j ACCEPT
|
||||
+ -A OUTPUT -p tcp -m state --state ESTABLISHED -j ACCEPT
|
||||
-A OUTPUT -p icmp -j ACCEPT
|
||||
```
|
||||
|
||||
We could allow UDP packets through a separate rule ... :
|
||||
|
||||
```diff
|
||||
-P INPUT DROP
|
||||
-P FORWARD DROP
|
||||
-P OUTPUT DROP
|
||||
-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
|
||||
-A INPUT -i wg0 -j ACCEPT
|
||||
-A INPUT -i eth0 -p udp -m udp --dport 51913 -j ACCEPT
|
||||
-A OUTPUT -p tcp -m state --state ESTABLISHED -j ACCEPT
|
||||
-A OUTPUT -p icmp -j ACCEPT
|
||||
+ -A OUTPUT -p udp -j ACCEPT
|
||||
```
|
||||
|
||||
... but since we don't have a specific IP address for limiting the traffic,
|
||||
we will revert back to the previous stateful rule and also allow any traffic from the virtual network interface:
|
||||
|
||||
```
|
||||
(remote) $ iptables -R OUTPUT 1 -m state --state ESTABLISHED -j ACCEPT
|
||||
(remote) $ iptables -D OUTPUT 3
|
||||
(remote) $ iptables -R OUTPUT 2 -o wg0 -j ACCEPT
|
||||
```
|
||||
|
||||
Done. We effectively only added a single rule to the remote firewall:
|
||||
|
||||
```diff
|
||||
-P INPUT DROP
|
||||
-P FORWARD DROP
|
||||
-P OUTPUT DROP
|
||||
-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
|
||||
-A INPUT -i wg0 -j ACCEPT
|
||||
-A INPUT -i eth0 -p udp -m udp --dport 51913 -j ACCEPT
|
||||
-A OUTPUT -m state --state ESTABLISHED -j ACCEPT
|
||||
+ -A OUTPUT -o wg0 -j ACCEPT
|
||||
```
|
||||
|
||||
<a class="header" name="firewall-local-input-configuration">
|
||||
## switch local INPUT policy back to DROP
|
||||
</a>
|
||||
|
||||
We have a bidirectional connection now. The only thing left to do is to revert back to
|
||||
a local INPUT chain policy of DROP and keep the connection up.
|
||||
|
||||
|
||||
When going back to DROP as the INPUT chain policy ... :
|
||||
|
||||
```diff
|
||||
- -P INPUT ACCEPT
|
||||
+ -P INPUT DROP
|
||||
-P FORWARD DROP
|
||||
-P OUTPUT DROP
|
||||
-A INPUT -m state --state ESTABLISHED -j ACCEPT
|
||||
-A OUTPUT -d 139.162.153.133/32 -p tcp -m tcp --dport 22 -j ACCEPT
|
||||
-A OUTPUT -o wg0 -j ACCEPT
|
||||
-A OUTPUT -d 139.162.153.133/32 -o enp3s0 -p udp -m udp --dport 51913 -j ACCEPT
|
||||
```
|
||||
|
||||
... we notice that the ping continues to work. This is because of the first INPUT rule:
|
||||
|
||||
```
|
||||
-A INPUT -m state --state ESTABLISHED -j ACCEPT
|
||||
```
|
||||
|
||||
If we kill the connection and then run `ping` again, it no longer works:
|
||||
|
||||
```
|
||||
64 bytes from 10.172.16.2: icmp_seq=93 ttl=64 time=9.16 ms
|
||||
64 bytes from 10.172.16.2: icmp_seq=94 ttl=64 time=9.06 ms
|
||||
64 bytes from 10.172.16.2: icmp_seq=95 ttl=64 time=8.83 ms
|
||||
64 bytes from 10.172.16.2: icmp_seq=96 ttl=64 time=9.15 ms
|
||||
^C
|
||||
--- 10.172.16.2 ping statistics ---
|
||||
96 packets transmitted, 96 received, 0% packet loss, time 95139ms
|
||||
rtt min/avg/max/mdev = 8.602/9.114/9.606/0.209 ms
|
||||
(remote) $ ping 10.172.16.2
|
||||
PING 10.172.16.2 (10.172.16.2) 56(84) bytes of data.
|
||||
|
||||
|
||||
```
|
||||
|
||||
This is expected since it only worked because of an established connection.
|
||||
|
||||
|
||||
After allowing ICMP packets, the ping also works immediately again:
|
||||
|
||||
```diff
|
||||
-P INPUT DROP
|
||||
-P FORWARD DROP
|
||||
-P OUTPUT DROP
|
||||
-A INPUT -m state --state ESTABLISHED -j ACCEPT
|
||||
+ -A INPUT -p icmp -j ACCEPT
|
||||
-A OUTPUT -d 139.162.153.133/32 -p tcp -m tcp --dport 22 -j ACCEPT
|
||||
-A OUTPUT -o wg0 -j ACCEPT
|
||||
-A OUTPUT -d 139.162.153.133/32 -o enp3s0 -p udp -m udp --dport 51913 -j ACCEPT
|
||||
```
|
||||
|
||||
This is again because the UDP packets can still use the established VPN connection.
|
||||
This is similar to what happened while configuring the remote OUTPUT chain.
|
||||
|
||||
|
||||
But again, the proper configuration would be to allow all traffic into the <code>wg0</code> interface
|
||||
but limit incoming UDP packets with a source IP address and port filter:
|
||||
|
||||
```diff
|
||||
-P INPUT DROP
|
||||
-P FORWARD DROP
|
||||
-P OUTPUT DROP
|
||||
-A INPUT -m state --state ESTABLISHED -j ACCEPT
|
||||
-A INPUT -p icmp -j ACCEPT
|
||||
- -A INPUT -p icmp -j ACCEPT
|
||||
+ -A INPUT -i wg0 -j ACCEPT
|
||||
+ -A INPUT -s 139.162.153.133/32 -i enp3s0 -p udp -m udp --sport 51913 -j ACCEPT
|
||||
-A OUTPUT -d 139.162.153.133/32 -p tcp -m tcp --dport 22 -j ACCEPT
|
||||
-A OUTPUT -o wg0 -j ACCEPT
|
||||
-A OUTPUT -d 139.162.153.133/32 -o enp3s0 -p udp -m udp --dport 51913 -j ACCEPT
|
||||
```
|
||||
|
||||
<a class="header" name="firewall-final-configuration">
|
||||
## final configuration
|
||||
</a>
|
||||
|
||||
_local firewall configuration:_
|
||||
|
||||
```diff
|
||||
-P INPUT DROP
|
||||
-P FORWARD DROP
|
||||
-P OUTPUT DROP
|
||||
-A INPUT -m state --state ESTABLISHED -j ACCEPT
|
||||
+ -A INPUT -i wg0 -j ACCEPT
|
||||
+ -A INPUT -s 139.162.153.133 -i enp3s0 -p udp -m udp --sport 51913 -j ACCEPT
|
||||
-A OUTPUT -d 139.162.153.133/32 -p tcp -m tcp --dport 22 -j ACCEPT
|
||||
+ -A OUTPUT -o wg0 -j ACCEPT
|
||||
+ -A OUTPUT -d 139.162.153.133/32 -o enp3s0 -p udp -m udp --dport 51913 -j ACCEPT
|
||||
```
|
||||
|
||||
_remote firewall configuration:_
|
||||
|
||||
```diff
|
||||
-P INPUT DROP
|
||||
-P FORWARD DROP
|
||||
-P OUTPUT DROP
|
||||
-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
|
||||
+ -A INPUT -i wg0 -j ACCEPT
|
||||
+ -A INPUT -i eth0 -p udp -m udp --dport 51913 -j ACCEPT
|
||||
-A OUTPUT -m state --state ESTABLISHED -j ACCEPT
|
||||
+ -A OUTPUT -o wg0 -j ACCEPT
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Thanks for reading my first blog post!
|
||||
If you want to read more content like this, please consider subscribing via [RSS](/blog/rss.xml).
|
||||
|
||||
|
||||
Also, I would highly appreciate any feedback in the [comments]({{- .Comments -}}).
|
||||
You can tell me if it was too long, too boring, too complicated or anything else, that's no problem!
|
||||
I am very new to this whole blogging thing and thus could really _really_ need **any** kind of feedback.
|
||||
I'll even pay you 100 sats!
|
||||
|
||||
---
|
||||
|
||||
<small>
|
||||
<span id="ft-0">[0]
|
||||
Originally, I wanted to make a blog post how to use WireGuard and port forwarding to expose your bitcoin
|
||||
node at home to the internet with a static public IPv4 address. This avoids that inbound connections drop when
|
||||
your ISP changes your public IPv4 address. However, I realized that I want to be thorough with explaining the
|
||||
basics first and not skip anything just to get to the port forwarding part faster.
|
||||
</span><br />
|
||||
<span id="ft-1">[1]
|
||||
Some targets don't terminate the chain. For example, targets can redirect to another user-defined chain and
|
||||
then
|
||||
return or just log a packet.
|
||||
</span><br />
|
||||
<span id="ft-2">[2]
|
||||
This chart only contains the filter and nat table and was taken from
|
||||
[here](https://wiki.archlinux.org/title/iptables#Basic_concepts).
|
||||
</span><br />
|
||||
<span id="ft-3">[3]
|
||||
If you are confused by the mask 077 like me since it looks like it gives everyone full access except to
|
||||
yourself:
|
||||
as mentioned [here](https://wiki.archlinux.org/title/umask).
|
||||
<code class="code">umask</code> uses
|
||||
the logical complement of the permission bits. This means that any bit set via umask will
|
||||
<strong>not</strong>
|
||||
be set in the file permissions.
|
||||
</span><br />
|
||||
<span id="ft-4">[4] See the
|
||||
[wikipedia article](https://en.wikipedia.org/wiki/Private_network#Private_IPv4_addresses)
|
||||
about private networks.
|
||||
</span><br />
|
||||
<span id="ft-5">[5]
|
||||
If you are confused by the /24 notation, you can read about CIDR
|
||||
[here](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing).
|
||||
</span><br />
|
||||
<span id="ft-6">[6] You could use any other path, but _/etc/wireguard/_ is searched automatically by
|
||||
`wg-quick` so our commands can be kept short.
|
||||
</span><br />
|
||||
<span id="ft-7">[7]
|
||||
I am not entirely sure which established connection the UDP packets use but I think it's the VPN connection
|
||||
since it always uses the same network sockets as far as I know. If you know more about this, please let me
|
||||
know!
|
||||
</span><br />
|
||||
</small>
|
@ -1,386 +0,0 @@
|
||||
Title: WireGuard Packet Forwarding
|
||||
Date: 2023-08-21
|
||||
ReadingTime: 10 minutes
|
||||
Sats: 2593
|
||||
Comments: https://stacker.news/items/230179
|
||||
|
||||
---
|
||||
|
||||
<details open>
|
||||
<summary><b><a name="index">index</a></b></summary>
|
||||
<div class="flex flex-column">
|
||||
<a href="#introduction">0. introduction</a>
|
||||
<a href="#initial-configuration">1. initial configuration</a>
|
||||
<a href="#end-device-configuration">2. end device configuration</a>
|
||||
<a href="#router-configuration">3. router configuration</a>
|
||||
<a class="subh" href="#router-firewall-configuration"><i>3.1 firewall</i></a>
|
||||
<a class="subh" href="#router-kernel-params"><i>3.1 kernel parameters</i></a>
|
||||
<a href="#tcpdump">4. inspecting network traffic with tcpdump</a>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
<a class="header" name="introduction">
|
||||
# introduction
|
||||
</a>
|
||||
|
||||
In my [previous blog post](/blog/20230809-demystifying-wireguard-and-iptables),
|
||||
I have shown you how to setup your own VPN with [WireGuard](https://wireguard.com/) and [`iptables`](https://wiki.archlinux.org/title/iptables).
|
||||
We have established a point-to-point connection between two peers where one peer (10.172.16.1) was reachable from the internet:
|
||||
|
||||
![](/blog/img/point-to-point.png)
|
||||
|
||||
Today, I will explain how more peers can be added to our VPN.
|
||||
One peer ("the router") will be configured to forward packets between all other peers ("the end devices").
|
||||
Therefore, our VPN will become a star network:
|
||||
|
||||
![](/blog/img/star_network.png)
|
||||
|
||||
You could then [install](https://www.wireguard.com/install/#android-play-store-direct-apk-file) WireGuard on your mobile device
|
||||
and reach all other machines in your VPN from anywhere with internet connection.
|
||||
|
||||
To get a deeper understanding how this forwarding works, we will take a brief look at the network traffic with `tcpdump` after we configured our network.
|
||||
|
||||
---
|
||||
|
||||
<a class="header" name="initial-configuration">
|
||||
# initial configuration
|
||||
</a>
|
||||
|
||||
The end devices will start with a point-to-point connection to the router with 10.172.16.1 as its internal IP address.
|
||||
|
||||
The WireGuard and firewall configuration for these could therefore look like this:
|
||||
|
||||
**_WireGuard configuration for end device:_**
|
||||
|
||||
```
|
||||
[Interface]
|
||||
Address = 10.172.16.x/32
|
||||
PrivateKey = <PRIVATE KEY OF PEER>
|
||||
|
||||
[Peer]
|
||||
AllowedIPs = 10.172.16.1/32
|
||||
PublicKey = <PUBLIC KEY OF ROUTER>
|
||||
Endpoint = 139.162.153.133:51913
|
||||
PersistentKeepalive = 25
|
||||
```
|
||||
|
||||
**_firewall configuration for end device:_**
|
||||
|
||||
```
|
||||
-P INPUT DROP
|
||||
-P FORWARD DROP
|
||||
-P OUTPUT DROP
|
||||
-A INPUT -m state --state ESTABLISHED -j ACCEPT
|
||||
-A INPUT -i wg0 -j ACCEPT
|
||||
-A INPUT -s 139.162.153.133 -i enp3s0 -p udp -m udp --sport 51913 -j ACCEPT
|
||||
-A OUTPUT -d 139.162.153.133/32 -p tcp -m tcp --dport 22 -j ACCEPT
|
||||
-A OUTPUT -o wg0 -j ACCEPT
|
||||
-A OUTPUT -d 139.162.153.133/32 -o enp3s0 -p udp -m udp --dport 51913 -j ACCEPT
|
||||
```
|
||||
|
||||
Since the router is connected to multiple peers, it will have multiple <code class="bg-transparent">[Peer]</code> sections in its configuration:
|
||||
|
||||
**_WireGuard configuration for router:_**
|
||||
|
||||
```
|
||||
[Interface]
|
||||
Address = 10.172.16.1/32
|
||||
PrivateKey = <PRIVATE KEY OF ROUTER>
|
||||
ListenPort = 51913
|
||||
|
||||
[Peer]
|
||||
AllowedIPs = 10.172.16.2/32
|
||||
PublicKey = <PUBLIC KEY OF PEER>
|
||||
|
||||
[Peer]
|
||||
AllowedIPs = 10.172.16.4/32
|
||||
PublicKey = <PUBLIC KEY OF PEER>
|
||||
|
||||
...
|
||||
|
||||
[Peer]
|
||||
AllowedIPs = 10.172.16.25/32
|
||||
PublicKey = <PUBLIC KEY OF PEER>
|
||||
```
|
||||
|
||||
**_firewall configuration for router:_**
|
||||
|
||||
|
||||
```
|
||||
-P INPUT DROP
|
||||
-P FORWARD DROP
|
||||
-P OUTPUT DROP
|
||||
-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
|
||||
-A INPUT -i wg0 -j ACCEPT
|
||||
-A INPUT -i eth0 -p udp -m udp --dport 51913 -j ACCEPT
|
||||
-A OUTPUT -m state --state ESTABLISHED -j ACCEPT
|
||||
-A OUTPUT -o wg0 -j ACCEPT
|
||||
```
|
||||
|
||||
<small>_If these configurations are confusing to you, read my [previous blog post](/blog/20230809-demystifying-wireguard-and-iptables)._</small>
|
||||
|
||||
---
|
||||
|
||||
<a class="header" name="end-device-configuration">
|
||||
# end device configuration
|
||||
</a>
|
||||
|
||||
The only change we have to do on the end devices is to route all IP addresses within the VPN to the router peer.
|
||||
We configure this in the WireGuard configuration file at _/etc/wireguard/wg0.conf_:
|
||||
|
||||
```diff
|
||||
[Peer]
|
||||
- AllowedIPs = 10.172.16.1/32
|
||||
+ AllowedIPs = 10.172.16.0/24
|
||||
PublicKey = <PUBLIC KEY OF ROUTER>
|
||||
Endpoint = 139.162.153.133:51913
|
||||
PersistentKeepalive = 25
|
||||
```
|
||||
|
||||
To apply these changes, we run this command <span id="ft-0b">[[0]](#ft-0)</span>:
|
||||
|
||||
```
|
||||
$ wg syncconf wg0 <(wg-quick strip wg0)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
<a class="header" name="router-configuration">
|
||||
# router configuration
|
||||
</a>
|
||||
|
||||
<a class="header" name="router-firewall-configuration">
|
||||
## firewall
|
||||
</a>
|
||||
|
||||
The router requires no additional WireGuard configuration.
|
||||
|
||||
However, the router firewall needs to allow forwarding packets inside the VPN:
|
||||
|
||||
```diff
|
||||
-P INPUT DROP
|
||||
-P FORWARD DROP
|
||||
-P OUTPUT DROP
|
||||
-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
|
||||
-A INPUT -i wg0 -j ACCEPT
|
||||
-A INPUT -i eth0 -p udp -m udp --dport 51913 -j ACCEPT
|
||||
-A OUTPUT -m state --state ESTABLISHED -j ACCEPT
|
||||
-A OUTPUT -o wg0 -j ACCEPT
|
||||
+ -A FORWARD -i wg0 -o wg0 -j ACCEPT
|
||||
```
|
||||
|
||||
<a class="header" name="router-kernel-params">
|
||||
## kernel parameters
|
||||
</a>
|
||||
|
||||
We also need to allow IP forwarding in the kernel. We can configure kernel parameters with `sysctl`.
|
||||
|
||||
To see the current value for IP forwarding:
|
||||
|
||||
```
|
||||
$ sysctl net.ipv4.ip_forward
|
||||
net.ipv4.ip_forward = 0
|
||||
```
|
||||
|
||||
This basically does the same as checking the content of the file _/proc/sys/net/ipv4/ip\_forward_:
|
||||
|
||||
```
|
||||
$ cat /proc/sys/net/ipv4/ip_forward
|
||||
0
|
||||
```
|
||||
|
||||
To change the setting, we can use `sysctl -w`:
|
||||
|
||||
```
|
||||
$ sysctl -w net.ipv4.ip_forward=1
|
||||
```
|
||||
|
||||
However, this change is not persistent. To make sure the new setting is kept after reboot, we need to modify _/etc/sysctl.conf_.
|
||||
We can do this by appending <code class="bg-transparent">net.ipv4.ip_forward = 1</code> to the file:
|
||||
|
||||
```
|
||||
$ echo 'net.ipv4.ip_forward = 1' >> /etc/sysctl.conf
|
||||
```
|
||||
|
||||
To reload settings:
|
||||
|
||||
```
|
||||
$ sysctl -p
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
<a class="header" name="tcpdump">
|
||||
# inspecting the network traffic with tcpdump
|
||||
</a>
|
||||
|
||||
We now should have a connection between every peer via the router.
|
||||
|
||||
To confirm this, we ping one machine (10.172.16.25) from another machine (10.172.16.6):
|
||||
|
||||
```
|
||||
$ ping 10.172.16.25
|
||||
PING 10.172.16.25 (10.172.16.25) 56(84) bytes of data.
|
||||
64 bytes from 10.172.16.25: icmp_seq=1 ttl=63 time=138 ms
|
||||
64 bytes from 10.172.16.25: icmp_seq=2 ttl=63 time=165 ms
|
||||
64 bytes from 10.172.16.25: icmp_seq=3 ttl=63 time=182 ms
|
||||
64 bytes from 10.172.16.25: icmp_seq=4 ttl=63 time=206 ms
|
||||
64 bytes from 10.172.16.25: icmp_seq=5 ttl=63 time=229 ms
|
||||
```
|
||||
|
||||
We will inspect this traffic on 10.172.16.1 (the router) and 10.172.16.6 (the pinging machine) with `tcpdump` now <span id="ft-1b">[[1]](#ft-1)</span>.
|
||||
|
||||
On the pinging machine, we will get the following output for the virtual network interface <span id="ft-2b">[[2]](#ft-2)</span>:
|
||||
|
||||
```
|
||||
$ tcpdump -tni wg0
|
||||
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
|
||||
listening on wg0, link-type RAW (Raw IP), snapshot length 262144 bytes
|
||||
IP 10.172.16.6 > 10.172.16.25: ICMP echo request, id 19, seq 1, length 64
|
||||
IP 10.172.16.25 > 10.172.16.6: ICMP echo reply, id 19, seq 1, length 64
|
||||
IP 10.172.16.6 > 10.172.16.25: ICMP echo request, id 19, seq 2, length 64
|
||||
IP 10.172.16.25 > 10.172.16.6: ICMP echo reply, id 19, seq 2, length 64
|
||||
IP 10.172.16.6 > 10.172.16.25: ICMP echo request, id 19, seq 3, length 64
|
||||
IP 10.172.16.25 > 10.172.16.6: ICMP echo reply, id 19, seq 3, length 64
|
||||
IP 10.172.16.6 > 10.172.16.25: ICMP echo request, id 19, seq 4, length 64
|
||||
IP 10.172.16.25 > 10.172.16.6: ICMP echo reply, id 19, seq 4, length 64
|
||||
IP 10.172.16.6 > 10.172.16.25: ICMP echo request, id 19, seq 5, length 64
|
||||
IP 10.172.16.25 > 10.172.16.6: ICMP echo reply, id 19, seq 5, length 64
|
||||
```
|
||||
|
||||
and this for the physical network interface (filtered by UDP packets from/to port 51913):
|
||||
|
||||
```
|
||||
$ tcpdump -tni enp3s0 'udp and port 51913'
|
||||
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
|
||||
listening on enp3s0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
|
||||
IP 192.168.178.146.51941 > 139.144.78.247.51913: UDP, length 128
|
||||
IP 139.144.78.247.51913 > 192.168.178.146.51941: UDP, length 128
|
||||
IP 192.168.178.146.51941 > 139.144.78.247.51913: UDP, length 128
|
||||
IP 139.144.78.247.51913 > 192.168.178.146.51941: UDP, length 128
|
||||
IP 192.168.178.146.51941 > 139.144.78.247.51913: UDP, length 128
|
||||
IP 139.144.78.247.51913 > 192.168.178.146.51941: UDP, length 128
|
||||
IP 192.168.178.146.51941 > 139.144.78.247.51913: UDP, length 128
|
||||
IP 139.144.78.247.51913 > 192.168.178.146.51941: UDP, length 128
|
||||
IP 192.168.178.146.51941 > 139.144.78.247.51913: UDP, length 128
|
||||
IP 139.144.78.247.51913 > 192.168.178.146.51941: UDP, length 128
|
||||
```
|
||||
|
||||
On the router, we get this output for the virtual network interface:
|
||||
|
||||
```
|
||||
$ tcpdump -tni wg0
|
||||
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
|
||||
listening on wg0, link-type RAW (Raw IP), snapshot length 262144 bytes
|
||||
IP 10.172.16.6 > 10.172.16.25: ICMP echo request, id 21, seq 1, length 64
|
||||
IP 10.172.16.6 > 10.172.16.25: ICMP echo request, id 21, seq 1, length 64
|
||||
IP 10.172.16.25 > 10.172.16.6: ICMP echo reply, id 21, seq 1, length 64
|
||||
IP 10.172.16.25 > 10.172.16.6: ICMP echo reply, id 21, seq 1, length 64
|
||||
IP 10.172.16.6 > 10.172.16.25: ICMP echo request, id 21, seq 2, length 64
|
||||
IP 10.172.16.6 > 10.172.16.25: ICMP echo request, id 21, seq 2, length 64
|
||||
IP 10.172.16.25 > 10.172.16.6: ICMP echo reply, id 21, seq 2, length 64
|
||||
IP 10.172.16.25 > 10.172.16.6: ICMP echo reply, id 21, seq 2, length 64
|
||||
IP 10.172.16.6 > 10.172.16.25: ICMP echo request, id 21, seq 3, length 64
|
||||
IP 10.172.16.6 > 10.172.16.25: ICMP echo request, id 21, seq 3, length 64
|
||||
IP 10.172.16.25 > 10.172.16.6: ICMP echo reply, id 21, seq 3, length 64
|
||||
IP 10.172.16.25 > 10.172.16.6: ICMP echo reply, id 21, seq 3, length 64
|
||||
IP 10.172.16.6 > 10.172.16.25: ICMP echo request, id 21, seq 4, length 64
|
||||
IP 10.172.16.6 > 10.172.16.25: ICMP echo request, id 21, seq 4, length 64
|
||||
IP 10.172.16.25 > 10.172.16.6: ICMP echo reply, id 21, seq 4, length 64
|
||||
IP 10.172.16.25 > 10.172.16.6: ICMP echo reply, id 21, seq 4, length 64
|
||||
IP 10.172.16.6 > 10.172.16.25: ICMP echo request, id 21, seq 5, length 64
|
||||
IP 10.172.16.6 > 10.172.16.25: ICMP echo request, id 21, seq 5, length 64
|
||||
IP 10.172.16.25 > 10.172.16.6: ICMP echo reply, id 21, seq 5, length 64
|
||||
IP 10.172.16.25 > 10.172.16.6: ICMP echo reply, id 21, seq 5, length 64
|
||||
```
|
||||
|
||||
and this for the physical network interface _(public IP addresses of end devices redacted)_:
|
||||
|
||||
```
|
||||
$ tcpdump -tni eth0 'udp and port 51913'
|
||||
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
|
||||
listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
|
||||
IP 87.161.X.X.51941 > 139.144.78.247.51913: UDP, length 128
|
||||
IP 139.144.78.247.51913 > 109.43.X.X.11007: UDP, length 128
|
||||
IP 109.43.X.X.11007 > 139.144.78.247.51913: UDP, length 128
|
||||
IP 139.144.78.247.51913 > 87.161.X.X.51941: UDP, length 128
|
||||
IP 87.161.X.X.51941 > 139.144.78.247.51913: UDP, length 128
|
||||
IP 139.144.78.247.51913 > 109.43.X.X.11007: UDP, length 128
|
||||
IP 109.43.X.X.11007 > 139.144.78.247.51913: UDP, length 128
|
||||
IP 139.144.78.247.51913 > 87.161.X.X.51941: UDP, length 128
|
||||
IP 87.161.X.X.51941 > 139.144.78.247.51913: UDP, length 128
|
||||
IP 139.144.78.247.51913 > 109.43.X.X.11007: UDP, length 128
|
||||
IP 109.43.X.X.11007 > 139.144.78.247.51913: UDP, length 128
|
||||
IP 139.144.78.247.51913 > 87.161.X.X.51941: UDP, length 128
|
||||
IP 87.161.X.X.51941 > 139.144.78.247.51913: UDP, length 128
|
||||
IP 139.144.78.247.51913 > 109.43.X.X.11007: UDP, length 128
|
||||
IP 109.43.X.X.11007 > 139.144.78.247.51913: UDP, length 128
|
||||
IP 139.144.78.247.51913 > 87.161.X.X.51941: UDP, length 128
|
||||
IP 87.161.X.X.51941 > 139.144.78.247.51913: UDP, length 128
|
||||
IP 139.144.78.247.51913 > 109.43.X.X.11007: UDP, length 128
|
||||
IP 109.43.X.X.11007 > 139.144.78.247.51913: UDP, length 128
|
||||
IP 139.144.78.247.51913 > 87.161.X.X.51941: UDP, length 128
|
||||
```
|
||||
|
||||
As you can see, for every ICMP echo request, we get an ICMP echo reply from 10.172.16.25 in the `tcpdump` output of 10.172.16.6.
|
||||
We also see that we receive a response for every UDP packet sent to port 51913 of 139.144.78.247.
|
||||
|
||||
However, in the `tcpdump` output of 10.172.16.1 for <code class="bg-transparent">wg0</code>,
|
||||
we see every packet twice. Why is that?
|
||||
|
||||
The reason for this is that `tcpdump` captures every incoming and outgoing packet <span id="ft-3b">[[3]](#ft-3)</span>.
|
||||
|
||||
Since we are forwarding packets on the virtual network interface, the packet for 10.172.16.25 from 10.172.16.6 will be captured on 10.172.16.1
|
||||
when it enters the interface from 10.172.16.6 and when it leaves to 10.172.16.25.
|
||||
|
||||
The same happens when the response from 10.172.16.25 arrives at 10.172.16.1 and is forwarded to 10.172.16.6.
|
||||
|
||||
We can see this forwarding happening better in the `tcpdump` output for the physical network interface. There, we see that for every ICMP echo request, we see one UDP
|
||||
packet from 87.161.X.X to 139.144.78.247 and then another from 139.144.78.247 to 109.43.X.X.
|
||||
|
||||
As you also can see, the forwarding does not change internal IP addresses. The packets still contain the same source and destination IP addresses.
|
||||
This works because we have configured the interfaces of all peers to route all IP addresses towards the router peer.
|
||||
So when 10.172.16.25 sees the ICMP echo request from 10.172.16.6 via 10.172.16.1, it will just respond to 10.172.16.6 again via 10.172.16.1.
|
||||
|
||||
The UDP packet IP addresses do seem changed, though. However, this is not network address translation (NAT) but just packet encapsulation.
|
||||
As mentioned in my [previous blog post](/blog/20230809-demystifying-wireguard-and-iptables), the physical network interfaces have no notion of internal IP addresses
|
||||
thus every UDP packet is sent using publicly routable IP addresses over the wire.
|
||||
|
||||
This means we are not doing any network address translation here hence the name forwarding.
|
||||
|
||||
Another important detail is that we do not need the following FORWARD rules in the router:
|
||||
|
||||
```
|
||||
-A FORWARD -s 10.172.16.0/24 -i wg0 -o eth0 -j ACCEPT
|
||||
-A FORWARD -d 10.172.16.0/24 -i eth0 -o wg0 -j ACCEPT
|
||||
```
|
||||
|
||||
These rules are only required if we want to forward VPN traffic to <code class="bg-transparent">eth0</code> which faces the public internet.
|
||||
Therefore, these rules would be part of a configuration to give internet access via 10.172.16.1 to peers inside the VPN.
|
||||
|
||||
---
|
||||
|
||||
Thanks for reading my second blog post! If you want to read more content like this, please consider subscribing via [RSS](https://dev.ekzyis.com/blog/rss.xml).
|
||||
|
||||
In the next blog post, we will use network address translation with the SNAT and DNAT targets in `iptables` for port forwarding.
|
||||
This will make it possible to expose an internal service (like an HTTP server for example) to the public internet.
|
||||
|
||||
---
|
||||
|
||||
<small>
|
||||
<span id="ft-0">[[0]](#ft-0b) `wg-quick strip wg0` returns the config in the format that `wg` can parse.
|
||||
This is necessary because `wg-quick` <q>adds a few extra configuration values to the format understood by `wg` in order to
|
||||
configure additional attributes of an interface</q>. ([source](https://man.archlinux.org/man/wg-quick.8.en#CONFIGURATION))
|
||||
</span><br />
|
||||
<span id="ft-1">[[1]](#ft-1b) Since 10.172.16.25 is a mobile device, I didn't include `tcpdump` output from that device. It also wouldn't include anything interesting.
|
||||
</span><br />
|
||||
<span id="ft-2">[[2]](#ft-2b) I used `-t` to not include timestamps and `-n` to not resolve IP addresses to hostnames. `-i <interface>` selects the interface we want to tap.
|
||||
</span><br />
|
||||
<span id="ft-3">[[3]](#ft-3b) To be precise, `tcpdump` captures incoming packets _before_ firewall processing
|
||||
while outgoing packets will be captured _after_ firewall processing. This means `tcpdump` will capture incoming packets that will be dropped by the firewall
|
||||
whereas outgoing packets that were dropped will not show up.
|
||||
See [here](https://wiki.archlinux.org/title/Network_Debugging#Tcpdump) and [here](https://superuser.com/a/925332) for more information.
|
||||
</span>
|
||||
</small>
|
@ -1,314 +0,0 @@
|
||||
Title: WireGuard Port Forwarding
|
||||
Date: 2023-09-25
|
||||
ReadingTime: 10 minutes
|
||||
Sats: 1803
|
||||
Comments: https://stacker.news/items/265524
|
||||
|
||||
---
|
||||
|
||||
<details open>
|
||||
<summary><b><a name="index">index</a></b></summary>
|
||||
<div class="flex flex-column">
|
||||
<a href="#introduction">0. introduction</a>
|
||||
<a href="#nat-primer">1. NAT primer</a>
|
||||
<a href="#port-forwarding">2. port forwarding</a>
|
||||
<a class="subh" href="#port-forwarding-initial"><i>2.1 initial configuration</i></a>
|
||||
<a class="subh" href="#port-forwarding-internal"><i>2.2 HTTP server on 10.172.16.2 <> 10.172.16.1</i></a>
|
||||
<a class="subh" href="#port-forwarding-internet"><i>2.3 HTTP server on 10.172.16.2 <> 93.184.216.34</i></a>
|
||||
<a class="subh" href="#port-forwarding-final"><i>2.4 final configuration</i></a>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
<a class="header" name="introduction">
|
||||
# introduction
|
||||
</a>
|
||||
|
||||
Today, we will implement port forwarding with [`iptables`](https://wiki.archlinux.org/title/iptables). We will do this to expose a service to the public internet which is running inside a VPN.
|
||||
|
||||
The exposed service will be a basic HTTP server running on a host in a home network. This means that the host itself already has a connection to the public internet (as most hosts in a home network do) and is not only connected to all other hosts inside the VPN via a VPN router.
|
||||
|
||||
However, since the HTTP server is running inside the VPN, we will need to use port forwarding inside our VPN instead of on the router with which we access the public internet. Port forwarding on that home router won't work since it can't access our VPN service (even though the host it's running on is inside the home network!).
|
||||
|
||||
Essentially, we will update the network topology from the [previous blog post](/blog/20230821-wireguard-packet-forwarding) with a connection to the public internet:
|
||||
|
||||
![](/blog/img/star_network_with_internet_access.png)
|
||||
|
||||
As always, after reading my blog post, you will not only be able to setup your own HTTP server inside a VPN; but will actually understand what happens at the network level. With this knowledge, you will be able to adapt and find the best solution for your specific needs.
|
||||
|
||||
Therefore, we start with a primer about NAT which stands for _Network Address Translation_.
|
||||
|
||||
---
|
||||
|
||||
<a class="header" name="nat-primer">
|
||||
# NAT primer
|
||||
</a>
|
||||
|
||||
Network Address Translation was developed to mitigate the issue of not having enough IPv4 addresses for every device which wants to connect to the internet. IPv4 addresses are only 32 bits long which means there are only 2<sup>32</sup> = 4,294,967,296 addresses available. This may sound like a lot but when considering that more and more devices are connected to the internet (which isn't going to stop anytime soon) and that the world population is around 8 billion people, there simply aren't enough IPv4 addresses to go around. Hence a solution needed to be found.
|
||||
|
||||
With NAT, one can hide multiple devices behind a router and thus behind a single IPv4 address. The hosts connected to the internet via this router are assigned private IPv4 addresses and thus form a private network. Private IPv4 addresses are addresses which are not routable on the public internet. Routers on the public internet would simply not forward packets containing these IP addresses. The following IPv4 address ranges were reserved for such private use by [IANA](https://www.iana.org/) in [RFC1918](https://datatracker.ietf.org/doc/html/rfc1918#section-3):
|
||||
|
||||
- 10.0.0.8/8
|
||||
- 172.16.0.0/12
|
||||
- 192.168.0.0/16
|
||||
|
||||
This mitigates the problem since these private IP addresses only have to be unique per private network and not globally. That's also the reason why these addresses are not routable. How should a router know how to route these packets? There isn't a unique device with this IP address!
|
||||
|
||||
To enable internet access for hosts with only a private IPv4 address, every packet with a destination not inside the private network gets forwarded to the _NAT gateway_. Usually, this is the router. The router then replaces the private source IP address with its own public IP address <span id="ft-0b">[[0]](#ft-0)</span> and the port with another random port:
|
||||
|
||||
![](/blog/img/nat_send.png)
|
||||
|
||||
The NAT gateway then stores this replacement in a NAT table:
|
||||
|
||||
![](/blog/img/nat_table.png)
|
||||
|
||||
For arriving packets, this table is consulted to reverse the translation:
|
||||
|
||||
![](/blog/img/nat_recv.png)
|
||||
|
||||
The NAT IP address and NAT port are required to reverse this process.
|
||||
Without them, we would not be able to distinguish multiple connections from the private network to the same destination IP address.
|
||||
|
||||
This method to allow multiple hosts inside a private network access to the internet is called _Source NAT_ (SNAT) since we change the source IP address when initiating the connection.
|
||||
|
||||
If we want to allow hosts from the public internet to access hosts in a private network, we use _Destination NAT_ (DNAT) since we change the destination IP address when the connection is initiated.
|
||||
|
||||
The reversal in both methods is automatically handled using the NAT table.
|
||||
|
||||
---
|
||||
|
||||
<a class="header" name="port-forwarding">
|
||||
# Port forwarding
|
||||
</a>
|
||||
|
||||
We will now apply this knowledge to expose an HTTP server running inside a VPN. Our setup will work like this:
|
||||
|
||||
![](/blog/img/nat_http.png)
|
||||
|
||||
We will have to use DNAT _and_ SNAT since we need to change the destination IP address to 10.172.16.2 _and_ the source IP address to 10.172.16.1 since we can only route internal IP addresses (10.172.16.0/24) over the virtual network interface to the HTTP server.
|
||||
|
||||
<a class="header" name="port-forwarding-initial">
|
||||
## initial configuration
|
||||
</a>
|
||||
|
||||
We will start with the following configuration of the VPN router which will also act as a NAT gateway:
|
||||
|
||||
_/etc/wireguard/wg0.conf @ 10.172.16.1:_
|
||||
```
|
||||
[Interface]
|
||||
Address = 10.172.16.1/32
|
||||
PrivateKey = r3M073+s3cR37+fouaQZbP5QqfgwypHjKGBNmztxNEc=
|
||||
ListenPort = 51913
|
||||
|
||||
[Peer]
|
||||
AllowedIPs = 10.172.16.2/32
|
||||
PublicKey = /wH4OzafBUJVvRGzK8itUweV/GpwoUzn7OS99lr7gHI=
|
||||
```
|
||||
|
||||
_firewall configuration @ 10.172.16.1:_
|
||||
```
|
||||
-P INPUT DROP
|
||||
-P FORWARD DROP
|
||||
-P OUTPUT DROP
|
||||
-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
|
||||
-A INPUT -i wg0 -j ACCEPT
|
||||
-A INPUT -i eth0 -p udp -m udp --dport 51913 -j ACCEPT
|
||||
-A FORWARD -i wg0 -o wg0 -j ACCEPT
|
||||
-A OUTPUT -m state --state ESTABLISHED -j ACCEPT
|
||||
-A OUTPUT -o wg0 -j ACCEPT
|
||||
```
|
||||
|
||||
IP forwarding is also enabled in the kernel:
|
||||
|
||||
```
|
||||
$ sysctl net.ipv4.ip_forward
|
||||
net.ipv4.ip_forward = 1
|
||||
```
|
||||
|
||||
The host on which we will run the HTTP server is configured like this:
|
||||
|
||||
_/etc/wireguard/wg0.conf @ 10.172.16.2:_
|
||||
```
|
||||
[Interface]
|
||||
Address = 10.172.16.2/32
|
||||
PrivateKey = l0c4l+s3cR37+RDr+dJdgX/ACeRQLANiduQRJK9O23A=
|
||||
|
||||
[Peer]
|
||||
AllowedIPs = 10.172.16.0/24
|
||||
PublicKey = GL33DRrI8/2yAT6+r5mTtBLd7CoErAAsio3yNqQ3K1M=
|
||||
Endpoint = 93.184.216.34:51913
|
||||
PersistentKeepalive = 25
|
||||
```
|
||||
|
||||
_firewall configuration @ 10.172.16.2:_
|
||||
|
||||
```
|
||||
-P INPUT DROP
|
||||
-P FORWARD DROP
|
||||
-P OUTPUT DROP
|
||||
-A INPUT -m state --state ESTABLISHED -j ACCEPT
|
||||
-A INPUT -i wg0 -j ACCEPT
|
||||
-A INPUT -s 93.184.216.34 -i enp3s0 -p udp -m udp --sport 51913 -j ACCEPT
|
||||
-A OUTPUT -d 93.184.216.34/32 -p tcp -m tcp --dport 22 -j ACCEPT
|
||||
-A OUTPUT -o wg0 -j ACCEPT
|
||||
-A OUTPUT -d 93.184.216.34/32 -o enp3s0 -p udp -m udp --dport 51913 -j ACCEPT
|
||||
```
|
||||
|
||||
<a class="header" name="port-forwarding-internal">
|
||||
## HTTP server on 10.172.16.2 <> 10.172.16.1
|
||||
</a>
|
||||
|
||||
With the existing configuration, we can run an HTTP server on 10.172.16.2 and access it from 10.172.16.1.
|
||||
|
||||
To keep it simple, we will use the built-in HTTP server from Python:
|
||||
|
||||
_10.172.16.2:_
|
||||
```
|
||||
$ python -m http.server -b 10.172.16.2 8000
|
||||
```
|
||||
|
||||
As mentioned, we can already access the HTTP server from 10.172.16.1 with the existing configuration:
|
||||
|
||||
_10.172.16.1:_
|
||||
```
|
||||
$ curl -I 10.172.16.2:8000
|
||||
HTTP/1.0 200 OK
|
||||
Server: SimpleHTTP/0.6 Python/3.11.5
|
||||
Date: Sun, 24 Sep 2023 22:28:14 GMT
|
||||
Content-type: text/html; charset=utf-8
|
||||
Content-Length: 187
|
||||
```
|
||||
|
||||
<a class="header" name="port-forwarding-internet">
|
||||
## HTTP server on 10.172.16.2 <> 93.184.216.34
|
||||
</a>
|
||||
|
||||
Our goal is to access the HTTP server using the public IP address of the VPN server.
|
||||
With the following command, we try exactly this. We try to access the HTTP server running on the same host but inside the VPN over the public internet:
|
||||
|
||||
_10.172.16.2:_
|
||||
```
|
||||
$ curl -v 93.184.216.34:8000
|
||||
* processing: 93.184.216.34:8000
|
||||
* Trying 93.184.216.34:8000...
|
||||
```
|
||||
|
||||
However, currently, we get no response since the firewall of 93.184.216.34 drops the incoming packets.
|
||||
|
||||
We can also see this in the captured network traffic since there are no further packets:
|
||||
|
||||
_10.172.16.1:_
|
||||
```
|
||||
$ tcpdump -i any -n '(host 54.147.66.132 or net 10.172.16.0/24) and not port 22'
|
||||
tcpdump: data link type LINUX_SLL2
|
||||
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
|
||||
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
|
||||
22:52:25.007994 eth0 In IP 54.147.66.132.47746 > 93.184.216.34.8000: Flags [S], seq 224277161, win 64240, options [mss 1452,sackOK,TS val 3994473945 ecr 0,nop,wscale 7], length 0
|
||||
```
|
||||
|
||||
_(54.147.66.132 is the public IP address used by 10.172.16.2)_
|
||||
|
||||
To open port 8000 and use DNAT, we update the firewall configuration like this:
|
||||
|
||||
```diff
|
||||
-P INPUT DROP
|
||||
-P FORWARD DROP
|
||||
-P OUTPUT DROP
|
||||
-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
|
||||
-A INPUT -i wg0 -j ACCEPT
|
||||
-A INPUT -i eth0 -p udp -m udp --dport 51913 -j ACCEPT
|
||||
-A FORWARD -i wg0 -o wg0 -j ACCEPT
|
||||
+ -A FORWARD -i eth0 -o wg0 -j ACCEPT
|
||||
-A OUTPUT -m state --state ESTABLISHED -j ACCEPT
|
||||
-A OUTPUT -o wg0 -j ACCEPT
|
||||
+ -t nat -A PREROUTING -p tcp -m tcp --dport 8000 -j DNAT --to-destination 10.172.16.2:8000
|
||||
```
|
||||
|
||||
We will now see that the destination IP address of the packet is replaced <span id="ft-1b">[[1]](#ft-1)</span>:
|
||||
|
||||
```diff
|
||||
23:00:35.336176 eth0 In IP 54.147.66.132.38308 > 93.184.216.34.8000: Flags [S], seq 511352029, win 64240, options [mss 1452,sackOK,TS val 3994964273 ecr 0,nop,wscale 7], length 0
|
||||
+ 23:00:35.336226 wg0 Out IP 54.147.66.132.38308 > 10.172.16.2.8000: Flags [S], seq 511352029, win 64240, options [mss 1452,sackOK,TS val 3994964273 ecr 0,nop,wscale 7], length 0
|
||||
```
|
||||
|
||||
As mentioned, we can't route public IP addresses inside our VPN. Therefore, we also need to use SNAT to translate the source IP address to the internal IP address:
|
||||
|
||||
```diff
|
||||
-t nat -A PREROUTING -p tcp -m tcp --dport 8000 -j DNAT --to-destination 10.172.16.2:8000
|
||||
+ -t nat -A POSTROUTING -d 10.172.16.2/32 -o wg0 -p tcp -m tcp --dport 8000 -j SNAT --to-source 10.172.16.1
|
||||
```
|
||||
|
||||
We now see that the source IP address is also translated and further packets are captured:
|
||||
|
||||
```diff
|
||||
23:14:28.136307 eth0 In IP 54.147.66.132.54866 > 93.184.216.34.8000: Flags [S], seq 1430183439, win 64240, options [mss 1452,sackOK,TS val 3995797073 ecr 0,nop,wscale 7], length 0
|
||||
- 23:14:28.136339 wg0 Out IP 54.147.66.132.54866 > 10.172.16.2.8000: Flags [S], seq 1430183439, win 64240, options [mss 1452,sackOK,TS val 3995797073 ecr 0,nop,wscale 7], length 0
|
||||
+ 23:14:28.136339 wg0 Out IP 10.172.16.1.54866 > 10.172.16.2.8000: Flags [S], seq 1430183439, win 64240, options [mss 1452,sackOK,TS val 3995797073 ecr 0,nop,wscale 7], length 0
|
||||
+ 23:14:28.136375 eth0 Out IP 93.184.216.34.51913 > 54.147.66.132.38785: UDP, length 96
|
||||
+ 23:14:28.146021 eth0 In IP 54.147.66.132.38785 > 93.184.216.34.51913: UDP, length 96
|
||||
+ 23:14:28.146053 wg0 In IP 10.172.16.2.8000 > 10.172.16.1.54866: Flags [S.], seq 3292104528, ack 1430183440, win 64296, options [mss 1380,sackOK,TS val 445665275 ecr 3995797073,nop,wscale 7], length 0
|
||||
```
|
||||
|
||||
The last two packets are the response from the HTTP server. However, the response does not get forwarded to the <code class="bg-transparent">eth0</code> interface. That's because we only added a rule to allow forwarding from <code class="bg-transparent">eth0</code> to <code class="bg-transparent">wg0</code> but not vice versa. After changing this:
|
||||
|
||||
```diff
|
||||
-A FORWARD -i eth0 -o wg0 -j ACCEPT
|
||||
+ -A FORWARD -i wg0 -o eth0 -j ACCEPT
|
||||
```
|
||||
|
||||
we receive the response now:
|
||||
|
||||
```diff
|
||||
23:31:36.029921 eth0 In IP 54.147.66.132.42710 > 93.184.216.34.8000: Flags [S], seq 2290103144, win 64240, options [mss 1452,sackOK,TS val 3996824967 ecr 0,nop,wscale 7], length 0
|
||||
23:31:36.030009 wg0 Out IP 10.172.16.1.54866 > 10.172.16.2.8000: Flags [S], seq 2290103144, win 64240, options [mss 1452,sackOK,TS val 3996824967 ecr 0,nop,wscale 7], length 0
|
||||
23:31:36.030073 eth0 Out IP 93.184.216.34.59194 > 54.147.66.132.38785: UDP, length 96
|
||||
23:31:36.039876 eth0 In IP 54.147.66.132.38785 > 93.184.216.34.59194: UDP, length 96
|
||||
23:31:36.039924 wg0 In IP 10.172.16.2.8000 > 10.172.16.1.42710: Flags [S.], seq 4227529946, ack 2290103145, win 64296, options [mss 1380,sackOK,TS val 446693169 ecr 3996824967,nop,wscale 7], length 0
|
||||
+ 23:31:36.039947 eth0 Out IP 93.184.216.34.8000 > 54.147.66.132.42710: Flags [S.], seq 4227529946, ack 2290103145, win 64296, options [mss 1380,sackOK,TS val 446693169 ecr 3996824967,nop,wscale 7], length 0
|
||||
```
|
||||
|
||||
The output of <code class="bg-transparent">curl</code> further confirms this:
|
||||
|
||||
_10.172.16.2_:
|
||||
```
|
||||
$ curl -I 93.184.216.34:8000
|
||||
HTTP/1.0 200 OK
|
||||
Server: SimpleHTTP/0.6 Python/3.11.5
|
||||
Date: Sun, 24 Sep 2023 23:36:26 GMT
|
||||
Content-type: text/html; charset=utf-8
|
||||
Content-Length: 187
|
||||
```
|
||||
|
||||
<a class="header" name="port-forwarding-final">
|
||||
## final configuration
|
||||
</a>
|
||||
|
||||
The final firewall configuration of 10.172.16.1 is this:
|
||||
|
||||
```diff
|
||||
-P INPUT DROP
|
||||
-P FORWARD DROP
|
||||
-P OUTPUT DROP
|
||||
-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
|
||||
-A INPUT -i wg0 -j ACCEPT
|
||||
-A INPUT -i eth0 -p udp -m udp --dport 51913 -j ACCEPT
|
||||
+ -A FORWARD -i eth0 -o wg0 -j ACCEPT
|
||||
+ -A FORWARD -i wg0 -o eth0 -j ACCEPT
|
||||
-A OUTPUT -m state --state ESTABLISHED -j ACCEPT
|
||||
-A OUTPUT -o wg0 -j ACCEPT
|
||||
+ -t nat -A PREROUTING -p tcp -m tcp --dport 8000 -j DNAT --to-destination 10.172.16.2:8000
|
||||
+ -t nat -A POSTROUTING -d 10.172.16.2/32 -o wg0 -p tcp -m tcp --dport 8000 -j SNAT --to-source 10.172.16.1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Congratulations! You now know how network address translation works and how port forwarding is implemented using DNAT and SNAT.
|
||||
|
||||
---
|
||||
|
||||
<small>
|
||||
<span id="ft-0">[[0]](#ft-0b) A router in a private network has two IP addresses: a private address which is commonly the first IP address in the used ranged (so 192.168.0.1 if the range 192.168.0.0/16 is used) and a public one assigned by an internet service provider.
|
||||
</span><br />
|
||||
<span id="ft-1">[[1]](#ft-1b) We can see that it's the same packet since everything is the same (especially the sequence number and TS val) except the destination IP address.
|
||||
</small>
|
@ -1,20 +0,0 @@
|
||||
<code>
|
||||
<strong>
|
||||
<pre class="text-center">
|
||||
_ _
|
||||
| |__ | | ___ __ _
|
||||
| '_ \| |/ _ \ / _` |
|
||||
| |_) | | (_) | (_| |
|
||||
|_.__/|_|\___/ \__, |
|
||||
|___/ </pre>
|
||||
</strong>
|
||||
</code>
|
||||
<ul>
|
||||
{{ range .Posts -}}
|
||||
<li>
|
||||
<span class="font-mono mb-1">{{- .Date -}}</span> |
|
||||
<a href="{{- ToHref .FsPath -}}">{{- .Title -}}</a> |
|
||||
<span>{{- .Sats }} sats</span>
|
||||
</li>
|
||||
{{- end }}
|
||||
</ul>
|
25
content/about.md
Normal file
@ -0,0 +1,25 @@
|
||||
+++
|
||||
title = "About"
|
||||
date = "2024-04-13"
|
||||
[params]
|
||||
banner = "psychedelic-digital-sky.jpg"
|
||||
+++
|
||||
|
||||
Hi, I'm ek. I guess this is the part where I introduce myself in an interesting and unique way such that reading this is worth your time and you don't immediately leave. [Attention is a scarce resource](https://en.wikipedia.org/wiki/Attention_economy) after all.
|
||||
|
||||
However, I would actually prefer to not introduce myself. I would prefer to let my words speak for themselves--my words here and in what I write about on this site. So let me introduce you to my site.
|
||||
|
||||
## To be unique to be authentic
|
||||
|
||||
I see this site as my tiny island in the vastness of the internet. It does not try to compete with other islands since I believe this would mean it's not trying to be unique. **Competition can only exist between parties that want the same thing.**
|
||||
|
||||
However, is this true? Even if an island is trying to be unique, from the visitor's perspective, it still competes with every other island, unwillingly or not. A visitor can only be at one island at a time after all. Additionally, the desire to be unique is not unique among islands. A visitor doesn't care what you want. A visitor cares what you provide.
|
||||
|
||||
Does this mean competition is a matter of perspective? Maybe so. But more importantly, it means that **competition can only exist between parties that provide the same thing.**
|
||||
|
||||
Therefore, the goal of this site is to provide you something unique. It eliminates competition by stepping out of it and embracing its quirks. It's very much like finding a niche in which you naturally fit in a way that others can't. **Maybe this is the ultimate competition: to be unique to be authentic.**
|
||||
|
||||
In any way, this whole site is meant to be a [complex search query to find fascinating people](https://www.henrikkarlsson.xyz/p/search-query). I will leave my introduction at this for now.
|
||||
|
||||
[Why don't you introduce yourself to me now?](https://stacker.news/items/505345)
|
||||
|
21
deploy
Executable file
@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# https://gohugo.io/hosting-and-deployment/deployment-with-rsync/
|
||||
|
||||
set -e
|
||||
|
||||
RSYNC_OPTS="-avh --delete"
|
||||
COMMIT=$(git rev-parse --short HEAD)
|
||||
|
||||
sed -i -e "s/^commit\s*=.*$/commit = \"$COMMIT\"/" hugo.toml
|
||||
|
||||
rm -r public/
|
||||
hugo
|
||||
rsync $RSYNC_OPTS --dry-run public/ ekzy.is:/var/www/ek
|
||||
|
||||
echo
|
||||
read -p "Continue deploy? [yn] " yn
|
||||
echo
|
||||
[ "$yn" == "y" ] && rsync $RSYNC_OPTS public/ ekzy.is:/var/www/ek
|
||||
|
||||
git checkout hugo.toml
|
11
deploy.sh
@ -1,11 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
ENV=production make -B
|
||||
rsync -avh .well-known public/ ekzyis.com:/var/www/ekzyis --delete --dry-run
|
||||
|
||||
echo
|
||||
read -p "Continue deploy? [yn] " yn
|
||||
echo
|
||||
[ "$yn" == "y" ] && rsync -avh .well-known public/ ekzyis.com:/var/www/ekzyis --delete
|
16
go.mod
@ -1,16 +0,0 @@
|
||||
module renderer
|
||||
|
||||
go 1.20
|
||||
|
||||
require github.com/tdewolff/minify/v2 v2.12.8
|
||||
|
||||
require (
|
||||
github.com/PuerkitoBio/goquery v1.8.1 // indirect
|
||||
github.com/andybalholm/cascadia v1.3.2 // indirect
|
||||
github.com/gomarkdown/markdown v0.0.0-20230716120725-531d2d74bc12 // indirect
|
||||
github.com/namsral/flag v1.7.4-pre // indirect
|
||||
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d // indirect
|
||||
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e // indirect
|
||||
github.com/tdewolff/parse/v2 v2.6.7 // indirect
|
||||
golang.org/x/net v0.14.0 // indirect
|
||||
)
|
59
go.sum
@ -1,59 +0,0 @@
|
||||
github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM=
|
||||
github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=
|
||||
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
|
||||
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
|
||||
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
|
||||
github.com/gomarkdown/markdown v0.0.0-20230716120725-531d2d74bc12 h1:uK3X/2mt4tbSGoHvbLBHUny7CKiuwUip3MArtukol4E=
|
||||
github.com/gomarkdown/markdown v0.0.0-20230716120725-531d2d74bc12/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
|
||||
github.com/namsral/flag v1.7.4-pre h1:b2ScHhoCUkbsq0d2C15Mv+VU8bl8hAXV8arnWiOHNZs=
|
||||
github.com/namsral/flag v1.7.4-pre/go.mod h1:OXldTctbM6SWH1K899kPZcf65KxJiD7MsceFUpB5yDo=
|
||||
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d h1:yKm7XZV6j9Ev6lojP2XaIshpT4ymkqhMeSghO5Ps00E=
|
||||
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
|
||||
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e h1:qpG93cPwA5f7s/ZPBJnGOYQNK/vKsaDaseuKT5Asee8=
|
||||
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
|
||||
github.com/tdewolff/minify/v2 v2.12.8 h1:Q2BqOTmlMjoutkuD/OPCnJUpIqrzT3nRPkw+q+KpXS0=
|
||||
github.com/tdewolff/minify/v2 v2.12.8/go.mod h1:YRgk7CC21LZnbuke2fmYnCTq+zhCgpb0yJACOTUNJ1E=
|
||||
github.com/tdewolff/parse/v2 v2.6.7 h1:WrFllrqmzAcrKHzoYgMupqgUBIfBVOb0yscFzDf8bBg=
|
||||
github.com/tdewolff/parse/v2 v2.6.7/go.mod h1:XHDhaU6IBgsryfdnpzUXBlT6leW/l25yrFBTEb4eIyM=
|
||||
github.com/tdewolff/test v1.0.9 h1:SswqJCmeN4B+9gEAi/5uqT0qpi1y2/2O47V/1hhGZT0=
|
||||
github.com/tdewolff/test v1.0.9/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
|
||||
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
118
html.go
@ -1,118 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/tdewolff/minify/v2"
|
||||
"github.com/tdewolff/minify/v2/html"
|
||||
)
|
||||
|
||||
var (
|
||||
m *minify.M
|
||||
BuildDate string
|
||||
t = template.Must(template.ParseGlob("html/template/*.html"))
|
||||
)
|
||||
|
||||
func init() {
|
||||
m = minify.New()
|
||||
m.AddFunc("text/html", html.Minify)
|
||||
BuildDate = time.Now().In(time.UTC).Format("2006-01-02 15:04:05.000000000 -0700")
|
||||
}
|
||||
|
||||
type HtmlSource struct {
|
||||
FsPath string
|
||||
Title string
|
||||
Href string
|
||||
Content []byte
|
||||
}
|
||||
|
||||
func NewHtmlSource(path string) *HtmlSource {
|
||||
source := HtmlSource{FsPath: path}
|
||||
source.ComputeTitle()
|
||||
source.ComputeHref()
|
||||
source.LoadContent()
|
||||
return &source
|
||||
}
|
||||
|
||||
func (source *HtmlSource) ComputeTitle() {
|
||||
// FIXME
|
||||
// this is just a temporary workaround.
|
||||
// actual solution would be to use title from blog post markdown metadata
|
||||
switch source.FsPath {
|
||||
case "html/pages/blog/index.html":
|
||||
source.Title = "blog | ekzyis"
|
||||
case "html/pages/blog/20230809-demystifying-wireguard-and-iptables.html":
|
||||
source.Title = "Demystifying WireGuard and iptables | blog | ekzyis"
|
||||
case "html/pages/blog/20230821-wireguard-packet-forwarding.html":
|
||||
source.Title = "WireGuard Packet Forwarding | blog | ekzyis"
|
||||
case "html/pages/blog/20230925-wireguard-port-forwarding.html":
|
||||
source.Title = "WireGuard Port Forwarding | blog | ekzyis"
|
||||
default:
|
||||
source.Title = "ekzyis"
|
||||
}
|
||||
}
|
||||
|
||||
func (source *HtmlSource) ComputeHref() {
|
||||
source.Href = strings.ReplaceAll(strings.ToLower(source.FsPath), ".html", "")
|
||||
if source.Href == "index" {
|
||||
source.Href = "/"
|
||||
}
|
||||
}
|
||||
|
||||
func (source *HtmlSource) LoadContent() {
|
||||
content, err := os.ReadFile(source.FsPath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
source.Content = content
|
||||
}
|
||||
|
||||
func GetVersion() string {
|
||||
cmd := exec.Command("git", "rev-parse", "--short", "HEAD")
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return out.String()
|
||||
}
|
||||
|
||||
func (source *HtmlSource) Render() {
|
||||
args := map[string]any{
|
||||
"BuildDate": BuildDate,
|
||||
"Env": Env,
|
||||
"Title": source.Title,
|
||||
"Href": source.Href,
|
||||
"Content": string(source.Content),
|
||||
"Version": GetVersion(),
|
||||
}
|
||||
ExecuteTemplate(args)
|
||||
}
|
||||
|
||||
func ExecuteTemplate(args map[string]any) {
|
||||
mw := m.Writer("text/html", os.Stdout)
|
||||
defer mw.Close()
|
||||
err := t.ExecuteTemplate(mw, "layout.html", args)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func GetHtmlSources(srcDir string) *[]HtmlSource {
|
||||
var sources []HtmlSource
|
||||
paths, err := filepath.Glob(srcDir + "*.html")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for _, path := range paths {
|
||||
sources = append(sources, *NewHtmlSource(path))
|
||||
}
|
||||
return &sources
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
<code>
|
||||
<strong>
|
||||
<pre class="text-center">
|
||||
_ _ ___ _ _
|
||||
| || | / _ \| || |
|
||||
| || |_| | | | || |_
|
||||
|__ _| |_| |__ _|
|
||||
|_| \___/ |_| </pre>
|
||||
</strong>
|
||||
</code>
|
||||
<div class="font-mono mb-1 text-center">Not Found</div>
|
@ -1,66 +0,0 @@
|
||||
<code>
|
||||
<strong>
|
||||
<pre class="text-center">
|
||||
_ _
|
||||
___| | __ _____ _(_)___
|
||||
/ _ \ |/ /|_ / | | | / __|
|
||||
| __/ < / /| |_| | \__ \
|
||||
\___|_|\_\/___|\__, |_|___/
|
||||
|___/ </pre>
|
||||
</strong>
|
||||
</code>
|
||||
<div class="text-center">
|
||||
<p>
|
||||
<small>
|
||||
<i>
|
||||
just a pleb who likes programming, networking, security, CTFs, privacy, bitcoin, or basically anything that is
|
||||
tech related ...
|
||||
</i>
|
||||
</small>
|
||||
</p>
|
||||
</div>
|
||||
<div class="font-mono mb-1 text-center">Welcome!</div>
|
||||
<div>
|
||||
<p>
|
||||
I see this website as my personal playground where I can experiment with (web) technologies.<br />
|
||||
For example, I intend to host this site as a onion site and integrate protocols like
|
||||
<a href="https://docs.lightning.engineering/the-lightning-network/l402">L402</a> or
|
||||
<a href="https://nostr.com/">nostr</a>.
|
||||
</p>
|
||||
<p>
|
||||
I also want to see with this site how far one can go with only static HTML and CSS; no JS.<sup>*</sup><br />
|
||||
The static HTML files are generated with
|
||||
<a href="https://pkg.go.dev/text/template">text/template</a>
|
||||
and deployed with
|
||||
<a href="https://wiki.archlinux.org/title/rsync">rsync</a>.
|
||||
</p>
|
||||
<p>
|
||||
Additionally, I want to share any knowledge gained in
|
||||
<a href="/blog">blog posts</a> for the following reasons:
|
||||
<ul>
|
||||
<li>They might be useful as guides if others want to do similar things</li>
|
||||
<li>
|
||||
Knowing that I can share whatever I learned with others later helps me stay motivated to keep learning more
|
||||
</li>
|
||||
<li>I want to keep track of all the things I've learned over time</li>
|
||||
<li>
|
||||
I would love to talk more about topics that I am interested in with people
|
||||
(<a href="https://stacker.news/">stacker.news</a> made me realize this!)
|
||||
</li>
|
||||
<li>
|
||||
Blogging is an obvious use case for a personal website and I want to be creative and write more
|
||||
</li>
|
||||
</ul>
|
||||
<p class="text-center">
|
||||
If I caught your interest now, you can subscribe via <a href="/blog/rss.xml">RSS</a>!
|
||||
</p>
|
||||
<p>
|
||||
<p class="text-center"><small>[subscribing via email and nostr coming soon]</small></p>
|
||||
<p class="text-center">
|
||||
<small>
|
||||
<sup>*</sup> i use
|
||||
<a target="_blank" href="https://plausible.io/ekzyis.com/">plausible.io</a>
|
||||
for web analytics though
|
||||
</small>
|
||||
</p>
|
||||
</div>
|
@ -1,15 +0,0 @@
|
||||
<footer class="flex text-center justify-center mx-1 pb-1">
|
||||
<div>
|
||||
<hr />
|
||||
<nav class="flex flex-row text-center justify-center">
|
||||
<a href="/blog/rss.xml">rss</a>
|
||||
<a href="https://primal.net/p/npub16x07c4qz05yhqe2gy2q2u9ax359d2lc0tsh6wn3y70dmk8nv2j2s96s89d">nostr</a>
|
||||
<a href="/simplex.jpeg">simplex</a>
|
||||
<a href="/pgp.txt">pgp</a>
|
||||
<a href="https://git.ekzyis.com/ekzyis">git</a>
|
||||
<a href="https://stacker.news/ekzyis">sn</a>
|
||||
</nav>
|
||||
<small><code>Build Date: {{ .BuildDate }}</code></small><br />
|
||||
<small><code>running <a target="_blank" href="https://git.ekzyis.com/ekzyis/ekzyis/commit/{{ .Version }}">{{ .Version }}</a></code></small>
|
||||
</div>
|
||||
</footer>
|
@ -1,17 +0,0 @@
|
||||
<head>
|
||||
<title>{{ if .Title }} {{- .Title -}} {{ else }} {{- "ekzyis" -}} {{ end }}</title>
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
<link rel="stylesheet" href="/index.css" />
|
||||
<link rel="stylesheet" href="/desert.css" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#091833" />
|
||||
<link rel="alternate" type="application/rss+xml" title="blog | ekzyis" href="/blog/rss.xml" />
|
||||
<script defer data-api="/api/event" data-domain="ekzyis.com" src="/js/script.js"></script>
|
||||
{{ if eq .Env "development" }}
|
||||
<script defer src="/hotreload.js"></script>
|
||||
{{ end }}
|
||||
</head>
|
@ -1,16 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
{{ template "head.html" . }}
|
||||
|
||||
<body>
|
||||
<header class="flex flex-row text-center justify-center pt-1 mx-1">
|
||||
{{ template "nav.html" }}
|
||||
</header>
|
||||
<div class="container flex flex-column">
|
||||
{{ .Content }}
|
||||
</div>
|
||||
{{ template "footer.html" . }}
|
||||
</body>
|
||||
|
||||
</html>
|
@ -1,4 +0,0 @@
|
||||
<nav>
|
||||
<a href="/">home</a>
|
||||
<a href="/blog">blog</a>
|
||||
</nav>
|
14
hugo.toml
Normal file
@ -0,0 +1,14 @@
|
||||
baseURL = 'https://ekzy.is/'
|
||||
languageCode = 'en-us'
|
||||
title = 'ekzyis'
|
||||
theme = 'holy'
|
||||
|
||||
[menu]
|
||||
[[menu.main]]
|
||||
identifier = "about"
|
||||
name = "About"
|
||||
url = "/about/"
|
||||
weight = 10
|
||||
|
||||
[params]
|
||||
commit = "000000"
|
7
layouts/404.html
Normal file
@ -0,0 +1,7 @@
|
||||
{{ define "main" }}
|
||||
<main id="main">
|
||||
<div>
|
||||
404 Not Found
|
||||
</div>
|
||||
</main>
|
||||
{{ end }}
|
1
layouts/partials/head-extra.html
Normal file
@ -0,0 +1 @@
|
||||
<script defer data-domain="ekzy.is" src="/js/script.js"></script>
|
171
markdown.go
@ -1,171 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/gomarkdown/markdown"
|
||||
"github.com/gomarkdown/markdown/html"
|
||||
)
|
||||
|
||||
var (
|
||||
MarkdownToHtmlFlags = (html.CommonFlags ^ html.SmartypantsFractions) | html.HrefTargetBlank
|
||||
)
|
||||
|
||||
type MarkdownPost struct {
|
||||
// file system path
|
||||
FsPath string
|
||||
// markdown content
|
||||
Content []byte
|
||||
// args parsed from markdown
|
||||
Title string
|
||||
Date string
|
||||
ReadingTime string
|
||||
Sats int
|
||||
Comments string
|
||||
}
|
||||
|
||||
func NewMarkdownPost(path string) *MarkdownPost {
|
||||
post := MarkdownPost{FsPath: path}
|
||||
post.LoadContent()
|
||||
return &post
|
||||
}
|
||||
|
||||
func (post *MarkdownPost) LoadContent() {
|
||||
f, err := os.OpenFile(post.FsPath, os.O_RDONLY, 0755)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
sc := bufio.NewScanner(f)
|
||||
post.ParseArgs(sc)
|
||||
var content []byte
|
||||
for sc.Scan() {
|
||||
read := sc.Bytes()
|
||||
content = append(content, read...)
|
||||
content = append(content, '\n')
|
||||
}
|
||||
err = sc.Err()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
post.Content = content
|
||||
}
|
||||
|
||||
func (post *MarkdownPost) ParseArgs(sc *bufio.Scanner) {
|
||||
for sc.Scan() {
|
||||
line := sc.Text()
|
||||
parts := strings.Split(line, ":")
|
||||
if len(parts) < 2 {
|
||||
break
|
||||
}
|
||||
parts[1] = strings.Trim(parts[1], " \n")
|
||||
switch parts[0] {
|
||||
case "Title":
|
||||
post.Title = strings.Join(parts[1:], ":")
|
||||
case "Date":
|
||||
post.Date = parts[1]
|
||||
case "ReadingTime":
|
||||
post.ReadingTime = parts[1]
|
||||
case "Sats":
|
||||
sats, err := strconv.Atoi(parts[1])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
post.Sats = int(sats)
|
||||
case "Comments":
|
||||
post.Comments = strings.Join(parts[1:], ":")
|
||||
}
|
||||
}
|
||||
err := sc.Err()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (post *MarkdownPost) InsertHeader(htmlContent *[]byte) {
|
||||
header := []byte("" +
|
||||
"<code class=\"bg-transparent\"><strong><pre class=\"bg-transparent text-center\">\n" +
|
||||
" _ _ \n" +
|
||||
"| |__ | | ___ __ _ \n" +
|
||||
"| '_ \\| |/ _ \\ / _` |\n" +
|
||||
"| |_) | | (_) | (_| |\n" +
|
||||
"|_.__/|_|\\___/ \\__, |\n" +
|
||||
" |___/ </pre></strong></code>\n" +
|
||||
"<div><div class=\"font-mono mb-1 text-center\">\n" +
|
||||
"<strong>{{- .Title }}</strong><br />\n" +
|
||||
"<small>{{- .Date }} | {{ .ReadingTime }} | <a target=\"_blank\" href=\"{{ .Comments }}\">Comments</a> | {{ .Sats }} sats</small>\n" +
|
||||
"</div>\n")
|
||||
*htmlContent = append(header, *htmlContent...)
|
||||
}
|
||||
|
||||
func (post *MarkdownPost) StyleHtml(htmlContent *[]byte) {
|
||||
doc, err := goquery.NewDocumentFromReader(bytes.NewReader(*htmlContent))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
doc.Find("img").Each(func(index int, element *goquery.Selection) {
|
||||
element.AddClass("flex m-auto")
|
||||
})
|
||||
doc.Find("pre, code").Each(func(index int, element *goquery.Selection) {
|
||||
element.AddClass("code")
|
||||
})
|
||||
doc.Find("code[class*=\"language-\"]").Each(func(index int, element *goquery.Selection) {
|
||||
SyntaxHighlighting(element)
|
||||
})
|
||||
htmlS, err := doc.Html()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
*htmlContent = []byte(htmlS)
|
||||
}
|
||||
|
||||
func GetPosts(srcDir string) *[]MarkdownPost {
|
||||
paths, err := filepath.Glob(srcDir + "*.md")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var posts []MarkdownPost
|
||||
for _, path := range paths {
|
||||
post := NewMarkdownPost(path)
|
||||
posts = append(posts, *post)
|
||||
}
|
||||
return &posts
|
||||
}
|
||||
|
||||
func (post *MarkdownPost) Render() {
|
||||
opts := html.RendererOptions{Flags: MarkdownToHtmlFlags}
|
||||
renderer := html.NewRenderer(opts)
|
||||
html := markdown.ToHTML(post.Content, nil, renderer)
|
||||
post.InsertHeader(&html)
|
||||
post.StyleHtml(&html)
|
||||
t, err := template.New("post").Parse(string(html))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
t.Execute(os.Stdout, *post)
|
||||
}
|
||||
|
||||
func RenderBlogIndex(srcDir string) {
|
||||
posts := GetPosts(srcDir)
|
||||
srcPath := srcDir + "index.html"
|
||||
t := template.New(filepath.Base(srcPath))
|
||||
t = t.Funcs(template.FuncMap{
|
||||
"ToHref": func(fsPath string) string {
|
||||
return "/" + strings.ReplaceAll(fsPath, ".md", "")
|
||||
},
|
||||
})
|
||||
t, err := t.ParseFiles(srcPath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = t.Execute(os.Stdout, map[string][]MarkdownPost{"Posts": *posts})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
proxy_cache_path /var/run/nginx-cache/jscache levels=1:2 keys_zone=jscache:100m inactive=30d use_temp_path=off max_size=100m;
|
||||
|
||||
server {
|
||||
server_name ekzyis.com;
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
|
||||
return 301 https://ekzyis.com$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
server_name ekzyis.com;
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/ekzyis.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/ekzyis.com/privkey.pem;
|
||||
|
||||
root /var/www/ekzyis;
|
||||
index index.html;
|
||||
try_files $uri.html $uri $uri/ =404;
|
||||
|
||||
error_page 404 /404.html;
|
||||
|
||||
resolver 9.9.9.9;
|
||||
set $plausible_script_url https://plausible.io/js/script.js;
|
||||
set $plausible_event_url https://plausible.io/api/event;
|
||||
|
||||
location = /js/script.js {
|
||||
proxy_pass $plausible_script_url;
|
||||
proxy_set_header Host plausible.io;
|
||||
|
||||
# Tiny, negligible performance improvement. Very optional.
|
||||
proxy_buffering on;
|
||||
|
||||
# Cache the script for 6 hours, as long as plausible.io returns a valid response
|
||||
proxy_cache jscache;
|
||||
proxy_cache_valid 200 6h;
|
||||
proxy_cache_use_stale updating error timeout invalid_header http_500;
|
||||
|
||||
# Optional. Adds a header to tell if you got a cache hit or miss
|
||||
add_header X-Cache $upstream_cache_status;
|
||||
}
|
||||
|
||||
location = /api/event {
|
||||
proxy_pass $plausible_event_url;
|
||||
proxy_set_header Host plausible.io;
|
||||
proxy_buffering on;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
}
|
||||
|
||||
include letsencrypt.conf;
|
||||
}
|
Before Width: | Height: | Size: 80 KiB |
Before Width: | Height: | Size: 449 KiB |
Before Width: | Height: | Size: 72 KiB |
Before Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 6.5 KiB |
Before Width: | Height: | Size: 9.9 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 51 KiB |
@ -1,27 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
||||
<channel>
|
||||
<atom:link href="https://ekzyis.com/blog/rss.xml" rel="self" type="application/rss+xml" />
|
||||
<title>blog | ekzyis</title>
|
||||
<link>https://ekzyis.com/blog</link>
|
||||
<description>Blog posts from ekzyis</description>
|
||||
<item>
|
||||
<guid>https://ekzyis.com/blog/20230809-demystifying-wireguard-and-iptables</guid>
|
||||
<title>Demystifying WireGuard and iptables</title>
|
||||
<link>https://ekzyis.com/blog/20230809-demystifying-wireguard-and-iptables</link>
|
||||
<comments>https://stacker.news/items/221471</comments>
|
||||
</item>
|
||||
<item>
|
||||
<guid>https://ekzyis.com/blog/20230821-wireguard-packet-forwarding</guid>
|
||||
<title>WireGuard Packet Forwarding</title>
|
||||
<link>https://ekzyis.com/blog/20230821-wireguard-packet-forwarding</link>
|
||||
<comments>https://stacker.news/items/230179</comments>
|
||||
</item>
|
||||
<item>
|
||||
<guid>https://ekzyis.com/blog/20230925-wireguard-port-forwarding</guid>
|
||||
<title>WireGuard Port Forwarding</title>
|
||||
<link>https://ekzyis.com/blog/20230925-wireguard-port-forwarding</link>
|
||||
<comments>https://stacker.news/items/265524</comments>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
@ -1,34 +0,0 @@
|
||||
/* desert scheme ported from vim to google prettify */
|
||||
pre.prettyprint { display: block; background-color: #333 }
|
||||
pre .nocode { background-color: none; color: #000 }
|
||||
pre .str { color: #ffa0a0 } /* string - pink */
|
||||
pre .kwd { color: #f0e68c; font-weight: bold }
|
||||
pre .com { color: #87ceeb } /* comment - skyblue */
|
||||
pre .typ { color: #98fb98 } /* type - lightgreen */
|
||||
pre .lit { color: #cd5c5c } /* literal - darkred */
|
||||
pre .pun { color: #fff } /* punctuation */
|
||||
pre .pln { color: #fff } /* plaintext */
|
||||
pre .tag { color: #f0e68c; font-weight: bold } /* html/xml tag - lightyellow */
|
||||
pre .atn { color: #bdb76b; font-weight: bold } /* attribute name - khaki */
|
||||
pre .atv { color: #ffa0a0 } /* attribute value - pink */
|
||||
pre .dec { color: #98fb98 } /* decimal - lightgreen */
|
||||
|
||||
/* Specify class=linenums on a pre to get line numbering */
|
||||
ol.linenums { margin-top: 0; margin-bottom: 0; color: #AEAEAE } /* IE indents via margin-left */
|
||||
li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8 { list-style-type: none }
|
||||
/* Alternate shading for lines */
|
||||
li.L1,li.L3,li.L5,li.L7,li.L9 { }
|
||||
|
||||
@media print {
|
||||
pre.prettyprint { background-color: none }
|
||||
pre .str, code .str { color: #060 }
|
||||
pre .kwd, code .kwd { color: #006; font-weight: bold }
|
||||
pre .com, code .com { color: #600; font-style: italic }
|
||||
pre .typ, code .typ { color: #404; font-weight: bold }
|
||||
pre .lit, code .lit { color: #044 }
|
||||
pre .pun, code .pun { color: #440 }
|
||||
pre .pln, code .pln { color: #000 }
|
||||
pre .tag, code .tag { color: #006; font-weight: bold }
|
||||
pre .atn, code .atn { color: #404 }
|
||||
pre .atv, code .atv { color: #060 }
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
/* Doxy pretty-printing styles. Used with prettify.js. */
|
||||
|
||||
pre .str, code .str { color: #fec243; } /* string - eggyolk gold */
|
||||
pre .kwd, code .kwd { color: #8470FF; } /* keyword - light slate blue */
|
||||
pre .com, code .com { color: #32cd32; font-style: italic; } /* comment - green */
|
||||
pre .typ, code .typ { color: #6ecbcc; } /* type - turq green */
|
||||
pre .lit, code .lit { color: #d06; } /* literal - cherry red */
|
||||
pre .pun, code .pun { color: #8B8970; } /* punctuation - lemon chiffon4 */
|
||||
pre .pln, code .pln { color: #f0f0f0; } /* plaintext - white */
|
||||
pre .tag, code .tag { color: #9c9cff; } /* html/xml tag (bluey) */
|
||||
pre .htm, code .htm { color: #dda0dd; } /* html tag light purply*/
|
||||
pre .xsl, code .xsl { color: #d0a0d0; } /* xslt tag light purply*/
|
||||
pre .atn, code .atn { color: #46eeee; font-weight: normal;} /* html/xml attribute name - lt turquoise */
|
||||
pre .atv, code .atv { color: #EEB4B4; } /* html/xml attribute value - rosy brown2 */
|
||||
pre .dec, code .dec { color: #3387CC; } /* decimal - blue */
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
pre.prettyprint, code.prettyprint {
|
||||
font-family:'Droid Sans Mono','CPMono_v07 Bold','Droid Sans';
|
||||
font-weight: bold;
|
||||
font-size: 9pt;
|
||||
background-color: #0f0f0f;
|
||||
-moz-border-radius: 8px;
|
||||
-webkit-border-radius: 8px;
|
||||
-o-border-radius: 8px;
|
||||
-ms-border-radius: 8px;
|
||||
-khtml-border-radius: 8px;
|
||||
border-radius: 8px;
|
||||
} /* background is black (well, just a tad less dark ) */
|
||||
|
||||
pre.prettyprint {
|
||||
width: 95%;
|
||||
margin: 1em auto;
|
||||
padding: 1em;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
pre.prettyprint a, code.prettyprint a {
|
||||
text-decoration:none;
|
||||
}
|
||||
/* Specify class=linenums on a pre to get line numbering; line numbers themselves are the same color as punctuation */
|
||||
ol.linenums { margin-top: 0; margin-bottom: 0; color: #8B8970; } /* IE indents via margin-left */
|
||||
li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8 { list-style-type: none }
|
||||
/* Alternate shading for lines */
|
||||
li.L1,li.L3,li.L5,li.L7,li.L9 { }
|
||||
|
||||
/* print is mostly unchanged from default at present */
|
||||
@media print {
|
||||
pre.prettyprint, code.prettyprint { background-color: #fff; }
|
||||
pre .str, code .str { color: #088; }
|
||||
pre .kwd, code .kwd { color: #006; font-weight: bold; }
|
||||
pre .com, code .com { color: #0c3; font-style: italic; }
|
||||
pre .typ, code .typ { color: #404; font-weight: bold; }
|
||||
pre .lit, code .lit { color: #044; }
|
||||
pre .pun, code .pun { color: #440; }
|
||||
pre .pln, code .pln { color: #000; }
|
||||
pre .tag, code .tag { color: #b66ff7; font-weight: bold; }
|
||||
pre .htm, code .htm { color: #606; font-weight: bold; }
|
||||
pre .xsl, code .xsl { color: #606; font-weight: bold; }
|
||||
pre .atn, code .atn { color: #c71585; font-weight: normal; }
|
||||
pre .atv, code .atv { color: #088; font-weight: normal; }
|
||||
}
|
Before Width: | Height: | Size: 799 B |
Before Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 15 KiB |
@ -1,16 +0,0 @@
|
||||
const scroll = (y) => window.scrollTo(0, 925*y)
|
||||
async function hotReload() {
|
||||
console.log("running in development mode")
|
||||
const r = await fetch("/hot-reload")
|
||||
let x = await r.text()
|
||||
setInterval(async () => {
|
||||
const r = await fetch("/hot-reload", {
|
||||
cache: "no-cache"
|
||||
})
|
||||
if (x !== await r.text()) {
|
||||
x = r.body
|
||||
window.location.reload()
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
hotReload().catch(console.error)
|
170
public/index.css
@ -1,170 +0,0 @@
|
||||
body {
|
||||
background-color: #091833;
|
||||
color: #ffffff;
|
||||
caret-color: transparent;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #8787a4;
|
||||
text-decoration: underline;
|
||||
width: fit-content;
|
||||
}
|
||||
a:hover {
|
||||
background: #8787A4;
|
||||
color: #ffffff;
|
||||
}
|
||||
a > code:hover {
|
||||
background-color: #8787A4;
|
||||
}
|
||||
a.header {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
a.subh {
|
||||
padding: 0 .3em;
|
||||
}
|
||||
a.header:hover {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
|
||||
nav > a {
|
||||
margin: 0 3px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
h2 {
|
||||
font-size: 1.3em;
|
||||
}
|
||||
h3 {
|
||||
font-size: 1.1em;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
hr {
|
||||
width: 100%;
|
||||
border: none;
|
||||
height: 1px;
|
||||
background: repeating-linear-gradient(90deg,#fff,#fff 6px,transparent 1px,transparent 8px);
|
||||
}
|
||||
|
||||
ul {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
pre {
|
||||
overflow-x: auto;
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
.code {
|
||||
background-color: #2b3148;
|
||||
}
|
||||
|
||||
.diff-remove {
|
||||
color: #ed88a5;
|
||||
}
|
||||
.diff-add {
|
||||
color: #a2de88;
|
||||
}
|
||||
|
||||
.container {
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
-ms-box-sizing: border-box;
|
||||
-o-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
margin: 1em auto;
|
||||
padding: 0 1em;
|
||||
max-width: min(60rem, 100vw);
|
||||
}
|
||||
|
||||
.block {
|
||||
display: block;
|
||||
}
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
.flex-column {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
.text-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.font-bold {
|
||||
font: bold;
|
||||
}
|
||||
.font-mono {
|
||||
font-family: "Lucida Console", "Courier New", monospace;
|
||||
}
|
||||
|
||||
.justify-center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.items-center {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.h-100vh {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.mx-1 {
|
||||
margin: auto 1em;
|
||||
}
|
||||
.m-auto {
|
||||
margin: auto;
|
||||
}
|
||||
.mb-1 {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
.mt-1 {
|
||||
margin-top: 1em;
|
||||
}
|
||||
.pt-1 {
|
||||
padding-top: 1em;
|
||||
}
|
||||
.pb-1 {
|
||||
padding-bottom: 1em;
|
||||
}
|
||||
|
||||
.bg-transparent {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
details > summary {
|
||||
list-style: none;
|
||||
color: #8787a4;
|
||||
text-decoration: underline;
|
||||
width: fit-content;
|
||||
cursor: pointer;
|
||||
}
|
||||
details > summary:hover {
|
||||
background: #8787A4;
|
||||
color: #ffffff;
|
||||
}
|
||||
details > summary > code:hover {
|
||||
background-color: #8787A4;
|
||||
}
|
||||
|
||||
details > summary::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
Version: Keybase OpenPGP v2.1.13
|
||||
Comment: https://keybase.io/ekzyis
|
||||
|
||||
xsFNBGXdXusBEACrWhcL8nU8Nz1osqBqnnqSX08YDniBltW9Y+hsmPxR3qjFyu3J
|
||||
rL7+j6OKvtMuxESbgoDbRK2zUY0vMCGV+hesRk2puZc6sGN3euwHHzTRRvKgoqV2
|
||||
NXO66CKQBfp6tKNGdwzgiqIjUeZC2viMogPhhCoyGuMKGfHOtLoNFjZ/gcMzDcSV
|
||||
yhkL2w3WcwgzZ4s5v85QkwLCYXUO70/Wnv8iVUNt4IXT1mD87ZpDvKZ1s/66I22l
|
||||
l65yDVhqpZBSN8eymz1NWht2siCXQMy5foyiIhQ/x8hwYP6TyvTtgSMzQ7RClwYU
|
||||
WVS30V6+MlErZKIYAJcNqCTQOyaklkhewVAyW717b/E/c9GH5+sM62fePDpuNl7f
|
||||
INHbFFp0rkiPJAL9HTISU9UVbe8Dpujy0z+yRU8TCqNZRfL5YH+X0EcGnd6soWEp
|
||||
tQHQqQbnVKnOJ/vFW0wBbi0N6SfQnScnMMOUgDkvg6My1YrJKqnEtJG89OpmAvjb
|
||||
LWnSGyBCcJh5KAAUYqNkDYtSm5fyhQkQIiD/c00KCAs/w/+1YEHJN3OmFADtxm76
|
||||
m/XT+JBLkCKhKacR8RYxs/YkuVtlhMT7qzBAu8Q4zvwKwkUWPGmwWaxkmYBb9cfo
|
||||
KEKzfvrzrB2F98AFN1H6ruWOAXpqtQ3Uu8mcmTF7YNYt1L53z9uk/OdKRwARAQAB
|
||||
zRpla3p5aXMgPGVrenlpc0Bla3p5aXMuY29tPsLBdAQTAQoAHgUCZd1e6wIbAwML
|
||||
CQcDFQoIAh4BAheAAxYCAQIZAQAKCRDrr3XacnnLSCvGD/9QuSd9QhvW8eHsyjDO
|
||||
EK2r06VcftnBAmG4YAEIcq//5rm426AqS1WjjCRJOd/Jho+ovLAljS3eScn/axaE
|
||||
t74preuZslQALpoNDw7h/Il9B5IoOyoMhkYFT51Y8b11ciXZisUOaFOeljwogxax
|
||||
20C+EillF0qhOcQzvGXWrLOuKmO42qUyGEdhrUeV6lrjHanevoTw6PASwk/rplQW
|
||||
zgq7/bUePsOwBg3oqq8v1U2uNUU02EJsC/QgMfDRFaiN3ukdplJ7obqoQjB6l6sJ
|
||||
JRA7VNTZCJOq4q3YpJCxQVIMa8F5QXAUwY89bZmsXJ88fQ1ajL88JpXHgqnVLlHD
|
||||
lzYZRNCZyt1EfsMAvCx0BhJ37vZ8FbcDKnf3WvEj1vrCiWFd/QdlIbyK1tuTHLQV
|
||||
xuDQN/yZnjVSQQbIYVHyRdiLqzPfV5LQa0Htad5XXKY0tlJOAYYfn9z5jHkh/9F9
|
||||
SxMkUykXYBwfrSUkpGYenlwMfiKERssbtutBpSPyjaeKzcq+t6HhqDPFEkoyh6wh
|
||||
FaVJP2/BjEMb75Uw0xQttgG8mWMoowldcl+aUav4t2Zx4HcdHQH8NxTQM7tefbxF
|
||||
s+VdXz0EaIwX18eC9yuLT42gCKiC8PldziYzztVcodgNOVvLhPxgQ4RjCi/A1Gxv
|
||||
H6DxLBS0ZDKXDOsH31BUi+nG6c7ATQRl3V7rAQgAzfN7sOogTHybBrzJ6b3C/cpD
|
||||
UtKrdB8OSFmbalOza1j9fq4UaHrpcrWC6HTPdWzKYhLE7ewUPDy4YoZYccVkFVgo
|
||||
QbybTTxNRrYkIsLEPaO/w27cGaaTpy3T+beqThnFwxSy7Ul7av+R92CHvZdeFbLK
|
||||
zXjU17bAhh7ePkZtPxRFCXrb30cA/YvbPgkWRiSE+Hvc8DnKlA5a9+dHu/n62yEk
|
||||
h7xopvX7rOYBEgsBoTgwSanP4CNQ2ZN1cdAOqLfnezCM9CjwZ6u97mDx/DroydbV
|
||||
TGFmjPO3gdCwzIAT/vbVeUx4Sdzj+u0wp05k/QvHvraveRbQHeL4AqOpV2p+jQAR
|
||||
AQABwsKEBBgBCgAPBQJl3V7rBQkPCZwAAhsMASkJEOuvddpyectIwF0gBBkBCgAG
|
||||
BQJl3V7rAAoJEJutZGn8n9QvM3sH/3iyOUZWpEWyH4mBYuZ/5cVjG/rEdTUsappv
|
||||
wJr8pmpLN2E32sCLNy/hfMiVFHw4GAgNv2z/wXEcZJqyH0QJtKg18alS0R245KFO
|
||||
MLX4ZuwrwIn0DtnXQ8meaUBZMQx4OGDyOOxlnWtt9YkZRkT41RMkriYkdFXLicDM
|
||||
/FpxBTk1DSmsKwbhBaXSmRDTHUA0z0LPdicqR1cPlfcSQlLJlhxmy+ef+oBiC8ZN
|
||||
L43lOHfjh6V0+d1WcGKuI2JO0ca8ZPmpcAs/Wg27BOlhWMpudAsEhjYFApXQpVik
|
||||
ai0RLIu0pU/lDuK4xcZyecpv3IFPyJGBzLzBBm9Rf9k6q3+ej7DaPA/+IojAk60Z
|
||||
TqLKvBftGqM7P3GkVFl3M2dvpZaOBjXNI7PbyYL7CuTAa9srspJEJ7vPI3BcgkR6
|
||||
DjEX4Xwouf3rKDtXPj+kyXUpJWD3fVjCjdgfcII2ClkJ6LAawfm4CBS2p1QKdENF
|
||||
wo3x8I16Sj8n2ulk/k9xGLmuX/dOdFp8vsthPTBDlv9P6pjCllWLV/HPdhl+mLcr
|
||||
ciNG9JvJDIBKxbC1XxcoDz4/rl6XBfxIry5yOShtjmQxFoq4qOMjVs9APOpOptc8
|
||||
8A8FT/9QY45eHlJGChXqeQ7tUovwZE7PLtdqMVT8krAForJItKn7b7+1z4sTtAIp
|
||||
FqGlNJKm8sWv30zCvUHZCxyphMb3gcRu6K8JgITZGNf2nzrLWlgar910OMyi1YkO
|
||||
a+SsVqiuNp/e/ST9ykX1bdCq8tIq3iTfZB+mjtT5SXJOJGaric02vd4HlMzuZjUc
|
||||
rJTUGqr3YPVPnld3MHGyu/JaJkD6pLjogFRQCu0k2flasiEzR3hgeJ8PFbrsdH+f
|
||||
aucL1KQHu8xCLsgckKP8gCsc8njA5gJ73vfWhJ0ABR7mNa8tfWvVZqPP9P55Uyw6
|
||||
Yj6VFlccWFPE0Df6io35V9LOg/yB/WE9xC+63+ju9mDJW61XLtBIxjF4SFmPAYeL
|
||||
rkvfGAWrfvmHWewRTS0nBjapIWrx4ZmSRA7OwE0EZd1e6wEIAKrsNuZqHN4JE1MZ
|
||||
2lyHR/zHnBMSqNSGhUUF0rYWXWu5D4ErRfUqDhLkrxcpSiIgh4pCXWfdwSUbg7MY
|
||||
neOpH4KDFNRDZZ9+3gPO8ZpNSrcdDFtuJcoR/bI1JQihcvJOCAI7Ijg8caMtMeNM
|
||||
8SRccJz32yX9mg8S9x+aFRjwC9VyJ20qagVDVp0W2AVPbWe+gWIP74ybgHwxJAwK
|
||||
LSqZ0mlhCFi/BJec5DYqPoR478Y5ovkAk2Y7dFnH1Ic8cvtTp6C6R6SPhbQ9WVgQ
|
||||
5MkbFtGBPjvVmx2T5BKr18m6cGA9k9atA+YVVOK/6cWKO+xLs9b5DKMuaP6zKhum
|
||||
QR2s3DMAEQEAAcLChAQYAQoADwUCZd1e6wUJDwmcAAIbIgEpCRDrr3XacnnLSMBd
|
||||
IAQZAQoABgUCZd1e6wAKCRDlEMJtiaaJVs4gB/0SOdKOGw3dIW6U2nQOS9+VauPx
|
||||
GAK6nzZjWpBt1hvcCrF7cJXWNeAenQ6xThk5G5bz81Qd3OZzykm3vWNkeJnMaU1Y
|
||||
7ZfsbO4y1OsS+iTE2FeBa7o4yCKdIoL8XUVINNaLL8EgsMudH9UxVpQUi26CPpZm
|
||||
MhyXrFnTAaBFYIHGK0u18CepjaHEdaINlqFR2VndJpheb/HungmEPTVPld3J0B/e
|
||||
g6wPwqg+tQo+dn3mNP9ZDy/tI+jcH/AcbdS86g7iyIhBBhjFpE/x5j7xlPCeJ0pz
|
||||
PGt/zHZnjX6t6/L8lYUO4FATIv1teXsWl9bpp2czO4tZwyZVGXOjqfdEPsVHArEQ
|
||||
AJXvJVhlZMjlquTdmNbiRNucuypvoqoYqURKQIo6U3LuM031e2jiDKvFydfmZQTD
|
||||
okw0xXLptSvT+jwSZFNOojUHDjS0dxmvRSqPr5hEEGHfc6FL6OmWUAGYu+LyFc/U
|
||||
Z/gmeJbhH/ChsR0MTE7HeBGWCr1OjQcXxw2MDJ2C324Cex7k6z1LbOUUyesiwWfL
|
||||
QSVVO1bgwFggkn7QOgUwIgzMfO4VRifPcBKiGyP8p2uzv3lhlzjFqnX4qskuuFdR
|
||||
IY6hEX6fkbnUg1WCRUx31kQsgt+6PVc4dxiLfoJo3FOC8Z0yRAGGrfj7FnoOuQLC
|
||||
uED5OU6a9gXk71xuSFGxpuxO87sl0Wp1m0oVdxE9RCtdsc9ASkvUtMsJTYv3iAi8
|
||||
iML9kHRczICwreeawoLQKnjVGEv6tN+uLV5XEfkqjluZLc5YHpbwUhSv9rA1RP5U
|
||||
ie6ifYf2YQ8Th5VkME2XUtdPhrXVC+YcXdCsN4MM8zlHn2t9jYIx6+OePC6mymSj
|
||||
+M153nHkH9S67NaD9PAouM/6B50WAH77LQQr4puwMuWp75FzcCx01NC08/Hi2YtC
|
||||
I+9x1jsYc2xT6ou3CdRKlNdQY2s/CbEU2XlPLdIV1NkqAZ6GLhAeWKfPrat1ZOEj
|
||||
QfYcasovMcZL3OicNiKq4cikcAAf5VRwbs+JmXe5ukkV
|
||||
=Ankv
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
Before Width: | Height: | Size: 204 KiB |
@ -1 +0,0 @@
|
||||
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
|
@ -1,118 +0,0 @@
|
||||
/*
|
||||
* Derived from einaros's Sons of Obsidian theme at
|
||||
* http://studiostyl.es/schemes/son-of-obsidian by
|
||||
* Alex Ford of CodeTunnel:
|
||||
* http://CodeTunnel.com/blog/post/71/google-code-prettify-obsidian-theme
|
||||
*/
|
||||
|
||||
.str
|
||||
{
|
||||
color: #EC7600;
|
||||
}
|
||||
.kwd
|
||||
{
|
||||
color: #93C763;
|
||||
}
|
||||
.com
|
||||
{
|
||||
color: #66747B;
|
||||
}
|
||||
.typ
|
||||
{
|
||||
color: #678CB1;
|
||||
}
|
||||
.lit
|
||||
{
|
||||
color: #FACD22;
|
||||
}
|
||||
.pun
|
||||
{
|
||||
color: #F1F2F3;
|
||||
}
|
||||
.pln
|
||||
{
|
||||
color: #F1F2F3;
|
||||
}
|
||||
.tag
|
||||
{
|
||||
color: #8AC763;
|
||||
}
|
||||
.atn
|
||||
{
|
||||
color: #E0E2E4;
|
||||
}
|
||||
.atv
|
||||
{
|
||||
color: #EC7600;
|
||||
}
|
||||
.dec
|
||||
{
|
||||
color: purple;
|
||||
}
|
||||
pre.prettyprint
|
||||
{
|
||||
border: 0px solid #888;
|
||||
}
|
||||
ol.linenums
|
||||
{
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.prettyprint {
|
||||
background: #000;
|
||||
}
|
||||
li.L0, li.L1, li.L2, li.L3, li.L4, li.L5, li.L6, li.L7, li.L8, li.L9
|
||||
{
|
||||
color: #555;
|
||||
list-style-type: decimal;
|
||||
}
|
||||
li.L1, li.L3, li.L5, li.L7, li.L9 {
|
||||
background: #111;
|
||||
}
|
||||
@media print
|
||||
{
|
||||
.str
|
||||
{
|
||||
color: #060;
|
||||
}
|
||||
.kwd
|
||||
{
|
||||
color: #006;
|
||||
font-weight: bold;
|
||||
}
|
||||
.com
|
||||
{
|
||||
color: #600;
|
||||
font-style: italic;
|
||||
}
|
||||
.typ
|
||||
{
|
||||
color: #404;
|
||||
font-weight: bold;
|
||||
}
|
||||
.lit
|
||||
{
|
||||
color: #044;
|
||||
}
|
||||
.pun
|
||||
{
|
||||
color: #440;
|
||||
}
|
||||
.pln
|
||||
{
|
||||
color: #000;
|
||||
}
|
||||
.tag
|
||||
{
|
||||
color: #006;
|
||||
font-weight: bold;
|
||||
}
|
||||
.atn
|
||||
{
|
||||
color: #404;
|
||||
}
|
||||
.atv
|
||||
{
|
||||
color: #060;
|
||||
}
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
/* Pretty printing styles. Used with prettify.js. */
|
||||
/* Vim sunburst theme by David Leibovic */
|
||||
|
||||
pre .str, code .str { color: #65B042; } /* string - green */
|
||||
pre .kwd, code .kwd { color: #E28964; } /* keyword - dark pink */
|
||||
pre .com, code .com { color: #AEAEAE; font-style: italic; } /* comment - gray */
|
||||
pre .typ, code .typ { color: #89bdff; } /* type - light blue */
|
||||
pre .lit, code .lit { color: #3387CC; } /* literal - blue */
|
||||
pre .pun, code .pun { color: #fff; } /* punctuation - white */
|
||||
pre .pln, code .pln { color: #fff; } /* plaintext - white */
|
||||
pre .tag, code .tag { color: #89bdff; } /* html/xml tag - light blue */
|
||||
pre .atn, code .atn { color: #bdb76b; } /* html/xml attribute name - khaki */
|
||||
pre .atv, code .atv { color: #65B042; } /* html/xml attribute value - green */
|
||||
pre .dec, code .dec { color: #3387CC; } /* decimal - blue */
|
||||
|
||||
pre.prettyprint, code.prettyprint {
|
||||
background-color: #000;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
pre.prettyprint {
|
||||
width: 95%;
|
||||
margin: 1em auto;
|
||||
padding: 1em;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
|
||||
/* Specify class=linenums on a pre to get line numbering */
|
||||
ol.linenums { margin-top: 0; margin-bottom: 0; color: #AEAEAE; } /* IE indents via margin-left */
|
||||
li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8 { list-style-type: none }
|
||||
/* Alternate shading for lines */
|
||||
li.L1,li.L3,li.L5,li.L7,li.L9 { }
|
||||
|
||||
@media print {
|
||||
pre .str, code .str { color: #060; }
|
||||
pre .kwd, code .kwd { color: #006; font-weight: bold; }
|
||||
pre .com, code .com { color: #600; font-style: italic; }
|
||||
pre .typ, code .typ { color: #404; font-weight: bold; }
|
||||
pre .lit, code .lit { color: #044; }
|
||||
pre .pun, code .pun { color: #440; }
|
||||
pre .pln, code .pln { color: #000; }
|
||||
pre .tag, code .tag { color: #006; font-weight: bold; }
|
||||
pre .atn, code .atn { color: #404; }
|
||||
pre .atv, code .atv { color: #060; }
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
XXXXXXXXXXXXXXXXXX
|
||||
XXX Network XXX
|
||||
XXXXXXXXXXXXXXXXXX
|
||||
+
|
||||
|
|
||||
v
|
||||
+-------------+ +------------------+
|
||||
|table: filter| <---+ | table: nat |
|
||||
|chain: INPUT | | | chain: PREROUTING|
|
||||
+-----+-------+ | +--------+---------+
|
||||
| | |
|
||||
v | v
|
||||
[local process] | **************** +--------------+
|
||||
| +---------+ Routing decision +------> |table: filter |
|
||||
v **************** |chain: FORWARD|
|
||||
**************** +------+-------+
|
||||
Routing decision |
|
||||
**************** |
|
||||
| |
|
||||
v **************** |
|
||||
+-------------+ +------> Routing decision <---------------+
|
||||
|table: nat | | ****************
|
||||
|chain: OUTPUT| | +
|
||||
+-----+-------+ | |
|
||||
| | v
|
||||
v | +-------------------+
|
||||
+--------------+ | | table: nat |
|
||||
|table: filter | +----+ | chain: POSTROUTING|
|
||||
|chain: OUTPUT | +--------+----------+
|
||||
+--------------+ |
|
||||
v
|
||||
XXXXXXXXXXXXXXXXXX
|
||||
XXX Network XXX
|
||||
XXXXXXXXXXXXXXXXXX
|
@ -1,11 +0,0 @@
|
||||
+~~~~~~~~~~~~~~~~~~~~~~~~+ +~~~~~~~~~~~~~~~~~~~~~~~~~~+ +~~~~~~~~~~~~~~~~~~~~~~~~~~+
|
||||
| SRC: 10.172.16.1:46541 | SNAT | SRC: 54.147.66.132:56140 | DNAT | SRC: 54.147.66.132:56140 |
|
||||
| DST: 10.172.16.2:8000 |<---->| DST: 10.172.16.2:8000 |<---->| DST: 93.184.216.34:8000 |
|
||||
+~~~~~~~~~~~~~~~~~~~~~~~~+ +~~~~~~~~~~~~~~~~~~~~~~~~~~+ +~~~~~~~~~~~~~~~~~~~~~~~~~~+
|
||||
| |
|
||||
+---+ +-------+
|
||||
| |<----------------------------------------->| . . . |<-----------------------------------------> INTERNET
|
||||
+---+ +-------+
|
||||
10.172.16.2 NAT gateway
|
||||
private IP address: 10.172.16.1
|
||||
public IP address: 93.184.216.34
|
@ -1,10 +0,0 @@
|
||||
+~~~~~~~~~~~~~~~~~~~~~~~~~+ +~~~~~~~~~~~~~~~~~~~~~~~~~~+
|
||||
| SRC: 54.147.66.132:443 | | SRC: 54.147.66.132:443 |
|
||||
| DST: 192.168.0.14:56781 | | DST: 93.184.216.34:41501 |
|
||||
+~~~~~~~~~~~~~~~~~~~~~~~~~+ | | +~~~~~~~~~~~~~~~~~~~~~~~~~~+
|
||||
+---+ packet +-------+
|
||||
| |<--------------------------------| . . . |<-------------------------------- INTERNET
|
||||
+---+ +-------+
|
||||
192.168.0.14 NAT gateway
|
||||
private IP address: 192.168.0.1
|
||||
public IP address: 93.184.216.34
|
@ -1,10 +0,0 @@
|
||||
+~~~~~~~~~~~~~~~~~~~~~~~~~+ +~~~~~~~~~~~~~~~~~~~~~~~~~~+
|
||||
| SRC: 192.168.0.14:56781 | | SRC: 93.184.216.34:41501 |
|
||||
| DST: 54.147.66.132:443 | | DST: 54.147.66.132:443 |
|
||||
+~~~~~~~~~~~~~~~~~~~~~~~~~+ | | +~~~~~~~~~~~~~~~~~~~~~~~~~~+
|
||||
+---+ packet +-------+
|
||||
| |-------------------------------->| . . . |--------------------------------> INTERNET
|
||||
+---+ +-------+
|
||||
192.168.0.14 NAT gateway
|
||||
private IP address: 192.168.0.1
|
||||
public IP address: 93.184.216.34
|
@ -1,3 +0,0 @@
|
||||
| Source IP address | Source Port | NAT IP address | NAT port | Destination IP address | Destination Port |
|
||||
| ----------------- | ----------- | -------------- | -------- | ---------------------- | ---------------- |
|
||||
| 192.168.0.14 | 56781 | 93.184.216.34 | 41501 | 54.147.66.132 | 443 |
|
@ -1,4 +0,0 @@
|
||||
+---+ +---+
|
||||
| |<------->| |
|
||||
+---+ +---+
|
||||
10.172.16.1 10.172.16.2
|
@ -1,19 +0,0 @@
|
||||
+---+ +---+ +---+
|
||||
| | | | | |
|
||||
+---+ +---+ +---+
|
||||
\ | /
|
||||
\ | /
|
||||
\ | /
|
||||
\ | /
|
||||
\ | /
|
||||
+---+ +~~~~~~~~~~~~+ +---+
|
||||
| |-----| router |-----| |
|
||||
+---+ +~~~~~~~~~~~~+ +---+
|
||||
/ 10.172.16.1 \
|
||||
/ | \
|
||||
/ | \
|
||||
/ | \
|
||||
/ | \
|
||||
+---+ +---+ +---+
|
||||
| | | | | |
|
||||
+---+ +---+ +---+
|
@ -1,26 +0,0 @@
|
||||
+-----------------------+ +---------------------+
|
||||
| ping process | | |
|
||||
| v | <-- local machine boundary remote machine boundary --> | |
|
||||
| +----------------+ | | +----------------+ |
|
||||
| | interface: wg0 | | | | interface: wg0 | |
|
||||
| +----------------+ | | +----------------+ |
|
||||
| | | | ^ |
|
||||
| v | | | |
|
||||
| +----------+ | | +----------+ |
|
||||
| | x ICMP x | <-------------- encrypted ICMP packet leaving/entering wg0 ----------> | x ICMP x | |
|
||||
| +----------+ | (virtual network interface) | +----------+ |
|
||||
| | | | ^ |
|
||||
| | | UDP packet leaving enp3s0 | | |
|
||||
| | | +------- (physical network interface) | | |
|
||||
| v | | which wraps ICMP packet | | |
|
||||
| +-------------------+ | v | +-----------------+ |
|
||||
| | interface: enp3s0 | | +------------+ +------------+ | | interface: eth0 | |
|
||||
| +-------------------+ | | UDP | | UDP | | +-----------------+ |
|
||||
+---------|-------------+ |+----------+| |+----------+| +----------^----------+
|
||||
| || x ICMP x || +~~~~~~~~~~+ || x ICMP x || |
|
||||
+----------------->|+----------+|----->| INTERNET |----->|+----------+|--------------+
|
||||
+------------+ +~~~~~~~~~~+ +------------+
|
||||
^
|
||||
UDP packet entering eth0 |
|
||||
(physical network interface) -----------+
|
||||
which wraps ICMP packet
|
50
renderer.go
@ -1,50 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"path"
|
||||
|
||||
"github.com/namsral/flag"
|
||||
)
|
||||
|
||||
var (
|
||||
Source string
|
||||
Env string
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&Source, "src", "", "Source file")
|
||||
flag.StringVar(&Env, "env", "development", "Specify for which environment files should be built")
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
func RenderExtension(path string, ext string) {
|
||||
switch ext {
|
||||
case ".md":
|
||||
NewMarkdownPost(path).Render()
|
||||
case ".html":
|
||||
NewHtmlSource(path).Render()
|
||||
default:
|
||||
log.Fatalf("unknown extension: %s", ext)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
if Source == "" {
|
||||
log.Fatal("no source given")
|
||||
}
|
||||
|
||||
if Source == "blog/index.html" {
|
||||
RenderBlogIndex("blog/")
|
||||
return
|
||||
}
|
||||
|
||||
if Source != "" {
|
||||
ext := path.Ext(Source)
|
||||
if ext == "" {
|
||||
log.Fatal("file has no extension")
|
||||
}
|
||||
RenderExtension(Source, ext)
|
||||
return
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 399 KiB |
BIN
static/favicon.ico
Normal file
After Width: | Height: | Size: 15 KiB |
17
sync-dev.sh
@ -1,17 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
function sync() {
|
||||
ENV=development make render $@
|
||||
date +%s.%N > public/hot-reload
|
||||
rsync -avhP public/ dev.ekzyis.com:/var/www/dev.ekzyis --delete
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
rm -f public/hot-reload
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
sync -B
|
||||
while inotifywait -r -e modify html/ blog/ *.go; do
|
||||
sync
|
||||
done
|
34
syntax.go
@ -1,34 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/sourcegraph/syntaxhighlight"
|
||||
)
|
||||
|
||||
func SyntaxHighlighting(element *goquery.Selection) {
|
||||
if element.HasClass("language-diff") {
|
||||
// syntaxhighlight does not support diff so we run our custom code in that case
|
||||
text := strings.Split(element.Text(), "\n")
|
||||
p1 := regexp.MustCompile(`^\+ `)
|
||||
p2 := regexp.MustCompile(`^- `)
|
||||
for i, line := range text {
|
||||
if p1.MatchString(line) {
|
||||
text[i] = fmt.Sprintf("<span class=\"diff-add\">%s</span>", line)
|
||||
}
|
||||
if p2.MatchString(line) {
|
||||
text[i] = fmt.Sprintf("<span class=\"diff-remove\">%s</span>", line)
|
||||
}
|
||||
}
|
||||
element.SetHtml(strings.Join(text, "\n"))
|
||||
return
|
||||
}
|
||||
formatted, err := syntaxhighlight.AsHTML([]byte(element.Text()))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
element.SetHtml(string(formatted))
|
||||
}
|
1
themes/holy
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 55f1291be9fa61867658306a103c0ea38803e7f8
|