Skip to content

Instantly share code, notes, and snippets.

@nirui
Last active July 14, 2022 06:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nirui/a7f05976eacdbbcf7470311717fef701 to your computer and use it in GitHub Desktop.
Save nirui/a7f05976eacdbbcf7470311717fef701 to your computer and use it in GitHub Desktop.
To deploy a proxy

To deploy a proxy

Copyright (C) 2020 RUI Ni (ranqus@gmail.com)

This is a document on how to depoly a reasonably secured proxy server which is capable of relaying non-critical communications and few other tasks.

Given the fact that this document is largely a personal note and memo, information and method provided in this document may NOT be useful for your use case. Do NOT follow the content if you don't fully understand the information and instruction.

The document also assumes the reader has decent amont of knownledge about Linux server. Because of that, step-by-step guide will not be provided, the reader must install and fine tune the softwares by themselves.

WARNING: If you decided to continue, please keep in mind that the author of this document does not provide any warranty. You have to proceed at your own risk.

Intro

Deploy a proxy server can be really easy. A typical HTTP or Socks5 proxy only takes one or two commands to setup and operate.

However, sometime, just to get the proxy software up & running is not enough. In many situation, you want to have more control over the server so it can be safely shared with your friend and even the public without been abused, or you maybe just want to make the server to be more secured against hackers who may hijack the server.

In order to achieve that, extra step is needed. And sometime those steps can be really troublesome and hard to figure out.

The document will provide few advices to readers who wants to create a decently reinforced proxy server, so it can be shared relately safely with other people.

Excluded contents

This document will not touch topics about DDoS protection, software bug, update policy, as well as anything that is unrelated to building and reinforcing a proxy.

This document will also not to recommend any actual proxy software, as there are too many of them. The reader can make the decision by themselves and apply necessary restrictions on those softwares.

The author will assume all softwares on the proxy is and will continue be bug-free, so the configuration given by this document will be applied correctly, and the softwares is working normally.

The eventual result

By the end of the document, the reader should receive some tips on creating a proxy server that:

  • Can automatically recover from software crash
  • Can be restricted from sending outbound traffic
  • Is isolated enough to reduce potential chance of danger that could effect the rest of the system

To achieve that, following method will be used:

  • Run the main proxy software inside a Docker container, or at least run it as a dedicated nologin'd user via systemd
  • Use iptables to restrict traffic going out from the proxy program
  • Use squid or other transparent proxy application to filter unencrypted HTTP traffic

Setup the server

Normally you want to have a server with cleanly installed OS. Make sure the OS setup image is acquired from an official source to avoid tamper.

After the OS is ready, make sure following softwares is installed:

  • iptables
  • openssh
  • openssl
  • squid or another transparent proxy of your choice
  • A text editor like vi or nano

If you decided to go with Docker, install it as well.

Create an non-privileged user to login to SSH

This can normally be done by using adduser command.

$ adduser proxy_server_user
$ passwd proxy_server_user
<Setup password for the user>
$ mkdir -p /home/proxy_server_user
$ chown proxy_server_user:proxy_server_user /home/proxy_server_user

Notice: Do NOT pick a common username such as admin, user or proxy. As the auto scanner used by some attackers can easily guess out those usernames.

Do NOT add the user to sudoers. The only purpose of this user is to allow remote SSH login.

After the login, user can switch to root via su command.

Reconfigure OpenSSH server to disable root login and change listening port

Edit the file /etc/ssh/sshd_config, change Port 22 to another number of your liking, we'll use 2222 for the rest of the document as an example, and enable following settings:

LoginGraceTime 1m
PermitRootLogin no
StrictModes yes
MaxAuthTries 2
MaxSessions 5

It is also strongly recommended to enable PubkeyAuthentication yes and disable PasswordAuthentication yes. Though this document will not cover the detail. User may implement the recommendation by themselves after the proxy is completely ready.

Notice: If you decided to use PubkeyAuthentication, do NOT directly enable it on root. Use a median account instead and perform su after each login

After the configuration is done, restart the openssh server to apply the change.

Setup initial iptables limitations

Keep in mind: You want to prevent unrelated user from accessing anything that is not suppose to be accessed. That is, a user should NOT be able to use anything that is unrelated to the proxy feature of the server.

A typical iptables rule set will by default, disallow all accesses, by

$ iptables -P INPUT DROP

and then selectively enable accesses, by

$ iptables -A INPUT -p tcp --dport 2222 -j ACCEPT

When it come together, the rules should be similar to:

$ iptables -P INPUT DROP
$ iptables -P FORWARD ACCEPT
$ iptables -P OUTPUT ACCEPT
$ iptables -A INPUT -i lo -j ACCEPT
$ iptables -A INPUT -m conntrack ! --ctstate NEW -j ACCEPT
$ iptables -A INPUT -p tcp --dport 2222 -j ACCEPT

This will allow only access to the OpenSSH host on our server for now.

Tip: You can also future limit the SSH port by employ methods such as source IP verification. However, the detail will not be covered by this document.

Determine what egress ports the user can access to

Depends on the use case, your user may want to access different ports. For example, 80 and 443 to browse the web, and 53 for DNS. You may have to consolidate the decision with your user.

Once the allowed egress ports is determined, an iptables whitelist chain can then be created to contain the traffic verification items.

There is an example ruleset which allows egress only to 80/tcp, 443/tcp and 53 ports:

# Create guest_output_redirect
$ iptables -t nat -N guest_output_redirect
$ iptables -t nat -A guest_output_redirect -p tcp --dport 80 -j REDIRECT --to-port 3128

# Create guest_output_limit
$ iptables -t filter -N guest_output_limit
$ iptables -t filter -A guest_output_limit -p tcp --dport 53 -j RETURN
$ iptables -t filter -A guest_output_limit -p udp --dport 53 -m limit --limit 16/s --limit-burst 32 -j RETURN
$ iptables -t filter -A guest_output_limit -p tcp --dport 443 -j RETURN

Notice we use NAT to redirect traffic from 80/tcp port to a Squid server so we can filter the HTTP traffic.

Configure the system /etc/sysctl.conf

net.core.default_qdisc=fq
net.ipv4.ip_forward=1
net.ipv6.conf.all.forwarding=1
net.ipv4.conf.docker0.route_localnet=1
net.ipv4.ip_default_ttl=129
net.ipv4.icmp_echo_ignore_all=1
net.ipv4.tcp_fastopen=3
net.ipv4.tcp_keepalive_time=96
net.ipv4.tcp_keepalive_probes=3
net.ipv4.tcp_keepalive_intvl=45
net.ipv4.tcp_retries1=3
net.ipv4.tcp_retries2=9

vm.oom_kill_allocating_task=1
vm.min_free_kbytes=32768
vm.admin_reserve_kbytes=16384

You may have to change 32768 to your actual application.

Then sysctl -p to apply.

Setup the main proxy software

Setup directly onto the server

WARNING: If your system is not drived by systemd, skip this section.

Please follow the instruction provided by the software vendor to install the software. This document will not cover that.

Many of those softwares can be installed via a simple apt or yum command, others needs binary download and manual install or even compile from source.

If the software does not come with it's own <PROXY_SERVICE>.service file for systemd, you may have to create one by hand. Here is an example:

[Unit]
Description=<PROXY_SERVICE_NAME>
After=network.target

[Service]
ExecStart=</path/to/the/executable>
Restart=always
RestartSec=5
User=<PROXY_SERVICE_RUN_USER>
Group=<PROXY_SERVICE_RUN_GROUP>

[Install]
WantedBy=multi-user.target

You need to replace <PROXY_SERVICE>, <PROXY_SERVICE_NAME>, </path/to/the/executable>, <PROXY_SERVICE_RUN_USER> and <PROXY_SERVICE_RUN_GROUP> above to the actual value that fits your application.

When your <PROXY_SERVICE>.service file is ready, put it into /usr/lib/systemd/system or other appreciate folder of your system, then run systemctl daemon-reload.

Watch out the <PROXY_SERVICE_RUN_USER>. This will be the dedicated user which the application will be running as, you need to create the user with adduser command, make it nologin so nobody can login to your server with this user.

Once the user creation is done, we can then apply the iptables whitelist chain guest_output_limit and guest_output_redirect to this user, so the user is limited by those iptables rules.

To do that, run:

$ iptables -t filter -I guest_output_limit -p tcp --sport <PROXY_SERVICE_PORT> -j RETURN
$ iptables -t filter -I guest_output_limit -p udp --sport <PROXY_SERVICE_PORT> -j RETURN
$ iptables -t filter -I OUTPUT -m owner --uid-owner <PROXY_SERVICE_RUN_USER> -j guest_output_limit
$ iptables -t nat -I OUTPUT -m owner --uid-owner <PROXY_SERVICE_RUN_USER> -j guest_output_redirect
$ iptables -A INPUT -p tcp --dport <PROXY_SERVICE_PORT> -j ACCEPT

Notice you need to replace <PROXY_SERVICE_PORT> and <PROXY_SERVICE_RUN_USER> with the actual value used on your server.

Setup as a Docker container (Recommended)

If the proxy software vendor provides a Docker image, follow their instruction to install the proxy in a Docker container. This document will not cover that.

However, because of how Docker is operated, the method used to restrict it's outbound traffic is different compare to the case above.

In Docker, when a container is using bridged network, the traffic in and out of the container is routed through a virtual interface, called docker0 by default.

To put limit on traffic in and out the container, we need to apply the restriction on the interface, and it will effect ALL containers which are routing their traffic through the interface.

The command is:

$ iptables -N DOCKER-USER # It's OK if the command reports duplication
$ iptables -t filter -I DOCKER-USER -i docker0 ! -o docker0 -m conntrack --ctstate NEW -j guest_output_limit
$ iptables -t nat -A PREROUTING -i docker0 -j guest_output_redirect
$ iptables -A INPUT -i docker0 -p tcp --dport 3128 -j ACCEPT

Keep in mind if the name of the interface is not docker0, you need to change the command accordingly.

As you may have noticed, we didn't allow the <PROXY_SERVICE_PORT> in the command above unlike what we did during the Setup directly onto the server section. This is because Docker manages iptables by itself.

By specify the -p or --publish parameter for docker run, it opens the port on iptables automatically, so we don't have to do that with iptables ourselves.

If you don't want to expose the container to the public, then publish the port to lo: --publish 127.0.0.1:8080:8080/tcp instead.

Squid

Edit the config file /etc/squid/squid.conf (maybe different on your system), change the http_port 3128 to http_port 127.0.0.1:3128 intercept, and http_access deny all to http_access allow all. Then add filter settings above the http_access allow all line:

acl limit5conn maxconn 16
http_access deny limit5conn

acl bad_urls urlpath_regex -i "/etc/squid/bad_urls"
acl bad_domains dstdomain "/etc/squid/bad_domains"

http_access deny bad_urls
http_access deny bad_domains

Create /etc/squid/bad_urls to contain "bad" urls such as:

\.torrent$
\/stat.*
\/announce.*
\/gtag.*
\/ga\.js.*
\/ga\.js$
\/hm\.js.*

Then create /etc/squid/bad_domains to contain "bad" domains such as:

.cn
.baidu.com
.360.com
.360.cn
.bdstatic.com
.alimama.com
.baidustatic.com
.jb51.net
.googleleadservices.com
.xunlei.com
.51la.com
.google-analytics.com
.atdmt.com
.doubleclick.net
.pointroll.com
.ard.yahoo.com
.yimg.com
.tribalfusion.com
.googlesyndication.com
.nozonedata.com
.fastclick.net

For some version of Squid, you must have a non-intercept http_port in order for it to start up. If that's the case, use http_port 127.0.0.1:3127 and never expose this port to the public.

Nginx

If you want to setup the transparent HTTP filtering proxy via Nginx, make sure the default site is disabled by overwriting it with:

server {
    listen 80 default_server;
    listen [::]:80 default_server;
    listen 443 ssl default_server;
    listen [::]:443 ssl default_server;
    ssl_certificate /etc/nginx/ssl/dummy.crt;
    ssl_certificate_key /etc/nginx/ssl/dummy.key;
    access_log off;
    server_name _;
    return 444;
}

in /etc/nginx/nginx.conf. The dummy certificate is a must, create them with command:

$ mkdir -p /etc/nginx/ssl
$ chmod 0600 /etc/nginx/ssl
$ openssl req -x509 -newkey rsa:4096 -keyout /etc/nginx/ssl/dummy.key -out /etc/nginx/ssl/dummy.crt -days 3650 --nodes

Attachments

File /etc/iptables-rules.sh (For Docker)

#!/bin/sh
#
# IPtables rules for proxy server

DOCKER_IF="docker0"
DOCKER_CHAIN="DOCKER-USER"
SSH_PORT=2222
SQUID_HOST=127.0.0.1
SQUID_PORT=3128

# Initialize default states
iptables -P INPUT DROP
iptables -P FORWARD ACCEPT
iptables -P OUTPUT ACCEPT
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -m conntrack ! --ctstate NEW -j ACCEPT

# Create guest_output_redirect
iptables -t nat -N guest_output_redirect
iptables -t nat -A guest_output_redirect -p tcp --dport 80 -j DNAT --to-destination $SQUID_HOST:$SQUID_PORT   # HTTP

# Create guest_output_limit
iptables -t filter -N guest_output_limit
iptables -t filter -A guest_output_limit -p tcp --dport 53 -j RETURN                                          # DNS
iptables -t filter -A guest_output_limit -p udp --dport 53 -m limit --limit 16/s --limit-burst 32 -j RETURN
iptables -t filter -A guest_output_limit -p tcp --dport 443 -j RETURN                                         # Web
iptables -t filter -A guest_output_limit -p tcp --dport 8080 -j RETURN
iptables -t filter -A guest_output_limit -p tcp --dport 993 -j RETURN                                         # Mail
iptables -t filter -A guest_output_limit -p tcp --dport 995 -j RETURN
iptables -t filter -A guest_output_limit -p tcp --dport 587 -j RETURN
iptables -t filter -A guest_output_limit -p tcp --dport 465 -j RETURN
iptables -t filter -A guest_output_limit -p tcp --dport 1080 -j RETURN                                        # Other proxies
iptables -t filter -A guest_output_limit -p tcp --dport 9050 -j RETURN
iptables -t filter -A guest_output_limit -p tcp -m multiport --dports 5228:5230 -j RETURN                     # Android
iptables -t filter -A guest_output_limit -p udp -m multiport --dports 5228:5230 -j RETURN
iptables -t filter -A guest_output_limit -p udp -m multiport --dports 3000:9000 -j RETURN                     # Extra UDP ports
iptables -t filter -A guest_output_limit -p tcp --dport 21 -j RETURN                                          # Misc
iptables -t filter -A guest_output_limit -p tcp --dport 22 -j RETURN
iptables -t filter -A guest_output_limit -p tcp --dport 23 -j RETURN
iptables -t filter -A guest_output_limit -p tcp --dport 2302 -j RETURN
iptables -t filter -A guest_output_limit -p udp --dport 2302 -m limit --limit 16/s --limit-burst 32 -j RETURN

iptables -t filter -A guest_output_limit -p tcp -j REJECT --reject-with tcp-reset                             # Deny the reset
iptables -t filter -A guest_output_limit -p udp -j DROP

# Allowing SSH port to be access from the public network
iptables -A INPUT -p tcp --dport $SSH_PORT -j ACCEPT

# Apply guest_output_limit to the default Docker if: docker0
iptables -N $DOCKER_CHAIN
iptables -t filter -I $DOCKER_CHAIN -i $DOCKER_IF ! -o $DOCKER_IF -m conntrack --ctstate NEW -j guest_output_limit

# Apply guest_output_redirect to the default Docker if: docker0
iptables -t nat -A PREROUTING -i $DOCKER_IF -j guest_output_redirect
iptables -A INPUT -i $DOCKER_IF -p tcp --dport $SQUID_PORT -j ACCEPT

docker run for Shadowsocks

$ docker run \
    --name shadowsocks \
    --detach \
    --restart always \
    --publish <PUBLIC_PORT>:8388/tcp \
    --publish <PUBLIC_PORT>:8388/udp \
    --env PASSWORD=******* \
    --env METHOD=******* \
    --env TIMEOUT=60 \
    --env DNS_ADDRS=1.1.1.1 \
    shadowsocks/shadowsocks-libev
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment