Create an iptables firewall using custom chains that will be used to control incoming and outgoing traffic.

Create an iptables firewall that will allow already established connections, incoming ssh for given source addresses, outgoing icmp, ntp, dns, ssh, http, and https.

# Flush rules and delete custom chains
iptables -F
iptables -X

# Define chain to allow particular source addresses
iptables -N chain-incoming-ssh
iptables -A chain-incoming-ssh -s 192.168.1.148 -j ACCEPT
iptables -A chain-incoming-ssh -s 192.168.1.149 -j ACCEPT
iptables -A chain-incoming-ssh -j DROP

# Define chain to allow particular services
iptables -N chain-outgoing-services
iptables -A chain-outgoing-services -p tcp --dport 53  -j ACCEPT
iptables -A chain-outgoing-services -p udp --dport 53  -j ACCEPT
iptables -A chain-outgoing-services -p tcp --dport 123 -j ACCEPT
iptables -A chain-outgoing-services -p udp --dport 123 -j ACCEPT
iptables -A chain-outgoing-services -p tcp --dport 80  -j ACCEPT
iptables -A chain-outgoing-services -p tcp --dport 443 -j ACCEPT
iptables -A chain-outgoing-services -p tcp --dport 22  -j ACCEPT
iptables -A chain-outgoing-services -p icmp            -j ACCEPT
iptables -A chain-outgoing-services -j DROP

# Define chain to allow established connections
iptables -N chain-states
iptables -A chain-states -p tcp  -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A chain-states -p udp  -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A chain-states -p icmp -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A chain-states -j RETURN

# Drop invalid packets
iptables -A INPUT -m conntrack --ctstate INVALID -j DROP

# Accept everything on loopback
iptables -A INPUT  -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT

# Accept incoming/outgoing packets for established connections
iptables -A INPUT  -j chain-states
iptables -A OUTPUT -j chain-states

# Accept incoming ICMP
iptables -A INPUT -p icmp -j ACCEPT

# Accept incoming SSH
iptables -A INPUT -p tcp --dport 22 -j chain-incoming-ssh

# Accept outgoing 
iptables -A OUTPUT -j chain-outgoing-services

## Drop everything else
iptables -P INPUT   DROP
iptables -P FORWARD DROP
iptables -P OUTPUT  DROP

List all firewall rules to verify that executed commands are applied as desired.

$ sudo iptables -L -v -n
Chain INPUT (policy DROP 2 packets, 92 bytes)
 pkts bytes target              prot opt in     out     source               destination         
    0     0 DROP                all  --  *      *       0.0.0.0/0            0.0.0.0/0            ctstate INVALID
    1    29 ACCEPT              all  --  lo     *       0.0.0.0/0            0.0.0.0/0           
  190 15639 chain-states        all  --  *      *       0.0.0.0/0            0.0.0.0/0           
    0     0 ACCEPT              icmp --  *      *       0.0.0.0/0            0.0.0.0/0           
    1    60 chain-incoming-ssh  tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:22

Chain FORWARD (policy DROP 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain OUTPUT (policy DROP 0 packets, 0 bytes)
 pkts bytes target        prot opt in     out     source               destination         
    1    29 ACCEPT        all  --  *      lo      0.0.0.0/0            0.0.0.0/0           
  150 39893 chain-states  all  --  *      *       0.0.0.0/0            0.0.0.0/0           
    1    62 chain-outgoing-services  all  --  *      *       0.0.0.0/0            0.0.0.0/0           

Chain chain-incoming-ssh (1 references)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 ACCEPT     all  --  *      *       192.168.1.148        0.0.0.0/0           
    1    60 ACCEPT     all  --  *      *       192.168.1.149        0.0.0.0/0           
    0     0 DROP       all  --  *      *       0.0.0.0/0            0.0.0.0/0           

Chain chain-outgoing-services (1 references)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:53
    1    62 ACCEPT     udp  --  *      *       0.0.0.0/0            0.0.0.0/0            udp dpt:53
    0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:123
    0     0 ACCEPT     udp  --  *      *       0.0.0.0/0            0.0.0.0/0            udp dpt:123
    0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:80
    0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:443
    0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:22
    0     0 ACCEPT     icmp --  *      *       0.0.0.0/0            0.0.0.0/0           
    0     0 DROP       all  --  *      *       0.0.0.0/0            0.0.0.0/0           

Chain chain-states (2 references)
 pkts bytes target     prot opt in     out     source               destination         
  335 55240 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
    1    78 ACCEPT     udp  --  *      *       0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
    0     0 ACCEPT     icmp --  *      *       0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
    4   214 RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0           

I have used RETURN target for chain-states so the packet would continue its passage through the firewall.

Editing address list

List firewall rules in the source address list.

$ sudo iptables -n --list chain-incoming-ssh --line-numbers
Chain chain-incoming-ssh (1 references)
num  target     prot opt source               destination         
1    ACCEPT     all  --  192.168.1.148        0.0.0.0/0           
2    ACCEPT     all  --  192.168.1.149        0.0.0.0/0           
3    DROP       all  --  0.0.0.0/0            0.0.0.0/0           

Remove the first entry.

sudo iptables -D chain-incoming-ssh  1

Add a new rule at the beginning of the chain.

$ sudo iptables -I chain-incoming-ssh 1 -s 192.168.1.140 -j ACCEPT 

Add new rule just one before last entry.

$ sudo iptables -I chain-incoming-ssh 3 -s 192.168.1.150 -j ACCEPT 

List firewall rules in the source address list.

$ sudo iptables -n --list chain-incoming-ssh --line-numbers
Chain chain-incoming-ssh (1 references)
num  target     prot opt source               destination         
1    ACCEPT     all  --  192.168.1.140        0.0.0.0/0           
2    ACCEPT     all  --  192.168.1.149        0.0.0.0/0           
3    ACCEPT     all  --  192.168.1.150        0.0.0.0/0           
4    DROP       all  --  0.0.0.0/0            0.0.0.0/0           

Additional notes

You can inspect a particular chain using the following command.

$ sudo iptables -n --list chain-outgoing-services
Chain chain-outgoing-services (1 references)
target     prot opt source               destination         
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:53
ACCEPT     udp  --  0.0.0.0/0            0.0.0.0/0            udp dpt:53
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:123
ACCEPT     udp  --  0.0.0.0/0            0.0.0.0/0            udp dpt:123
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:80
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:443
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:22
ACCEPT     icmp --  0.0.0.0/0            0.0.0.0/0           
DROP       all  --  0.0.0.0/0            0.0.0.0/0    

You can also use it inside a shell script to perform different actions depending on its existence.

iptables --list chain-outgoing-services &>/dev/null
if [ "$?" -eq "0" ]; then
  iptables -F chain-outgoing-services
else
  iptables -N chain-outgoing-services
fi