sh 在debian上使用strongSwan建立安全的VPN

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了sh 在debian上使用strongSwan建立安全的VPN相关的知识,希望对你有一定的参考价值。

#!/bin/zsh

CLIENT="me"
SERVER="debian"
FQDN="debian.example.com"
CA="ca"

# WiFi SSIDs that do not require automatic connection to VPN on network change
TRUSTED_SSIDS=("SSID1" "SSID2")

PAYLOADCERTIFICATEUUID=$( cat /proc/sys/kernel/random/uuid )
PKCS12PASSWORD=$( cat /proc/sys/kernel/random/uuid )

cat << EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>PayloadDisplayName</key>
    <string>${SERVER} VPN</string>
    <key>PayloadIdentifier</key>
    <string>${(j:.:)${(Oas:.:)FQDN}}</string>
    <key>PayloadUUID</key>
    <string>$( cat /proc/sys/kernel/random/uuid )</string>
    <key>PayloadType</key>
    <string>Configuration</string>
    <key>PayloadVersion</key>
    <integer>1</integer>
    <key>PayloadContent</key>
    <array>
        <dict>
            <key>PayloadDisplayName</key>
            <string>${SERVER} VPN</string>
            <key>PayloadDescription</key>
            <string>Configure VPN</string>
            <key>UserDefinedName</key>
            <string>${SERVER}</string>
            <key>VPNType</key>
            <string>IKEv2</string>
            <key>IKEv2</key>
            <dict>
                <key>RemoteAddress</key>
                <string>${FQDN}</string>
                <key>RemoteIdentifier</key>
                <string>${FQDN}</string>
                <key>LocalIdentifier</key>
                <string>${CLIENT}</string>
                <key>AuthenticationMethod</key>
                <string>Certificate</string>
                <key>PayloadCertificateUUID</key>
                <string>${PAYLOADCERTIFICATEUUID}</string>
                <key>CertificateType</key>
                <string>RSA</string>
                <key>ServerCertificateIssuerCommonName</key>
                <string>${FQDN}</string>
                <key>EnablePFS</key>
                <integer>1</integer>
                <key>IKESecurityAssociationParameters</key>
                <dict>
                    <key>EncryptionAlgorithm</key>
                    <string>AES-128-GCM</string>
                    <key>IntegrityAlgorithm</key>
                    <string>SHA2-256</string>
                    <key>DiffieHellmanGroup</key>
                    <integer>19</integer>
                </dict>
                <key>ChildSecurityAssociationParameters</key>
                <dict>
                    <key>EncryptionAlgorithm</key>
                    <string>AES-128-GCM</string>
                    <key>IntegrityAlgorithm</key>
                    <string>SHA2-256</string>
                    <key>DiffieHellmanGroup</key>
                    <integer>19</integer>
                </dict>
                <key>OnDemandEnabled</key>
                <integer>1</integer>
                <key>OnDemandRules</key>
                <array>
                    <dict>
                        <key>InterfaceTypeMatch</key>
                        <string>WiFi</string>
                        <key>SSIDMatch</key>
                        <array>
`for x in ${TRUSTED_SSIDS}; echo "                            <string>$x</string>"`
                        </array>
                        <key>Action</key>
                        <string>Disconnect</string>
                    </dict>
                    <dict>
                        <key>InterfaceTypeMatch</key>
                        <string>Cellular</string>
                        <key>Action</key>
                        <string>Disconnect</string>
                    </dict>
                    <dict>
                        <key>Action</key>
                        <string>Connect</string>
                    </dict>
                </array>
            </dict>
            <key>PayloadType</key>
            <string>com.apple.vpn.managed</string>
            <key>PayloadIdentifier</key>
            <string>com.apple.vpn.managed.${SERVER}</string>
            <key>PayloadUUID</key>
            <string>$( cat /proc/sys/kernel/random/uuid )</string>
            <key>PayloadVersion</key>
            <integer>1</integer>
        </dict>
        <dict>
            <key>PayloadDisplayName</key>
            <string>${CLIENT}.p12</string>
            <key>PayloadDescription</key>
            <string>Add PKCS#12 certificate</string>
            <key>PayloadCertificateFileName</key>
            <string>${CLIENT}.p12</string>
            <key>Password</key>
            <string>${PKCS12PASSWORD}</string>
            <key>PayloadContent</key>
            <data>
$( openssl pkcs12 -export -inkey /etc/ipsec.d/private/${CLIENT}.pem -in /etc/ipsec.d/certs/${CLIENT}.pem -name "${CLIENT}" -certfile /etc/ipsec.d/cacerts/${CA}.pem -password pass:${PKCS12PASSWORD} | base64 )
            </data>
            <key>PayloadType</key>
            <string>com.apple.security.pkcs12</string>
            <key>PayloadIdentifier</key>
            <string>com.apple.security.pkcs12.${CLIENT}</string>
            <key>PayloadUUID</key>
            <string>${PAYLOADCERTIFICATEUUID}</string>
            <key>PayloadVersion</key>
            <integer>1</integer>
        </dict>
        <dict>
            <key>PayloadDisplayName</key>
            <string>${SERVER} CA</string>
            <key>PayloadDescription</key>
            <string>Add CA root certificate</string>
            <key>PayloadCertificateFileName</key>
            <string>ca.pem</string>
            <key>PayloadContent</key>
            <data>
$( cat /etc/ipsec.d/cacerts/${CA}.pem | base64 )
            </data>
            <key>PayloadType</key>
            <string>com.apple.security.root</string>
            <key>PayloadIdentifier</key>
            <string>com.apple.security.root.${SERVER}</string>
            <key>PayloadUUID</key>
            <string>$( cat /proc/sys/kernel/random/uuid )</string>
            <key>PayloadVersion</key>
            <integer>1</integer>
        </dict>
    </array>
</dict>
</plist>
EOF
With heightening concern regarding the state of internet privacy (fuelled in part by the passing of the Investigatory Powers Act in the UK), I have set up a VPN server on the virtual server I have hosted with [Mythic Beasts](https://www.mythic-beasts.com/).  This uses [strongSwan](https://strongswan.org/) and certificate-based IKEv2 authentication.

Assumptions:
- Debian Jessie server already set up and accessible via `debian.example.com`, a public IPv4 of `203.0.113.1` and a public IPv6 of `2001:db8::1`
- Client username of `me`
- Clients are running the latest versions of macOS and iOS (Sierra and 10 respectively at the time of writing)
- No need to support any other operating systems (although the setup is easily translated)

For automated deployment of a similar setup, albeit Ubuntu-based and using ansible for deployment, I recommend you take a look at [Algo VPN](https://github.com/trailofbits/algo).  I used that project as a basis for my configuration.


Install strongSwan
==================

Most of the rest of this guide assumes that you are on the server with root permissions, so:

    % ssh debian.example.com
    % sudo -s
    $ apt-get install strongswan
    
    
Build the public key infrastructure
===================================

All of the certificates are stored in `/etc/ipsec.d`.  Unfortunately, macOS Sierra does not seem to like PKI built using ECDSA certificates for reasons which are not clear to me so I have used 4096-bit RSA keys.  The first step is to create a self-signed root CA certificate.

    $ cd /etc/ipsec.d
    $ ipsec pki --gen --type rsa --size 4096 --outform pem > private/ca.pem
    $ ipsec pki --self --ca --lifetime 3650 --in private/ca.pem \
    > --type rsa --digest sha256 \
    > --dn "CN=debian.example.com" \
    > --outform pem > cacerts/ca.pem

Next, generate a private key and signed certificate for the server.  macOS requires the hostname/address in subjectAltName.  Versions of Mac OS X prior to 10.7.4 required `--flag ikeIntermediate` but I have not added it here.

    $ ipsec pki --gen --type rsa --size 4096 --outform pem > private/debian.pem
    $ ipsec pki --pub --in private/debian.pem --type rsa |
    > ipsec pki --issue --lifetime 3650 --digest sha256 \
    > --cacert cacerts/ca.pem --cakey private/ca.pem \
    > --dn "CN=debian.example.com" \
    > --san debian.example.com --san 203.0.113.1 --san 2001:db8::1
    > --flag serverAuth --outform pem > certs/debian.pem
    
The last certificate is for the client.  You use one certificate for a single user on multiple devices hence I have only generated one.

    $ ipsec pki --gen --type rsa --size 4096 --outform pem > private/me.pem
    $ ipsec pki --pub --in private/me.pem --type rsa |
    > ipsec pki --issue --lifetime 3650 --digest sha256 \
    > --cacert cacerts/ca.pem --cakey private/ca.pem \
    > --dn "CN=me" --san me \
    > --flag clientAuth \
    > --outform pem > certs/me.pem

Now that certificate generation is complete, you should remove the CA private key (`private/ca.pem`) from the server.  You can either store it offline, or delete it entirely.  If you choose the latter, you will not be able to sign any more peer certificates or [revoke a certificate](https://wiki.strongswan.org/projects/strongswan/wiki/SimpleCA#Certificate-Revocation-Lists-CRL) if a client were compromised.  In the latter scenario, you would need to recreate a fresh PKI and disseminate to all peers.  Failure to remove the CA private key means that anyone who gains access to it could MitM your TLS connections.


Generate an IPv6 ULA
====================

Before getting started with configuring strongSwan, you'll want to generate an IPv6 [unique local address](https://en.wikipedia.org/wiki/Unique_local_address) block.  The addresses are within the fc00::/7 block and contain a pseudo-random component according to [RFC 4193](https://tools.ietf.org/html/rfc4193).  I've cobbled together a short script which will generate a ULA for you:

    % curl -s https://raw.githubusercontent.com/andrewlkho/ulagen/master/ulagen.py | python
    Prefix:       fdf3:5237:bf63::/48
    First subnet: fdf3:5237:bf63::/64
    Last subnet:  fdf3:5237:bf63:ffff::/64

Clearly your prefix will not be the same as that, so substitute `fdf3:5237:bf63::/64` in the dnsmasq, strongSwan and iptables configuration below with what you have.


Optional: secure DNS with dnscrypt and DNSSEC
=============================================

To prevent DNS cache poisoning, I run a local DNS server on `debian` which the VPN clients use.  This consists of a [dnscrypt-proxy](https://github.com/jedisct1/dnscrypt-proxy/wiki) client bound to [::1]:5353 which transparently proxies queries through to a dnscrypt-aware upstream resolver.  dnscrypt-proxy itself is upstream of [dnsmasq](http://www.thekelleys.org.uk/dnsmasq/doc.html) which provides features like DNSSEC validation and is what the VPN clients have configured as their DNS server.

Install dnscrypt-proxy.

    $ sudo apt-get install libsodium-dev libldns-dev
    $ cd /usr/local/src
    $ curl -LO https://download.dnscrypt.org/dnscrypt-proxy/dnscrypt-proxy-1.9.2.tar.bz2
    $ gpg --recv-keys 2B6F76DA
    $ curl https://download.dnscrypt.org/dnscrypt-proxy/dnscrypt-proxy-1.9.2.tar.gz.sig | \
    > gpg --verify - ./dnscrypt-proxy-1.9.2.tar.gz
    $ tar xjf dnscrypt-proxy-1.9.2.tar.gz && cd dnscrypt-proxy-1.9.2
    $ ./configure --prefix=/usr/local
    $ make
    $ make install

Create an unprivileged user.

    $ useradd --system -d /usr/local/lib/dnscrypt-proxy -s /usr/sbin/nologin dnscrypt

Edit `/usr/local/etc/dnscrypt-proxy.conf`.  You can choose whichever dnscrypt-enabled upstream server you want; I have chosen to use [DNSCrypt.eu](https://dnscrypt.eu/) of which their Netherlands server has the fewest hops to `debian`.  dnscrypt-proxy is bound to ::1 port 5353.

    ProviderName    2.dnscrypt-cert.resolver1.dnscrypt.eu
    ProviderKey     67C0:0F2C:21C5:5481:45DD:7CB4:6A27:1AF2:EB96:9931:40A3:09B6:2B8D:1653:1185:9C66
    ResolverAddress [2a00:d880:3:1::a6c1:2e89]:443
    Daemonize       no
    LocalAddress    [::1]:5353
    LocalCache      off
    User            dnscrypt
    EphemeralKeys   on
    BlockIPv6       no

Copy across the systemd service (amending the paths) and start dnscrypt-proxy.

    $ sed -e 's:^ExecStart=.*$:ExecStart=/usr/local/sbin/dnscrypt-proxy /usr/local/etc/dnscrypt-proxy.conf:' \
    > /usr/local/src/dnscrypt-proxy-1.9.2/dnscrypt-proxy.service > /etc/systemd/system/dnscrypt-proxy.service
    $ systemctl enable dnscrypt-proxy.service
    $ systemctl start dnscrypt-proxy

You can check that it works by querying `resolver.dnscrypt.org` which returns the address of the nameserver you are using.

    $ dig @::1 -p 5353 +short -x $( dig @::1 -p 5353 +short resolver.dnscrypt.org A )
    resolver1.dnscrypt.eu

The next step is to install dnsmasq.

    $ apt-get install dnsmasq

dnsmasq should bind to ::1 and also a unique local address on the loopback interface.  Use the ULA prefix generated previously, but a different subnet, by putting the following into `/etc/network/interfaces`.

    auto lo:1
    iface lo:1 inet6 static
    address fdf3:5237:bf63:1::1
    netmask 64
    autoconf 1

Then bring it up with `ifup lo:1`.  Edit `/etc/dnsmasq.conf`.

    no-resolv
    no-hosts
    server=::1#5353
    listen-address=::1
    listen-address=fdf3:5237:bf63:1::1
    bind-interfaces
    
    dnssec
    trust-anchor=.,19036,8,2,49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5
    dnssec-check-unsigned
    
    bogus-priv
    domain-needed
    stop-dns-rebind
    rebind-localhost-ok
    
    cache-size=2000

Restart dnsmasq.

    $ systemctl restart dnsmasq


Configure strongSwan
====================

Disable any plugins that you do not need.

    $ plugins=(aes gcm hmac kernel-netlink nonce openssl pem pgp \
    > pkcs12 pkcs7 pkcs8 pubkey random revocation sha2 socket-default stroke x509)
    $ cd /etc/strongswan.d/charon
    $ for x in *.conf; do
    >   if [[ ${plugins[(i)${x%.conf}]} -le ${#plugins} ]]; then
    >     sed -E 's/^([[:space:]]+)load = no/\1load = yes/' ${x}
    >   else
    >     sed -E 's/^([[:space:]]+)load = yes/\1load = no/' ${x}
    >   fi
    > done
    
Copy the following `/etc/ipsec.conf` (don't forget to change the ULA).

    config setup
        uniqueids=never
    
    conn %default
        keyexchange=ikev2
        ike=aes128gcm16-sha2_256-prfsha256-ecp256!
        esp=aes128gcm16-sha2_256-ecp256!
        fragmentation=yes
        rekey=no
        compress=yes
        dpdaction=clear
    
        left=%any
        leftauth=pubkey
        leftid=debian.example.com
        leftcert=debian.pem
        leftsendcert=always
        leftsubnet=0.0.0.0/0,::/0
    
        right=%any
        rightauth=pubkey
        rightsourceip=172.16.0.0/24,fdf3:5237:bf63::/64
        rightdns=fdf3:5237:bf63:1::1
    
    conn ikev2-pubkey
        auto=add

A few points to note on that:

- I have only allowed a single IKE cipher suite.  If you want to support clients other than macOS and iOS, you may need to adjust this.
- macOS/iOS will only propose 128-bit AES-GCM if you configure the VPN using a `.mobileconfig` configuration profile.  If you use the GUI to configure the VPN on the client then you will need to use `ike=aes256-sha2_256-prfsha256-ecp256!` and `esp=aes256-sha2_256-ecp256!`.
- I have used the local DNS server bound to the loopback interface.

Next, copy the following `/etc/ipsec.secrets`.

    : RSA debian.pem

Then restart strongswan.

    $ systemctl restart strongswan


Enable packet forwarding
========================

Configure the kernel to enable packet forwarding by putting the following lines in `/etc/sysctl.conf`.

    net.ipv4.ip_forward = 1
    net.ipv4.conf.all.accept_redirects = 0
    net.ipv4.conf.all.send_redirects = 0
    net.ipv6.conf.all.forwarding = 1
    net.ipv6.conf.eth0.accept_ra = 2

The penultimate line enables forwarding for IPv6, but has the unfortunate side-effect of therefore not accepting router advertisements.  These are needed for SLAAC, without which the server cannot get routing information (at least, with how Mythic Beasts configure their virtual servers).  This would break IPv6 networking on the server.  The last line therefore adjusts this for the interface `eth0`.

Load the settings.

    % sysctl -p


Configure iptables
==================

Here is a snippet of my `/etc/iptables/rules.v4`.

    *filter
    [...]
    -A INPUT -p esp -j ACCEPT
    -A INPUT -p ah -j ACCEPT
    -A INPUT -p udp -m multiport --dports 500,4500 -j ACCEPT
    -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
    -A FORWARD -m conntrack --ctstate NEW -s 172.16.0.0/24 -m policy --pol ipsec --dir in -j ACCEPT
    COMMIT
    
    *nat
    [...]
    -A POSTROUTING -s 172.16.0.0/24 -m policy --pol none --dir out -j MASQUERADE
    # If you have a static IP address you can do:
    # -A POSTROUTING -s 172.16.0.0/24 -m policy --pol none --dir out -j SNAT --to-source 203.0.113.1
    COMMIT

Here is a snippet of my `/etc/iptables/rules.v6` (don't forget to change the ULA).

    *filter
    [...]
    -A INPUT -p esp -j ACCEPT
    -A INPUT -m ah -j ACCEPT
    -A INPUT -p udp -m multiport --dports 500,4500 -j ACCEPT
    -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
    -A FORWARD -m conntrack --ctstate NEW -s fdf3:5237:bf63::/64 -m policy --pol ipsec --dir in -j ACCEPT
    
    # If you are running a local DNS server as above
    -A INPUT -p udp --dport 53 -d fdf3:5237:bf63:1::1 -j ACCEPT
    COMMIT

    *nat
    [...]
    -A POSTROUTING -s fdf3:5237:bf63::/64 -m policy --pol none --dir out -j MASQUERADE
    COMMIT


Configure the clients (macOS and iOS)
=====================================

To configure the macOS and iOS clients, you need to generate a configuration profile.  I wrote a shell script to do this (`mobileconfiggen.sh`) which is attached to this gist.  As well as the VPN settings, this pulls in the root CA certificate and client key/certificate pair.

Aside from the variables at the start of the script, you will probably also want to amend the `OnDemandRules` array.  This is an array of dictionaries, each of which specifies whether or not your iOS device will automatically connect to the VPN in certain scenarios.  The rules I have specified mean that unless you are on a mobile connection or using one of the pre-specified trusted WiFi networks (`$TRUSTED_SSIDS`) it will connect to the VPN.  If you want to edit this, then take a look at [Apple's Configuration Profile Reference](https://developer.apple.com/library/content/featuredarticles/iPhoneConfigurationProfileRef/Introduction/Introduction.html#//apple_ref/doc/uid/TP40010206-CH1-SW36).

The script takes no arguments, and will output the configuration profile to STDOUT.

    $ ./mobileconfiggen.sh > debian.mobileconfig

On macOS, you can install this with:

    $ profiles -I -F debian.mobileconfig

On iOS, the easiest way to install it is to send the file to the device over AirDrop.


Configure the clients (Android)
===============================

To configure the Android clients, you need to generate a configuration profile for the strongSwan VPN Client (https://play.google.com/store/apps/details?id=org.strongswan.android).  I wrote a shell script to do this (`androidconfiggen.sh`) which is attached to this gist.  As well as the VPN settings, this pulls in the root CA certificate and client key/certificate pair.

Conclusion
==========

That's it.  Reboot your server to check it all comes up automatically and you should be done.  A few links which were helpful to me:

- [Algo VPN](https://github.com/trailofbits/algo): a similar setup based on deploying to an Ubuntu cloud server using ansible
- [Andy Smith's blog](http://strugglers.net/~andy/blog/2011/09/04/linux-ipv6-router-advertisements-and-forwarding/): getting IPv6 packet forwarding and SLAAC to work together
- [The strongSwan wiki](https://wiki.strongswan.org/projects/strongswan)
- [Apple's Configuration Profile Reference](https://developer.apple.com/library/content/featuredarticles/iPhoneConfigurationProfileRef/Introduction/Introduction.html#//apple_ref/doc/uid/TP40010206-CH1-SW4)

If you have any suggestions for how this guide or setup could be improved, then please [let me know](https://andrewho.co.uk/).
#!/bin/zsh

CLIENT="client"
SERVER="mpalet.duckdns.org"
FQDN="mpalet.duckdns.org"
CA="ca"


cat << EOF
{
    "uuid":"$( cat /proc/sys/kernel/random/uuid )",
    "name":"VPN ${SERVER}",
    "type":"ikev2-cert",
    "remote":
    {
        "addr":"${FQDN}",
        "cert":"$( cat /etc/ipsec.d/cacerts/${CA}.pem | base64 )"
    },
    "local":
    {
        "p12":"$( openssl pkcs12 -export -inkey /etc/ipsec.d/private/${CLIENT}.pem -in /etc/ipsec.d/certs/${CLIENT}.pem -name "${CLIENT}" -certfile /etc/ipsec.d/cacerts/${CA}.pem | base64 )"
    }
}
EOF

以上是关于sh 在debian上使用strongSwan建立安全的VPN的主要内容,如果未能解决你的问题,请参考以下文章

Openswan 和 strongSwan 相比,各有啥特点

sh 在Debian上安装zRAM(8)

sh 在debian上设置shadowsocks

sh 在Debian上安装zRAM(8)

sh 在Debian上安装zRAM(8)

sh 在Debian上安装Rails