diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b0a2873 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,43 @@ +FROM alpine:edge + +LABEL maintainer="Kirillius Labutin " + +ENV container=docker + +RUN apk --update add openrc openvpn frr php easy-rsa nano + +RUN echo 'rc_cgroups_enabled="NO"' >> /etc/rc.conf + +RUN echo 'rc_provide="loopback net"' >> /etc/rc.conf + +RUN rm /etc/init.d/loopback /etc/init.d/networking /etc/init.d/machine-id /etc/init.d/hwdrivers + +RUN openrc + +RUN ln -s /data/pki /etc/openvpn/keys + +COPY connect.php /etc/openvpn/connect.php + +RUN chmod +x /etc/openvpn/connect.php + +COPY openvpn.conf /data/openvpn.conf + +RUN ln -s /data/openvpn.conf /etc/openvpn/openvpn.conf + +COPY vars /usr/share/easy-rsa + +COPY client-config-template.conf /data/client-config-template.conf + +COPY build-client-config.php /usr/bin/build-client-config + +RUN sed -i 's/^tty/#&/' /etc/inittab + +RUN chmod +x /usr/bin/build-client-config + +RUN mv /etc/frr /data/frr + +RUN ln -s /data/frr /etc/frr + +RUN sed -i 's#/etc/frr#/data/frr#g' /etc/init.d/frr + +ENTRYPOINT ["/sbin/init"] \ No newline at end of file diff --git a/README.md b/README.md index 5bddb8b..f933367 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,48 @@ -# openvpn-frr-template +# 1. Init RSA +```shell +cd /usr/share/easy-rsa +./easyrsa clean-all +./easyrsa init-pki +./easyrsa build-ca nopass +./easyrsa build-server-full server nopass +./easyrsa gen-dh +``` +# 2. Create user certs +```shell +cd /usr/share/easy-rsa +./easyrsa build-client-full USERNAME nopass +``` + +# 3. Configure server +Preconfigured example is here: +```shell +nano /data/openvpn.conf +``` + +# 4. Enable routing protocols +```shell +nano /data/frr/daemons +``` + +# 5. Enable services autostart +```shell +rc-update add frr +rc-update add openvpn +``` + +# 6. Start services +```shell +rc-service frr start +rc-service openvpn start +``` + +# 7. Configure router +```shell +vtysh +``` + +# 8. Get client config file +```shell +build-client-config USERNAME +``` \ No newline at end of file diff --git a/build-client-config.php b/build-client-config.php new file mode 100644 index 0000000..62902e2 --- /dev/null +++ b/build-client-config.php @@ -0,0 +1,25 @@ +#!/usr/bin/php +" . $what . "\n"; +} + +$clientName = $argv[1]; +$template = readFileOrDie("/data/client-config-template.conf"); +$template .= "\n"; +$template .= wrap(readFileOrDie("/data/pki/issued/" . $clientName . ".crt"), "cert"); +$template .= wrap(readFileOrDie("/data/pki/ca.crt"), "ca"); +$template .= wrap(readFileOrDie("/data/pki/private/" . $clientName . ".key"), "key"); +echo "\n\n\nYour config is: \n ================================\n\n" . $template . "\n\n===============================\n"; \ No newline at end of file diff --git a/client-config-template.conf b/client-config-template.conf new file mode 100644 index 0000000..51b5cca --- /dev/null +++ b/client-config-template.conf @@ -0,0 +1,10 @@ +client +dev tun +proto udp +remote YOURSERVER 1194 +resolv-retry infinite +nobind +ns-cert-type server +persist-key +persist-tun +verb 3 \ No newline at end of file diff --git a/connect.php b/connect.php new file mode 100644 index 0000000..b5e6960 --- /dev/null +++ b/connect.php @@ -0,0 +1,93 @@ +#!/usr/bin/php += $subnet_first and $test_ip <= $subnet_last and $candidate_prefix > $prefix; +} + +class RoutingTableReader +{ + private $routes = []; + + /** + * @return array + */ + public function getRoutes(): array + { + return $this->routes; + } + + public function __construct() + { + $result = @shell_exec("ip --json route show"); + if (!$result) { + throw new RuntimeException("Failed to read routing table"); + } + $this->routes = @json_decode($result, true); + if ($this->routes === null) { + throw new RuntimeException("Failed to parse json output"); + } + foreach ($this->routes as $key => &$route) { + if ($route["dst"] === "default") { + $route["dst"] = "0.0.0.0/0"; + } elseif (!str_contains($route["dst"], "/")) { + $route["dst"] .= "/32"; + } + } + } +} + +try { + if (!isset($argv[1])) { + throw new RuntimeException("Output config is not set"); + } + $config = []; + $reader = new RoutingTableReader(); + $routes = []; + $overlapped = []; + foreach ($reader->getRoutes() as $route) { + if ($route["dst"] == "0.0.0.0/0") { + continue; + } + $routes[] = $route; + } + foreach ($routes as $i => $master) { + if (in_array($master, $overlapped)) { + continue; + } + foreach ($routes as $j => $slave) { + if ($i == $j or in_array($slave, $overlapped)) { + continue; + } + if (is_subnet_overlaps($master["dst"], $slave["dst"])) { + $overlapped[] = $slave; + } + } + } + $subnets = []; + foreach ($routes as $i => $master) { + if (in_array($master, $overlapped)) { + continue; + } + $subnets[] = $master["dst"]; + } + foreach ($subnets as $route) { + $parts = explode("/", $route); + $mask = long2ip(-1 << (32 - (int)$parts[1])); + $dst = $parts[0]; + $config[] = "push \"route {$dst} {$mask}\""; + } + $outfile = $argv[1]; + file_put_contents($outfile, implode("\n", $config)); +} catch (Exception $e) { + echo "\nError:" . $e->getMessage() . "\n"; + exit(1); +} +exit(0); diff --git a/openvpn.conf b/openvpn.conf new file mode 100644 index 0000000..46b7b1e --- /dev/null +++ b/openvpn.conf @@ -0,0 +1,27 @@ +#keys +ca /etc/openvpn/keys/ca.crt +cert /etc/openvpn/keys/issued/server.crt +key /etc/openvpn/keys/private/server.key +dh /etc/openvpn/keys/dh.pem + +#IP settings +local 172.16.100.30 +port 1194 +proto udp +dev tun +server 172.16.102.0 255.255.255.128 + +#Routing settings +push "route 172.16.102.0 255.255.255.0" +push "dhcp-option DNS 8.8.8.8" +push "dhcp-option DNS 1.1.1.1" + +keepalive 10 120 +persist-key +persist-tun +ifconfig-pool-persist ipp.txt +status /var/log/openvpn-status.log +log-append /var/log/openvpn.log +verb 3 +script-security 2 +client-connect /etc/openvpn/connect.php \ No newline at end of file diff --git a/vars b/vars new file mode 100644 index 0000000..5d476a1 --- /dev/null +++ b/vars @@ -0,0 +1,164 @@ +# Easy-RSA 3 parameter settings + +# NOTE: If you installed Easy-RSA from your package manager, do not edit +# this file in place -- instead, you should copy the entire easy-rsa directory +# to another location so future upgrades do not wipe out your changes. + +# HOW TO USE THIS FILE +# +# vars.example contains built-in examples to Easy-RSA settings. You MUST name +# this file "vars" if you want it to be used as a configuration file. If you +# do not, it WILL NOT be automatically read when you call easyrsa commands. +# +# It is not necessary to use this config file unless you wish to change +# operational defaults. These defaults should be fine for many uses without +# the need to copy and edit the "vars" file. +# +# All of the editable settings are shown commented and start with the command +# "set_var" -- this means any set_var command that is uncommented has been +# modified by the user. If you are happy with a default, there is no need to +# define the value to its default. + +# NOTES FOR WINDOWS USERS +# +# Paths for Windows *MUST* use forward slashes, or optionally double-escaped +# backslashes (single forward slashes are recommended.) This means your path +# to the openssl binary might look like this: +# "C:/Program Files/OpenSSL-Win32/bin/openssl.exe" + +# A little housekeeping: DO NOT EDIT THIS SECTION +# +# Easy-RSA 3.x does not source into the environment directly. +# Complain if a user tries to do this: +if [ -z "$EASYRSA_CALLER" ]; then + echo "You appear to be sourcing an Easy-RSA *vars* file. This is" >&2 + echo "no longer necessary and is disallowed. See the section called" >&2 + echo "*How to use this file* near the top comments for more details." >&2 + return 1 +fi + +# DO YOUR EDITS BELOW THIS POINT + +# If your OpenSSL command is not in the system PATH, you will need to define +# the path here. Normally this means a full path to the executable, otherwise +# you could have left it undefined here and the shown default would be used. +# +# Windows users, remember to use paths with forward-slashes (or escaped +# back-slashes.) Windows users should declare the full path to the openssl +# binary here if it is not in their system PATH. +# +#set_var EASYRSA_OPENSSL "openssl" +# +# This sample is in Windows syntax -- edit it for your path if not using PATH: +#set_var EASYRSA_OPENSSL "C:/Program Files/OpenSSL-Win32/bin/openssl.exe" + +# Windows users, to generate OpenVPN TLS Keys the Openvpn binary must be +# defined here. +# +#set_var EASYRSA_OPENVPN "C:\\Program Files\\Openvpn\\bin\\openvpn.exe" + +# Define X509 DN mode. +# +# This is used to adjust which elements are included in the Subject field +# as the DN ("Distinguished Name"). Note that in 'cn_only' mode the +# Organizational fields, listed further below, are not used. +# +# Choices are: +# cn_only - Use just a commonName value. +# org - Use the "traditional" format: +# Country/Province/City/Org/Org.Unit/email/commonName +# +#set_var EASYRSA_DN "cn_only" + +# Organizational fields (used with "org" mode and ignored in "cn_only" mode). +# These are the default values for fields which will be placed in the +# certificate. Do not leave any of these fields blank, although interactively +# you may omit any specific field by typing the "." symbol (not valid for +# email). +# +# NOTE: The following characters are not supported +# in these "Organizational fields" by Easy-RSA: +# back-tick (`) +# +#set_var EASYRSA_REQ_COUNTRY "US" +#set_var EASYRSA_REQ_PROVINCE "California" +#set_var EASYRSA_REQ_CITY "San Francisco" +#set_var EASYRSA_REQ_ORG "Copyleft Certificate Co" +#set_var EASYRSA_REQ_EMAIL "me@example.net" +#set_var EASYRSA_REQ_OU "My Organizational Unit" + +# Preserve the Distinguished Name field order +# of the certificate signing request +# *Only* effective in --dn-mode=org +# +#set_var EASYRSA_PRESERVE_DN 1 + +# Set no password mode - This will create the entire PKI without passwords. +# This can be better managed by choosing which entity private keys should be +# encrypted with the following command line options: +# Global option '--no-pass' or command option 'nopass'. +# +#set_var EASYRSA_NO_PASS 1 + +# Choose a size in bits for your keypairs. The recommended value is 2048. +# Using 2048-bit keys is considered more than sufficient for many years into +# the future. Larger keysizes will slow down TLS negotiation and make key/DH +# param generation take much longer. Values up to 4096 should be accepted by +# most software. Only used when the crypto alg is rsa, see below. +# +#set_var EASYRSA_KEY_SIZE 2048 + +# The default crypto mode is rsa; ec can enable elliptic curve support. +# Note that not all software supports ECC, so use care when enabling it. +# Choices for crypto alg are: (each in lower-case) +# * rsa +# * ec +# * ed +# +#set_var EASYRSA_ALGO rsa + +# Define the named curve, used in ec & ed modes: +# +#set_var EASYRSA_CURVE secp384r1 + +# In how many days should the root CA key expire? +# +#set_var EASYRSA_CA_EXPIRE 3650 + +# In how many days should certificates expire? +# +set_var EASYRSA_CERT_EXPIRE 3650 + +# How many days until the Certificate Revokation List will expire. +# +# IMPORTANT: When the CRL expires, an OpenVPN Server which uses a +# CRL will reject ALL new connections, until the CRL is replaced. +# +#set_var EASYRSA_CRL_DAYS 180 + +# Random serial numbers by default. +# Set to 'no' for the old incremental serial numbers. +# +#set_var EASYRSA_RAND_SN "yes" + +# Cut-off window for checking expiring certificates. +# +#set_var EASYRSA_PRE_EXPIRY_WINDOW 90 + +# Generate automatic subjectAltName for certificates +# +#set_var EASYRSA_AUTO_SAN 1 + +# Add critical attribute to X509 fields: basicConstraints (BC), +# keyUsage (KU), extendedKeyUsage (EKU) or SAN +# +#set_var EASYRSA_BC_CRIT 1 +#set_var EASYRSA_KU_CRIT 1 +#set_var EASYRSA_EKU_CRIT 1 +#set_var EASYRSA_SAN_CRIT 1 + +# Disable automatic inline files +# +#set_var EASYRSA_DISABLE_INLINE 1 + +set_var EASYRSA_PKI "/data/pki"