Home Automate UFW with Nginx and TCP wrapper
Post
Cancel

Automate UFW with Nginx and TCP wrapper

The why?

This is a shell script that I do use quite frequently, since it’s been run in a cron job to check for host lookup ip changes (utilizing dynamic DNS). This saves users from being locked out if their home ip address changes, also helps keep a stronger firewall.

This basic example will allow two people to access SSH and HTTPS ports.. This works so long as those two people are allowed access on those ports, so this script would probably need modified to accomodate, or just use seperate scripts for groups of individuals.

The only section of this script to adjust are the two arrays, one for ports and the other is clients, at the top of the script. If you want to add more, just follow the same format with ports and clients. Comment out or delete sections with nginx or hosts.allow if not required.

If using hosts.deny, this script will accomdate a hosts.allow, but obviously this could be modified also. If you want to use this TCP wrapper, put a ‘sshd: ALL’ or something along those lines which suits your needs in your hosts.deny.

If using the nginx side, place this key in the server block of your nginx config. #SED-MATCH-LEAVE I could have used a substitute option for sed up to a point, but at least with this method, it will help with the set up of a newly created config or new user. Also if using the nginx config like me, and you don’t need the world wide web on your server, place a deny all; in the server block underneath the allowed ip’s.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
#!/bin/bash
#allow a dyndns name through your firewall. The idea is to set up a crontab
#to look up the dns hostname so you always have access to the server, via
#your port and protocol of choice. I use something like this to keep a VPN
#tunnel open to a server.

#for manual entry, syntax in terminal for UFW....
#./script.sh ip port proto, i.e.
#./script.sh 123.12.123.12 22 tcp

declare -A ports
# Update ports to cycle through your firewall
ports[ssh]=12345
ports[https]=443
#ports[]=

declare -A clients
# Check hosts associated with client
clients[jamie]=$(host homeIPaddress1.ddns.net | cut -f4 -d' ')
clients[holly]=$(host homeIPaddress2.ddns.net | cut -f4 -d' ')
#clients[]=

# nginx config file
nginx_conf='/etc/nginx/conf.d/web.conf'

# Check if any ufw rules
if [[ $(sudo ufw status | wc -l) == 1 ]]
then
  append=True
fi

mkdir -p "$HOME"/logs

timelog="$HOME"/logs/dyndnstime.log
now=$(printf '%(%d-%m-%Y %H:%M:%S)T\n')

function sed-nginx {
  # Update an nginx config if you have one? Test on a dummy nginx config beforehand.
  # You might also want to change a subnet instead, replace or leave out entirely
  # An example of append (Placing that comment '#SED-MATCH-LEAVE' somewhere in your nginx config file (server block))
  # I'm using \\\t to get a tab space in the conf, might need adjusted
  sudo sed -i "/#SED-MATCH-LEAVE/a \\\tallow ${clients[$client]};" "$nginx_conf"
}

# adds new UFW firewall rules
function add-new-rules {
  for port in "${ports[@]}"; do
    if [[ $append == True ]]; then
      sudo ufw allow from "${clients[$client]}" to any port "$port" proto tcp
    else
      # The reason for inserting new rules, is to keep them above any rate limiting rules which might interfere with the users. I've chosen 1 to keep above all rules.
      sudo ufw insert 1 allow from "${clients[$client]}" to any port "$port" proto tcp
    fi
  done
}

# If no commandline options...
if [[ -z "$1" ]]
then
  for client in "${!clients[@]}"
  do
    # If no ip found from host, continue to next host, otherwise hosts.allow may fill with junk
    if [[ ! ${clients[$client]} =~ [0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3} ]]; then
      echo "No ip found from ${client}, moving to next client"
      continue
    fi
    logfile="${HOME}/logs/${client}.ip.log"
    if [[ ! -f "$logfile" ]]
    then
      # data will be stored in the users $HOME directory and not the root directory.
      # add new UFW rules
      add-new-rules

      # Update an nginx config if you have one? Test on a dummy nginx config beforehand.
      # Remove or comment out line below if you don't need this
      sed-nginx

      # hosts allow file update
      echo "ALL: ${clients[$client]} # Home" | sudo tee -a /etc/hosts.allow
      echo "${clients[$client]}" > "$logfile"
      echo "New host time log created" > "$timelog"
    else
      old_ip=$(< "$logfile")
      if [[ ${clients[$client]} == "$old_ip" ]]
      then
        echo "$client IP address has not changed"
        echo "$now : $client : log running every hour, no changes" >> "$timelog"
      else
        # Remove old IPs from client
        num_rules=$(sudo ufw status numbered | grep -c "$old_ip")
        for (( a=1; a<=num_rules; a++  )); do
          first_rule=$(sudo ufw status numbered | grep "$old_ip" | sed -En "1s/.*([0-9]+)\].*/\1/p")
          echo "y" | sudo ufw delete "$first_rule"
        done
        # Clean up hosts.allow file
        sudo sed -i "/$old_ip/d" /etc/hosts.allow
        # Clean up nginx conf
        sudo sed -i "/$old_ip/d" "$nginx_conf"
        
        # Add new rules
        add-new-rules

        # Update an nginx config if you have one? Test on a dummy nginx config beforehand. You might change to many ip addresses with this command.
        # Remove line below if you don't need this
        sed-nginx

        # update hosts allow file
        echo "ALL: ${clients[$client]} # Home" | sudo tee -a /etc/hosts.allow
        echo "${clients[$client]}" > "$logfile"
        echo "$client IP has been updated"
        echo "$now : $client New IP updated" >> "$timelog"
      fi
    fi
  done
else
    # Sense checking the commandline entries
    if [[ ! $1 =~ [0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3} ]]; then
      echo "$1 is not a valid ip address"
      exit 1
    fi
    if [[ ! $2 =~ [0-9]{1,5} ]]; then
      echo "$2 is not a valid port number"
      exit 1
    fi
    if [[ ! $3 =~ ^tcp$|^udp$ ]]; then
      echo "$3 is not a valid protocol, use tcp or udp"
      exit 1
    fi 
    if [[ $append == True ]]; then
      sudo ufw allow from "$1" to any port "$2" proto "$3"
    else
      sudo ufw insert 1 allow from "$1" to any port "$2" proto "$3"
    fi
    echo "ALL: $1 # Home" | sudo tee -a /etc/hosts.allow
    # Update an nginx config if you have one? Test on a dummy nginx config beforehand.
    # Remove line below if you don't need this
    # An example of append (Placing that comment '#SED-MATCH-LEAVE' somewhere in your nginx config file (server block))
    # I'm using \\\t to get a tab space in the conf, might need adjusted
    sudo sed -i "/#SED-MATCH-LEAVE/a \\\tallow $1;" "$nginx_conf"
  fi
This post is licensed under CC BY 4.0 by the author.