Zero-To-Awall: Difference between revisions

From Alpine Linux
m (→‎Custom services: Rescued dead link.)
 
(72 intermediate revisions by 5 users not shown)
Line 1: Line 1:
= Awall howto for dummies =
= Awall for dummies =


This howto is aimed at users with no (or little) experience with iptables and other firewall frameworks (like Shorewall).
This howto is aimed at users with no (or little) experience with iptables and other firewall frameworks (like Shorewall).


This howto is going to be split into 4 parts.
This howto is going to be split into 5 parts.


# Defining our base json file which holds our zones and base policies.
# Defining our base json file which holds our zones and base policies.
# Creating service policies.
# Creating service policies.
# Using aliases and custom services.
# Using aliases and custom services.
# enabling and testing policies.
# Enabling and testing policies.
# Finishing up and making it start (at boot)


NOTE: please be aware that all configuration files are stored as JSON files. JSON is not a human friendly standard,  
NOTE: please be aware that all configuration files are stored as JSON files. JSON is not a human friendly standard,  
for instance it does not support comments so you will have to move them outside of the json structure.
for instance it does not support comments so you will have to move them outside of the json structure.
Beginners should use a decent text editor with JSON highlight support which will make your life easier.
Beginners should use a decent text editor with JSON highlight support which will make your life easier.
Since recent versions of awall it is also possible to use yaml instead of json but this is out of the scope of this howto.
Since recent versions of [[Alpine Wall|awall]] it is also possible to use yaml instead of json but this is out of the scope of this howto.


== Creating base ==
== Base policies ==


Creating zones depends on the function of your firewall. Is it installed on a endpoint (server) or will it act as a router and filter/forward.
Creating zones depends on the function of your firewall. Is it installed on a endpoint (server) or will it act as a router and filter/forward.
Line 57: Line 58:


This is where our zones are defined. Zones are defined based on a interface and assigned a name to be used in your policies.
This is where our zones are defined. Zones are defined based on a interface and assigned a name to be used in your policies.
In our example you can see that we have two real interfaces eth0 and eth1 and one or more virtual interfaces tun+ (the plus sign stands for any digit like tun0 tun1 and so on). In case you are installing awall on an endpoint (a server) then you will most probably not have the eth1 interfaces and can leave it out.
In our example you can see that we have two real interfaces eth0 and eth1 and one or more virtual interfaces tun+ (the plus sign stands for any digit like tun0 tun1 and so on). In case you are installing awall on an endpoint (a server) then you will most probably not have the eth1 interfaces and can leave it out. In our example the tun+ interface is added as it is very commonly used like when using openvpn.


=== policy ===
=== policy ===


These are our main policies. It will tell our firewall what to do with when a packet enters or leaves on of the zones (interfaces).
These are our main policies. It will tell our firewall what to do with when a packet enters or leaves from one of the zones (interfaces).
You will notice a special _fw name which means the internal firewall itself (the local machine) which means the packet does not leave the firewall via another interface but should use one of the local services.
You will notice a special <code>_fw</code> name which means the internal firewall (the local machine) which means the packet does not leave the firewall via another interface but should be send to one of the local services.
You can see that we by default do not filter any package coming from or going to our VPN zone/interface. You could instead change the default action to drop all packets and create separate policies to allow specific traffic but this is out of the scope of this howto.


=== snat ===
=== snat ===
Line 71: Line 73:


https://github.com/alpinelinux/awall#mss-clamping-rules
https://github.com/alpinelinux/awall#mss-clamping-rules
== Service policies ==
Now that we have the base firewall in place we can start to define specific policies so our services will be reachable from the outside world.\
By default we are blocking all traffic coming in on our WAN interface (action=drop). The first thing we want to open is our SSH port/service. To do this we need to create a new policy inside the "optional" directory.
You could be wondering why the optional name, thats is because mandatory policies are stored in <code>/usr/share/awall/mandatory</code> and not to be touched and our optional policies can be enabled/disabled on the run.
=== SSH service ===
To add our SSH policies we create a new file: /etc/awall/optional/ssh.json
<pre>
{
  "description": "Allow rate-limited SSH on WAN",
  "filter": [
    {
      "in": "WAN",
      "out": "_fw",
      "service": "ssh",
      "action": "accept",
      "conn-limit": { "count": 3, "interval": 20 }
    }
  ]
}
</pre>
==== description ====
This is similar for any policy
==== Filter ====
This is the actual filter that is currently set to drop the packets arriving or leaving the interface.
===== in =====
The interface the packets arrive on, in this case its the WAN interface.
===== out =====
The interface the packets leave on, in this case its _fw which means it does not leave our firewall/device and is targeted at our local SSH service.
===== service =====
This is the service definition provided by awall or a custom service which we will discuss later on.
===== action =====
The action on the packet, this inverts the default action of drop and accepts the packets.
===== conn-limit =====
This is a special feature of our firewall/iptables to allow only a certain amount of packets in a certain amount of time. For more information please check our awall manual.
=== SSH to another Host ===
edit the following file: /etc/awall/optional/ssh-to-hostname.json
<pre>
{
  "description": "Forward SSH to hostname",
  "filter": [
    {
      "in": "WAN",
      "service": { "proto": "tcp", "port": 22001 },
      "action": "accept",
      "conn-limit": { "count": 3, "interval": 20 }
    }
  ],
  "dnat": [
    {
      "in": "WAN",
      "dest": "$SERVER",
      "service": { "proto": "tcp", "port": 22001 },
      "to-port": "22"
    }
  ]
}
</pre>
Lets discuss the differences between this policy and the previous SSH policy.
==== Filter ====
===== service =====
Because port 22 is already in use by our own firewall, we need to listen on a different port. In this example we listen on port 22001.
And because we are not using the default port 22 we need to define our own service specification.
==== dnat ====
Also known as destination NAT.
===== dest =====
The destination the packet will be forwarded to. In this case we are using a variable named $HOSTNAME. Anywhere in your policies you can define your own variables and use them.
In our case we have used a file in /etc/awall/private/aliases.json more on this topic later on.
===== to-port =====
This is the destination target port number. The packet will be forwarded from 22001 to 22 on the $hostname
=== OpenVPN Service ===
This is the most generic config available. It does nothing more then opening port(s) defined for our openvpn service in {{path|/etc/awall/private/custom-services.json}}
<pre>
{
    "description": "Allow local OpenVPN",
    "filter": [
        {
            "in": "WAN",
            "out": "_fw",
            "service": "openvpn",
            "action": "accept"
        }
    ]
}
</pre>
=== Allow ping on WAN ===
Allow rate-limited ping on WAN. Which has the same kind of flow limit as our previous SSH policy.
<pre>
{
  "description": "Allow rate-limited ping on WAN",
  "filter": [
    {
      "in": "WAN",
      "out": "_fw",
      "service": "ping",
      "action": "accept",
      "flow-limit": { "count": 10, "interval": 6 }
    }
  ]
}
</pre>
== Using aliases and custom services ==
=== Aliases ===
To make life easier when your firewall rules increase, it can be nice to map specific hosts to names.
Awall supports something called [https://github.com/alpinelinux/awall#variable-expansion variable expansion] which is a mapping between a value and a variable.
When you have many devices behind your firewall/router, your policies can be harder to read. Also when one of your devices IP address change you will have to update all of your policies.
With awalls variables you can assign the ip address of a device to a variable name. Edit the following file: <code>/etc/awall/private/aliases.json</code>
<pre>
{
  "description": "Hostname aliases",
  "variable": {
    "PRINTER": "192.168.1.1",
    "SERVER": "192.168.1.2"
  }
}
</pre>
Look in the example above where $SERVER is used to forward port 22001 to port 22.
NOTE: You are not limited to assigning only IP addresses to variables. You can use it however you like. More information can be found in the awall manual.
=== Custom services ===
Awall includes a predefined list of [https://raw.githubusercontent.com/alpinelinux/awall/master/mandatory/services.json services]. If the service you try to define in your policy does not exist in awalls services list you can define services yourself.
Create the file: {{path|/etc/awall/private/custom-services.json}}
<pre>
{
    "service": {
        "mqtt": [
            { "proto": "udp", "port": 1883 },
            { "proto": "tcp", "port": 1883 }
        ],
        "openvpn": [
            { "proto": "udp", "port": 1194 },
            { "proto": "tcp", "port": 1194 }
        ]
    }
}
</pre>
NOTE: although you are free to name your policy files however you want, you cannot name this file <code>services.json</code> because this policy name is already in use by the included services.json of awall.
== Using our policies ==
You should now have two directories in your awall config directory named optional and private with multiple json files. The biggest difference between these two directories is the ability to enable and disable policies located in the optional directory. When you enable a policy by using <code>awall enable policy-name</code> awall will generate a symlink in your awall config directory and will automatically load them when you activate the firewall. To be able to also use the files in the private directory we will need to include them in one of our optional policies. You can name the file however you like as long it doesn't conflict with existing policies names (including the ones in private directory and awall's system policies). Example names would be hostname.json main.json firewall.json. For this example we will use main.json.
Create the file: <code>/etc/awall/optional/main.json</code>
<pre>
{
  "description": "Main firewall",
  "import": [ "base", "aliases", "custom-services" ]
}
</pre>
Contents of your awall directory:
<pre>
awall
├── optional
│   ├── main.json
│  ├── openvpn.json
│   ├── ssh-to-hostname.json
│   └── ssh.json
└── private
   ├── aliases.json
   ├── base.json
   └── custom-services.json
</pre>
=== Enabling optional policies ===
Lets enable our created policies. First we list them by running <code>awall list</code> which would show something like:
<pre>
openvpn        disabled  Allow local OpenVPN
main            disabled  Main firewall
ping            disabled  Allow rate-limited ping on WAN
ssh            disabled  Allow rate-limited SSH on WAN
ssh-to-hostname disabled  Forward SSH to hostname
</pre>
Each of these needs to be enabled:
<pre>
awall enable openvpn
awall enable main
awall enable ping
awall enable ssh
awall enable ssh-to-hostname
</pre>
The contents of your awall directory should now look like:
<pre>
awall/
├── main.json -> ./optional/main.json
├── openvpn.json -> ./optional/openvpn.json
├── optional
│   ├── main.json
│   ├── openvpn.json
│   ├── ping.json
│   ├── ssh-to-hostname.json
│   └── ssh.json
├── ping.json -> ./optional/ping.json
├── private
│   ├── aliases.json
│   ├── base.json
│   └── custom-services.json
├── ssh-to-hostname.json -> ./optional/ssh-to-hostname.json
└── ssh.json -> ./optional/ssh.json
2 directories, 13 files
</pre>
=== Testing policies ===
<code>awall translate --verify</code>
if everything goes well the output should be null.
=== Activating the firewall ===
Now that all our policies are verified for proper json we can activate it.
<code>awall activate</code>
This will load the firewall rules and show you a message to confirm. If by accident you made a mistake and lock yourself out you just have to wait for awall to disable itself again.
== Finishing up ==
=== Activating firewall rules at boot ===
When awall has been properly activated it will generate a file with all iptables rules which iptables will read when its is started via openrc.
Make sure you have added iptables to an openrc runlevel. If using snat, also enable ipset for the masquerade set.
<code>rc-update add iptables</code>
<code>rc-update add ipset</code>
=== Allow IPv4 forwarding ===
To allow iptables to forward packets from one zone to the other we need to enable this at the iptables level.
==== On the fly ====
To enable it on the fly:
<code>sysctl -w net.ipv4.ip_forward=1</code>
==== Enable within iptables tools (at boot) ====
Add the following to:
<code>/etc/conf.d/iptables</code>
<pre>
# Enable/disable IPv4 forwarding with the rules
IPFORWARD="yes"
</pre>
= See also =
* [[How-To Alpine Wall]]
[[Category:Security]]

Latest revision as of 04:59, 26 August 2023

Awall for dummies

This howto is aimed at users with no (or little) experience with iptables and other firewall frameworks (like Shorewall).

This howto is going to be split into 5 parts.

  1. Defining our base json file which holds our zones and base policies.
  2. Creating service policies.
  3. Using aliases and custom services.
  4. Enabling and testing policies.
  5. Finishing up and making it start (at boot)

NOTE: please be aware that all configuration files are stored as JSON files. JSON is not a human friendly standard, for instance it does not support comments so you will have to move them outside of the json structure. Beginners should use a decent text editor with JSON highlight support which will make your life easier. Since recent versions of awall it is also possible to use yaml instead of json but this is out of the scope of this howto.

Base policies

Creating zones depends on the function of your firewall. Is it installed on a endpoint (server) or will it act as a router and filter/forward. For this howto we assume you are going to setup a router and use NAT to forward services (ports) to different hosts on your network.

For each interface on router we will setup a zone and assign it a zone name. We do this by creating the following file: /etc/awall/private/base.json

{
  "description": "Base zones and policies",

  "zone": {
    "WAN": { "iface": "eth0" },
    "LAN": { "iface": "eth1" },
    "VPN": { "iface": "tun+" }
  },

  "policy": [
     { "in": "VPN", "action": "accept" },
     { "out": "VPN", "action": "accept" },
     { "in": "LAN", "action": "accept" },
     { "out": "LAN", "action": "accept" },
     { "in": "_fw", "action": "accept" },
     { "in": "_fw", "out":  "WAN" , "action": "accept" },
     { "in": "WAN", "action": "drop" }
  ],

  "snat": [ { "out": "WAN" } ],

  "clamp-mss": [ { "out": "WAN" } ]

}

Lets break this down into sections

description

The description is here just for reference and will be used by awall list.

zone

This is where our zones are defined. Zones are defined based on a interface and assigned a name to be used in your policies. In our example you can see that we have two real interfaces eth0 and eth1 and one or more virtual interfaces tun+ (the plus sign stands for any digit like tun0 tun1 and so on). In case you are installing awall on an endpoint (a server) then you will most probably not have the eth1 interfaces and can leave it out. In our example the tun+ interface is added as it is very commonly used like when using openvpn.

policy

These are our main policies. It will tell our firewall what to do with when a packet enters or leaves from one of the zones (interfaces). You will notice a special _fw name which means the internal firewall (the local machine) which means the packet does not leave the firewall via another interface but should be send to one of the local services. You can see that we by default do not filter any package coming from or going to our VPN zone/interface. You could instead change the default action to drop all packets and create separate policies to allow specific traffic but this is out of the scope of this howto.

snat

Apply source nat for outgoing packets. This is only needed if your firewall acts as a router and traffic behind the router needs a modified source address (translate from local ip to public ip).

clamp-mss

https://github.com/alpinelinux/awall#mss-clamping-rules

Service policies

Now that we have the base firewall in place we can start to define specific policies so our services will be reachable from the outside world.\ By default we are blocking all traffic coming in on our WAN interface (action=drop). The first thing we want to open is our SSH port/service. To do this we need to create a new policy inside the "optional" directory. You could be wondering why the optional name, thats is because mandatory policies are stored in /usr/share/awall/mandatory and not to be touched and our optional policies can be enabled/disabled on the run.

SSH service

To add our SSH policies we create a new file: /etc/awall/optional/ssh.json

{
  "description": "Allow rate-limited SSH on WAN",

  "filter": [
    {
      "in": "WAN",
      "out": "_fw",
      "service": "ssh",
      "action": "accept",
      "conn-limit": { "count": 3, "interval": 20 }
    }
  ]
}

description

This is similar for any policy

Filter

This is the actual filter that is currently set to drop the packets arriving or leaving the interface.

in

The interface the packets arrive on, in this case its the WAN interface.

out

The interface the packets leave on, in this case its _fw which means it does not leave our firewall/device and is targeted at our local SSH service.

service

This is the service definition provided by awall or a custom service which we will discuss later on.

action

The action on the packet, this inverts the default action of drop and accepts the packets.

conn-limit

This is a special feature of our firewall/iptables to allow only a certain amount of packets in a certain amount of time. For more information please check our awall manual.

SSH to another Host

edit the following file: /etc/awall/optional/ssh-to-hostname.json

{

  "description": "Forward SSH to hostname",

  "filter": [
    {
      "in": "WAN",
      "service": { "proto": "tcp", "port": 22001 },
      "action": "accept",
      "conn-limit": { "count": 3, "interval": 20 }
    }
  ],

  "dnat": [
    {
      "in": "WAN",
      "dest": "$SERVER",
      "service": { "proto": "tcp", "port": 22001 },
      "to-port": "22"
    }
  ]
}

Lets discuss the differences between this policy and the previous SSH policy.

Filter

service

Because port 22 is already in use by our own firewall, we need to listen on a different port. In this example we listen on port 22001. And because we are not using the default port 22 we need to define our own service specification.

dnat

Also known as destination NAT.

dest

The destination the packet will be forwarded to. In this case we are using a variable named $HOSTNAME. Anywhere in your policies you can define your own variables and use them. In our case we have used a file in /etc/awall/private/aliases.json more on this topic later on.

to-port

This is the destination target port number. The packet will be forwarded from 22001 to 22 on the $hostname

OpenVPN Service

This is the most generic config available. It does nothing more then opening port(s) defined for our openvpn service in /etc/awall/private/custom-services.json

{

    "description": "Allow local OpenVPN",

    "filter": [
        {
            "in": "WAN",
            "out": "_fw",
            "service": "openvpn",
            "action": "accept"
        }
    ]
}

Allow ping on WAN

Allow rate-limited ping on WAN. Which has the same kind of flow limit as our previous SSH policy.

{

  "description": "Allow rate-limited ping on WAN",

  "filter": [
    {
      "in": "WAN",
      "out": "_fw",
      "service": "ping",
      "action": "accept",
      "flow-limit": { "count": 10, "interval": 6 }
    }
  ]
}

Using aliases and custom services

Aliases

To make life easier when your firewall rules increase, it can be nice to map specific hosts to names. Awall supports something called variable expansion which is a mapping between a value and a variable. When you have many devices behind your firewall/router, your policies can be harder to read. Also when one of your devices IP address change you will have to update all of your policies. With awalls variables you can assign the ip address of a device to a variable name. Edit the following file: /etc/awall/private/aliases.json

{
  "description": "Hostname aliases",

  "variable": {
    "PRINTER": "192.168.1.1",
    "SERVER": "192.168.1.2"
  }

}

Look in the example above where $SERVER is used to forward port 22001 to port 22.

NOTE: You are not limited to assigning only IP addresses to variables. You can use it however you like. More information can be found in the awall manual.

Custom services

Awall includes a predefined list of services. If the service you try to define in your policy does not exist in awalls services list you can define services yourself.

Create the file: /etc/awall/private/custom-services.json

{
    "service": {

        "mqtt": [
            { "proto": "udp", "port": 1883 },
            { "proto": "tcp", "port": 1883 }
        ],

        "openvpn": [
            { "proto": "udp", "port": 1194 },
            { "proto": "tcp", "port": 1194 }
        ]

    }
}

NOTE: although you are free to name your policy files however you want, you cannot name this file services.json because this policy name is already in use by the included services.json of awall.

Using our policies

You should now have two directories in your awall config directory named optional and private with multiple json files. The biggest difference between these two directories is the ability to enable and disable policies located in the optional directory. When you enable a policy by using awall enable policy-name awall will generate a symlink in your awall config directory and will automatically load them when you activate the firewall. To be able to also use the files in the private directory we will need to include them in one of our optional policies. You can name the file however you like as long it doesn't conflict with existing policies names (including the ones in private directory and awall's system policies). Example names would be hostname.json main.json firewall.json. For this example we will use main.json.

Create the file: /etc/awall/optional/main.json

{
  "description": "Main firewall",

  "import": [ "base", "aliases", "custom-services" ]

}

Contents of your awall directory:

awall
│
├── optional
│   ├── main.json
│   ├── openvpn.json
│   ├── ssh-to-hostname.json
│   └── ssh.json
└── private
    ├── aliases.json
    ├── base.json
    └── custom-services.json

Enabling optional policies

Lets enable our created policies. First we list them by running awall list which would show something like:

openvpn         disabled  Allow local OpenVPN
main            disabled  Main firewall
ping            disabled  Allow rate-limited ping on WAN
ssh             disabled  Allow rate-limited SSH on WAN
ssh-to-hostname disabled  Forward SSH to hostname

Each of these needs to be enabled:

awall enable openvpn
awall enable main
awall enable ping
awall enable ssh
awall enable ssh-to-hostname

The contents of your awall directory should now look like:

awall/
├── main.json -> ./optional/main.json
├── openvpn.json -> ./optional/openvpn.json
├── optional
│   ├── main.json
│   ├── openvpn.json
│   ├── ping.json
│   ├── ssh-to-hostname.json
│   └── ssh.json
├── ping.json -> ./optional/ping.json
├── private
│   ├── aliases.json
│   ├── base.json
│   └── custom-services.json
├── ssh-to-hostname.json -> ./optional/ssh-to-hostname.json
└── ssh.json -> ./optional/ssh.json

2 directories, 13 files

Testing policies

awall translate --verify

if everything goes well the output should be null.

Activating the firewall

Now that all our policies are verified for proper json we can activate it.

awall activate

This will load the firewall rules and show you a message to confirm. If by accident you made a mistake and lock yourself out you just have to wait for awall to disable itself again.

Finishing up

Activating firewall rules at boot

When awall has been properly activated it will generate a file with all iptables rules which iptables will read when its is started via openrc. Make sure you have added iptables to an openrc runlevel. If using snat, also enable ipset for the masquerade set.

rc-update add iptables

rc-update add ipset

Allow IPv4 forwarding

To allow iptables to forward packets from one zone to the other we need to enable this at the iptables level.

On the fly

To enable it on the fly: sysctl -w net.ipv4.ip_forward=1

Enable within iptables tools (at boot)

Add the following to: /etc/conf.d/iptables

# Enable/disable IPv4 forwarding with the rules
IPFORWARD="yes"


See also