Showing posts with label iptables. Show all posts
Showing posts with label iptables. Show all posts

Friday, April 29, 2016

My Introduction to firewallD

WARNING:

  1. When I say My Introduction, I do not mean I am trying to introduce you to firewallD. I really mean this is the first time I had to properly deal with it. I wish this story would involve robots, but it is not that exciting

  2. You should not follow this blindly as a guide. I screwed it up and documented it here. Read it first and see what I did wrong and why.

So I am building an apache webserver on a Linux box (Red Hat Enterprise Server 7.2 if you are curious, but there is nothing stopping the gist of my story working on any Linux distro using systemd and, more specifically, firewallD) and want to limit the number of hosts that can access it to one, which happens to have 192.168.79.13 as its IP.

In the golden days...

If we were to use dear ol' pedestrian iptables, one way to create the rules would be

iptables -I INPUT -s '192.168.79.13/32' -m state --state NEW \
   -m tcp -p tcp --dport 80 -j ACCEPT
iptables -I INPUT -s '192.168.79.13/32' -m state --state NEW \
   -m tcp -p tcp --dport 443 -j ACCEPT

and then check if it has been loaded using

iptables -L -n

At this point we might have decided we really should have also told it to apply the rule to only a specific interface or IP (we might be listening to many IPs on the same interface). So we would adjust it as needed until we are satisfied. And, once satisfied it works, save it using

service iptables save

Modern Times

So far so good. But in Red Hat 7 and CentOS 7 as a result, iptables save is no longer an available command. firewallD should be used instead, which is amusing since it is but a front end to iptables. I guess the point is that if iptables is replaced you would still be able to use firewallD to control whatever comes next; in other words, it is an abstraction layer.

But, I digress. Bottom line is we need to figure out how to use firewallD. First thing is that we will be using firewall-cmd to talk to it. Next is that it allows to create separate firewall zones (same idea as the Windows firewall zones for the Microsoft crowd amongst you) which define the interface and possibly IPs it is associated with and which services and rules are supposed to do what there. And, yes, that last sentence was a mouthful, so we shall begin by seeing which zone we are using.

[user@webtest httpd]$ sudo firewall-cmd --get-default-zone
public
[user@webtest httpd]$

I guess that makes sense since this is a pretty much out-of-the-box install; we are still customizing it. But, which zones are in use?

[user@webtest httpd]$ sudo firewall-cmd --get-active-zones
public
  interfaces: eth0
[user@webtest httpd]$

So, the only active zone is the default one, which is being applied to eth0. What can you tell me about the public zone?

[user@webtest httpd]$ sudo firewall-cmd --zone=public --list-all
public (default, active)
  interfaces: eth0
  sources:
  services: dhcpv6-client ssh
  ports:
  masquerade: no
  forward-ports:
  icmp-blocks:
  rich rules:

[user@webtest httpd]$

I knew about ssh service (sshd) -- that is how I am connected to it -- but I did not realize it actually has a dhcpv6 client. How about ports in use?

[user@webtest httpd]$ sudo firewall-cmd --zone=public --list-ports
[user@webtest httpd]$

Hmmm, nothing? But sshd uses port 22 by default (which is how it is set right now); shouldn't it show up? Well, like Windows' firewall you can specify a service or just a port. If you do the former, its port is not listed. I wonder if it is just satisfied the ssh service is on and relies on it to know which port it is using; this sounds like something worthwhile to test later. Talking about services, which ones firewallD knows of, be them running or not?

[user@webtest httpd]$ sudo firewall-cmd --get-services
RH-Satellite-6 amanda-client bacula bacula-client dhcp dhcpv6 dhcpv6-client 
dns freeipa-ldap freeipa-ldaps freeipa-replication ftp high-availability http 
https imaps ipp ipp-client ipsec iscsi-target kerberos kpasswd ldap ldaps 
libvirt libvirt-tls mdns mountd ms-wbt mysql nfs ntp openvpn pmcd pmproxy 
pmwebapi pmwebapis pop3s postgresql proxy-dhcp radius rpc-bind rsyncd samba 
samba-client smtp ssh telnet tftp tftp-client transmission-client vdsm 
vnc-server wbem-https
[user@webtest httpd]$

It even knows about telnet! Not that it is even installed here, which means firewallD has some kind of configuration file somewhere listing all the services it should be aware of. Which means you can add/subtract services to this list... or it ends up overwritten during an upgrade. One of the two, right?

So we created the firewall zone. How does it look like? It is actually a xml file; here's the one for the public zone as it stands right now in this conversation:

[user@webtest httpd]$ sudo cat /etc/firewalld/zones/public.xml
<?xml version="1.0" encoding="utf-8"?>
<zone>
  <short>Public</short>
  <description>For use in public areas. You do not trust the other 
computers on networks to not harm your computer. Only selected incoming 
connections are accepted.</description>
  <service name="dhcpv6-client"/>
  <service name="ssh"/>
</zone>
[user@webtest httpd]$

I think we have an idea of the beast.

Houston, we have a problem

So, let's say we want to allow access to our webserver on port 80. We can do something like

firewall-cmd --zone=public --add-port=80/tcp --permanent

or

firewall-cmd --zone=public --add-service=http --permanent

right? Er, not quite. Let me show what I mean: after we run one of those commands (I picked the service one) we should always check if the zone was updated.

[user@webtest httpd]$ sudo firewall-cmd --zone=public --list-all
public (default, active)
  interfaces: eth0
  sources:
  services: dhcpv6-client ssh
  ports:
  masquerade: no
  forward-ports:
  icmp-blocks:
  rich rules:

[user@webtest httpd]$

But yet, it is in the file!

[user@webtest httpd]$ sudo cat /etc/firewalld/zones/public.xml
<?xml version="1.0" encoding="utf-8"?>
<zone>
  <short>Public</short>
  <description>For use in public areas. You do not trust the other 
computers on networks to not harm your computer. Only selected incoming 
connections are accepted.</description>
  <service name="dhcpv6-client"/>
  <service name="ssh"/>
  <service name="http"/>
</zone>
[user@webtest httpd]$

What is going on? Short version: user error. Long version: --permanent means save to file only. It does not affect the running version. If you want to turn it on, you need to restart firewalld.

[user@webtest httpd]$ sudo systemctl restart firewalld
[user@webtest httpd]$

Another alternative is not to use --permanent, as in

firewall-cmd --zone=public --add-service=http

That will commit the change immediately. If you reboot it is lost, so it is a good way to test it. And then once you are ready to commit, you can then do

firewall-cmd --runtime-to-permanent

which as the name implies saves the running firewall to the appropriate zone files. In any case, here is the outcome:

[user@webtest httpd]$ sudo firewall-cmd --zone=public --list-all
public (default, active)
  interfaces: eth0
  sources:
  services: dhcpv6-client httpd ssh
  ports:
  masquerade: no
  forward-ports:
  icmp-blocks:
  rich rules:

[user@webtest httpd]$

If you want to remove that very rule, you can do it by

firewall-cmd --zone=public --remove-service=httpd

Back to the iptables example

Do you remember it? No? Let me put it here

iptables -I INPUT -s '192.168.79.13/32' -m state --state NEW \
   -m tcp -p tcp --dport 80 -j ACCEPT
iptables -I INPUT -s '192.168.79.13/32' -m state --state NEW \
   -m tcp -p tcp --dport 443 -j ACCEPT

The key here is not we are specifying the ports, but that we want to specify the IP traffic is coming from. In the firewalld universe that requires a rich rule. Here is what I am using:

firewall-cmd --zone=public --add-rich-rule="rule family="ipv4" \
    source address="192.168.79.13/32" service name="http" accept"
firewall-cmd --zone=public --add-rich-rule="rule family="ipv4" \
    source address="192.168.79.13/32" service name="https" accept"

it should behave the very same way as the iptables entries. But, let's see how it looks like in runtime

[user@webtest httpd]$ sudo firewall-cmd --zone=public --list-all
public (default, active)
  interfaces: eth0
  sources:
  services: dhcpv6-client ssh
  ports:
  masquerade: no
  forward-ports:
  icmp-blocks:
  rich rules:
        rule family="ipv4" source address="192.168.79.13/32" service 
name="https" accept
        rule family="ipv4" source address="192.168.79.13/32" service 
name="http" accept
[user@webtest httpd]$

And, after we are satisfied and committed it, in the zone file

[user@webtest httpd]$ sudo cat /etc/firewalld/zones/public.xml
<?xml version="1.0" encoding="utf-8"?>
<zone>
  <short>Public</short>
  <description>For use in public areas. You do not trust the other 
computers on networks to not harm your computer. Only selected incoming 
connections are accepted.</description>
  <service name="dhcpv6-client"/>
  <service name="ssh"/>
  <rule family="ipv4">
    <source address="192.168.79.13/32"/>
    <service name="https"/>
    <accept/>
  </rule>
  <rule family="ipv4">
    <source address="192.168.79.13/32"/>
    <service name="http"/>
    <accept/>
  </rule>
</zone>
[user@webtest httpd]$

References

Thursday, January 23, 2014

Internal server touching itself from behind another servers/router's external IP

Before I start, I have to say I saw this happening using virtual machines where the network 192.168.1.0/24 was also virtualized, but not when both machines were physical ones. Your situation might be different. YMMV, use with care, this side up, rauchen verbotten. Now that's out, let's say we have one of the following networks:

 {Internet}                     OR       {LAN}
   |                                       |
   | 1.2.3.4 (external IP)                 | 1.2.3.4 (LAN IP)
 [ Router ]                            [ serverA ]
   | 192.168.1.1                           | 192.168.1.1
   |                                       |
   |                                       |
   | 192.168.1.10                          | 192.168.1.10
 [ serverB ]                           [ serverB ]

It is really the same thing for all practical purposes: one machine in an internal network behind another. In any case, we want to reach port 1234 in the internal machine (serverB) from the network outside the router or serverA. So, we can go to the router/serverA do a simple port forwarding iptables rules using DNAT:

iptables -t nat -a PREROUTING -d 1.2.3.4 -p tcp -m tcp --dport 1234 -j DNAT --to-destinaton 192.128.1.10:1234

But, let's say serverB also wants to connect to the same port? Well, the easiest way it to connect to localhost:1224, but what if we want to run the same script that external machines run, which then connect to 1.2.3.4:1234? It might not be what we want:

raub@serverB:~$ nc -v localhost 1234
Connection to localhost 1234 port [tcp/https] succeeded!
^C
raub@serverB:~$ nc -v 1.2.3.4 1234
nc: connect to 1.2.3.4 port 1234 (tcp) failed: Connection timed out
raub@serverB:~$ 

In broad strokes, here is what is happening: ServerB want to connect to 1.2.3.4, a non-local address, so it sends the packet to it's gateway (router or serverA in the diagram). Gateway then assembles its response, which gets put through router/serverA's routing table. Now serverA looks at the IP and realizes that it's a local one, so it sends the response directly to serverB. But serverB is expecting the response from the outside IP, so it does not connect (topplewagon in #centos wrote a better explanation). The solution is mentioned in the session Destination NAT Onto the Same Network of the Linux 2.4 NAT howto: create a SNAT rule:

iptables -t nat -A POSTROUTING -d 192.168.1.10 -s 192.168.1.0/24 -j SNAT --to-source 192.168.1.1

Tuesday, August 27, 2013

Fail2ban and RedHat/CentOS

Fail2ban is another neat intrusion detection program. It monitors log files for suspicious access attempts and, once it has enough of that, edits the firewall to block the offender. The really neat part is that it will unban the offending IP later on (you define how long); that usually will suffice to your garden variety automatic port scanner/dictionary attack but also would give hope to your user who just can't remember a password. There are other programs out there that will deal with ssh attacks, but fail2ban will handle many different services; I myself use it with Asterisk, mail, and web just to name a few.

But, you did not come here to hear me babbling; let's get busy and do some installing, shall we?

Installing fail2ban in RedHat/CentOS

For this example I will be using CentOS 6. YMMV.

  1. Get required packages. Need jwhois (for whois) from base and fail2ban from, say, epel or your favourite repository
    yum install jwhois fail2ban --enablerepo=epel

    whois is needed by /etc/fail2ban/action.d/sendmail-whois.conf, which is called
    by /etc/fail2ban/filter.d/sshd.conf.

    You will also need ssmtp or some kind of MTA so fail2ban can let you know that it caught a sneaky bastard. I briefly mentioned about ssmtp in a previous post; seek and thou shalt find.

  2. Configure fail2ban.
    1. Disable everything in /etc/fail2ban/jail.conf. We'll be using /etc/fail2ban/jail.local:
      sed -i -e 's/^enabled.*/enabled  = false/' /etc/fail2ban/jail.conf
    2. Configure /etc/fail2ban/jail.local. For now, we will just have ssh enabled
      HOSTNAME=`hostname -f`
      cat > /etc/fail2ban/jail.local << EOF
      # Fail2Ban jail.local configuration file.
      #
      
      [DEFAULT]
      actionban = iptables -I fail2ban- 1 -s  -m comment --comment "FAIL2BAN temporary ban" -j DROP
      
      # Destination email address used solely for the interpolations in
      # jail.{conf,local} configuration files.
      destemail = raub@kudria.com
      
      # This will ignore connection coming from our networks.
      # Note that local connections can come from other than just 127.0.0.1, so
      # this needs CIDR range too.
      ignoreip = 127.0.0.0/8 $(dig +short $HOSTNAME)
      
      #
      # ACTIONS
      #
      # action = %(action_mwl)s
      
      #
      # JAILS
      #
      [ssh]
      enabled = true
      port    = ssh
      filter  = sshd
      action   = iptables[name=SSH, port=ssh, protocol=tcp]
                 sendmail-whois[name=SSH, dest="%(destemail)s", sender=fail2ban@$HOSTNAME]
      logpath  = /var/log/secure
      maxretry = 5
      bantime = 28800
      EOF
      Note we are only whitelisting the host itself. You could whitelist your lan
      and other machines/networks if you want. Jail is a fail2ban term that defines a ruleset you want to check for, and ban as needed.
    3. Decide where you want fail2ban to log to. That is done in /etc/fail2ban/fail2ban.local using the logtarget variable. Some possible values could be
      cat > /etc/fail2ban/fail2ban.local << EOF
      [Definition]
      # logtarget = SYSLOG
      logtarget = /var/log/fail2ban.log
      EOF
      The file /etc/fail2ban/fail2ban.conf should provide you with examples on how to set that up.
  3. Enable fail2ban
    service fail2ban restart
    chkconfig fail2ban on
    If you now do
    chkconfig --list fail2ban
    you should then see
    fail2ban        0:off   1:off   2:on    3:on    4:on    5:on    6:off
    And then check the fail2ban log you defined just before for any funny business. If you have set it correctly, you should see an email to destemail saying fail2ban started. Now, you will get one email per jail. So, if you just did the default (ssh), you will get one email that looks like this:

    Hi,
    
    The jail SSH has been started successfully.
    
    Regards,
    
    Fail2Ban.

    When fail2ban bans someone, you will receive an email that looks like this:

    Hi,
    
    The IP 82.205.21.200 has just been banned by Fail2Ban after
    3 attempts against ASTERISK.
    
    
    Here are more information about 82.205.21.200:
    
    % This is the RIPE Database query service.
    % The objects are in RPSL format.
    %
    % The RIPE Database is subject to Terms and Conditions.
    % See http://www.ripe.net/db/support/db-terms-conditions.pdf
    
    % Note: this output has been filtered.
    %       To receive output for a database update, use the "-B" flag.
    
    % Information related to '82.205.16.0 - 82.205.31.255'
    [...]

    Note that it is not the SSH jail but the ASTERISk one; I just want to show a
    different example. Also, the stuff before the banned message is from whois.

    If you do iptables -L, you will see which rule fail2ban added to iptables:

    Chain fail2ban-SSH (1 references)
    target     prot opt source               destination
    DROP       all  --  221.178.164.251      anywhere
    RETURN     all  --  anywhere             anywhere

    Note it creates a chain for each jail.

References