MikrotTik: OpenVPN

From Luky-Wiki
Jump to: navigation, search

Attention: OpenVPN with certificates is broken in ROS 6.11. The option "Require Client Certificate" in version 6.12 works but you may need to re-import certificates if you are upgrading from 6.10 or earlier. Check forum for more details.

Note: I updated configuration of VPN to address CPU utilization and packet fragmentation problems. (2014-09-21)

Before you start

I am using OVPN client / server on MikroTik to connect several network/location. This document describe my findings and my way of configuration.

While I was designing my network I found following limitations (features ?) of OVPN implementation:

  • "require-client-certificate" is working only since ROS 6.12. It is possible to select CA+user+pass authorization method or simple user+pass.
  • It is not possible to create "password less" connection. Each connection is authorized by certificate (optional), username and password.
  • Only TCP connection as base channel for OVPN is supported by MikroTik (ROS 6.18).
  • Server can be specified by FQDN only in ROS 6.4 and later.
  • It is required that client address is managed and assigned by OVPN Server (e.g. it is not possible to set IP on server and client outside of OVPN configuration).

Generate valid certificates

I used "valid" in name of this section because I get wrongly generated certificates using "pkitool". I am not sure if this was fail of this tool or my fail but right now i am using different way to generate certificate. Maybe in future I'll debug what was wrong with pkitool.

easy-rsa installation

Gentoo users:

emerge app-crypt/easy-rsa

Ubuntu users:

apt-get install easy-rsa

Other users: please try to find easy-rsa using your distributions package manager or download it from GitHub

Prepare CA directory

Ubuntu have additional command to create CA directory. Select empty directory and then use (I selected "ovpn"):

make-cadir ovpn

Gentoo users can use following rsync command:

rsync -av /usr/share/easy-rsa/ ovpn/

On other Linux distribution you should find easy-rsa installation and copy it to desired working directory.

Variables configuration (e.g. "vars")

Before certificates can be generated it is necessary to customize "vars" file inside new ca directory ("ovpn" in this example).

I prefer strict security so i changed key size from 1024 to 4096.

export KEY_SIZE=4096

There are details about certificates at end of "vars" file. It is required that you modify it to reflect your settings. For example like this:

export KEY_COUNTRY="SK"
export KEY_PROVINCE="SK"
export KEY_CITY="Bratislava"
export KEY_ORG="My company name"
export KEY_OU="MikroTik OVPN"
export KEY_CN="changeme"
export KEY_NAME="changeme"
export KEY_EMAIL="myself@mydomain.tld"
export KEY_ALTNAMES="something"

CN and NAME will be different for each certificate so i left it as "changeme". Easy-rsa will ask to confirm each KEY_* variable during certificate generation, so it is possible to change both values for each certificate.

Certificate Revocation List (optional)

CRL configuration is directly in openssl.cnf. CA directory contain several configuration files so it is necessary to check which one is use:

$ . ./vars 
NOTE: If you run ./clean-all, I will be doing a rm -rf on /home/lukas/Desktop/ovpn/keys
$ ./whichopensslcnf .
./openssl-1.0.0.cnf

File openssl-1.0.0.cnf contain several section. It is necessary to add CRL in:

  • [ usr_cert ] for client certificate
  • [ server ] for server certificate
  • [ v3_ca ] for CA certificate

CRL configuration line is in following format (single host or multiple hosts):

crlDistributionPoints = URI:http://www.example.com/crl.pem

or

crlDistributionPoints = URI:http://www-1.example.com/crl.pem,URI:http://www-2.example.com/crl.pem

Note: ROS 6.11 support only HTTP.

CRL is single point of failure for OVPN so make sure you use domain hosted on HA solution. CRL is renewed once per hour (ROS 6.11) and if it fail then trusted certificate is no longer usable.

CA directory cleanup

Easy-rsa require "clean" even if CA directory is new and currently empty:

$ . ./vars 
NOTE: If you run ./clean-all, I will be doing a rm -rf on /.../.../ovpn
$ ./clean-all 
$

Generate CA certificate

Note: configuration is loaded as variables in shell environment. If you experiment with configuration then don't forget to overwrite/remove variables from shell. If you are unsure then simply close terminal session and start with clean one. After that start with ". ./vars" and ./clean-all command.

$ ./build-ca 
Generating a 2048 bit RSA private key
...........+++
.............+++
writing new private key to 'ca.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [SK]:
State or Province Name (full name) [SK]:
Locality Name (eg, city) [Bratislava]:
Organization Name (eg, company) [My company Name]:
Organizational Unit Name (eg, section) [MikroTik OVPN]:
Common Name (eg, your name or your server's hostname) [changeme]:server.domain.tld
Name [changeme]:server.domain.tld
Email Address [myself@mydomain.tld]:
$

I changed server name simply by typing it while tool was asking for it. Other variables are confirmed by hitting return.

Generate Diffie–Hellman

Note 1: This step is necessary only if you are running OpenVPN server also on Linux. If you are running only MikroTik OVPN and OpenVPN clients then you can skip this step.

Note 2: Without proper HW random generator this can take long time. Grab cup of coffee or try to generate some entropy on your machine while DH is genererated.

(output reduced)

$ ./build-dh 
Generating DH parameters, 2048 bit long safe prime, generator 2
This is going to take a long time
...............................
$ 

Generate server certificate

It is important that all certificates under one CA chain have unique Name/Common Name. I need client and server certificate for one machine so I will generate certificates using FQDN for servers and using hostname for clients. This will also help to simply identify type of certificate without tools.

$ ./build-key-server server.domain.tld
Generating a 2048 bit RSA private key
..................................................................................+++
..........+++
writing new private key to 'server.domain.tld.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [SK]:
State or Province Name (full name) [SK]:
Locality Name (eg, city) [Bratislava]:
Organization Name (eg, company) [My company Name]:
Organizational Unit Name (eg, section) [MikroTik OVPN]:
Common Name (eg, your name or your server's hostname) [server.domain.tld]:
Name [changeme]:server.domain.tld
Email Address [myself@mydomain.tld]:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
Using configuration from /home/lukas/Desktop/a/ovpn/openssl-1.0.0.cnf
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
countryName           :PRINTABLE:'SK'
stateOrProvinceName   :PRINTABLE:'SK'
localityName          :PRINTABLE:'Bratislava'
organizationName      :PRINTABLE:'My company Name'
organizationalUnitName:PRINTABLE:'MikroTik OVPN'
commonName            :PRINTABLE:'server.domain.tld'
name                  :PRINTABLE:'server.domain.tld'
emailAddress          :IA5STRING:'myself@mydomain.tld'
Certificate is to be certified until Oct  4 18:10:34 2023 GMT (3650 days)
Sign the certificate? [y/n]:y


1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated
$ 

Again Name/Common Name should be changed from "changeme" to something useful.

Note: Watch if database was successfully updated and certificate signed (last lines of output). It will prevent you from receiving strange errors later.

Generate client certificate

Client certificate is generated in same way as server:

$ ./build-key client
Generating a 2048 bit RSA private key
.............................................+++
.+++
writing new private key to 'client.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [SK]:
State or Province Name (full name) [SK]:
Locality Name (eg, city) [Bratislava]:
Organization Name (eg, company) [My company Name]:
Organizational Unit Name (eg, section) [MikroTik OVPN]:
Common Name (eg, your name or your server's hostname) [client]:
Name [changeme]:client
Email Address [myself@mydomain.tld]:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
Using configuration from /home/lukas/Desktop/a/ovpn/openssl-1.0.0.cnf
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
countryName           :PRINTABLE:'SK'
stateOrProvinceName   :PRINTABLE:'SK'
localityName          :PRINTABLE:'Bratislava'
organizationName      :PRINTABLE:'My company Name'
organizationalUnitName:PRINTABLE:'MikroTik OVPN'
commonName            :PRINTABLE:'client'
name                  :PRINTABLE:'client'
emailAddress          :IA5STRING:'myself@mydomain.tld'
Certificate is to be certified until Oct  4 18:55:35 2023 GMT (3650 days)
Sign the certificate? [y/n]:y


1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated
$ 

Note: Watch if database was successfully updated and certificate signed (last lines of output). It will prevent you from receiving strange errors later.

Content of CA directory

ca.key - private CA Certificate key. Keep this file in safe

ca.crt - CA Certificate key.

server.domain.tld.crt
server.domain.tld.key - public and private key for OVPN server

client.crt
client.key - public and private key for OVPN client

dh2048.pem - Diffie–Hellman key for OpenVPN server on Linux

Note: "public" is only name of role. All files should be handled with care and keep in safe. To keep overall integrity of network it is good practice to follow these rules:

  • ca.key is key used to generate all other keys. Protect it in most possible paranoid level. If necessary then store and access it on separate computer with no access to network of any kind
  • protect ca.key with strong password and also all other keys if necessary
  • upload only required keys to each device (e.g. don't upload all of them to each device)

For me my VPN network is important so I'll generate all keys in memory (in /dev/shm or other FS mounted as tmpfs). During this operation I'll disable swap and after I generate all required key's I'll destroy ca.key. Keys are small but there is still possibility that they will be swapped out by kernel. Trace of them can remain on system for some time if they was stored in swap even if keys are removed on OS level.

CRL verification (optional)

It is good practice to review certificate after issuing it and this review also show if CRL is in place. Following command show details about certificate in "human" readable form:

openssl x509 -in keys/ca.crt -noout -text

There should be following part:

X509v3 extensions:
    X509v3 CRL Distribution Points: 

        Full Name:
          URI:http://www-1.example.com/crl.pem

        Full Name:
          URI:http://www-2.example.com/crl.pem

Note: repeat this step for all certificates (server + client)

CRL file (optional)

At this point only one step is missing for certificates. File crl.pem is not generated automatically by commands used in previous steps.

There are two ways how to get crl.pem:

  • use openssl directly and generate empty one
  • use easy-rsa and revoke certificate

I'll describe second approach. This ensure that certification revoke procedure is working fine.

First of all we need "dummy" certificate:

./build-key dummy

This "dummy" certificate is clearly marked as "V" in keys/index.txt. This mean it is valid so lets revoke it:

$ ./revoke-full dummy
Using configuration from /home/lukas/Desktop/ovpn/openssl-1.0.0.cnf
Revoking Certificate 07.
Data Base Updated
Using configuration from /home/lukas/Desktop/ovpn/openssl-1.0.0.cnf
dummy.crt: <details stripped>
error 23 at 0 depth lookup:certificate revoked

Error on last line is certificate validation. Failure is expected as certificate get revoked. Certificate is now marked as "R" (revoked in) keys/index.txt. As a part of certificate revocation procedure crl.pem is updated or generated. This file can be now uploaded to web server acting as CRL distribution point.

Note: make sure you upload modified crl.pem after each certificate revoke action to CRL distribution point (web server).

OVPN Common configuration

Time / Date

First of all don't try to synchronize time over VPN connection. Most of the MikroTik RouterBoard devices don't have RTC (real time clock) and depend on NTP. If you try to synchronize time over VPN then you end up with chicken / egg problem (e.g. for successful VPN connection correct time is necessary but it is not possible to synchronize time because VPN connection is not running). Please note that MikroTik is ignoring time source with LI_ALARM set. If your NTP source was restarted recently then it will take a moment to synchronize time.

Select time source reachable without VPN and set it using following command. I am using my own NTP server, so for me Secondary NTP is optional. If You don't have NTP server then select two reliable servers in different location (for locations in Slovakia I am configuring Primary as stratum 1 NTP server in Bratislava and Secondary of same stratum in Prague)

/system ntp client
set enabled=yes primary-ntp=<IP> secondary-ntp=<IP>

Firewall rules

Network interfaces for VPN connection are not present on system all the time due to way how RouterOS handle VPN connection. In case of problem VPN interface disappear, IP address is deconfigured and VPN traffic can hit default gateway. Even if the connection can't be established with such a condition it is security risk. I don't accept that my internal traffic can go outside of my network. To prevent VPN traffic going unencrypted to default gateway I added following rule to each device acting as gateway for network (i am using 10.0.0.0/8 network for internal IP allocation and in this particular example "eth10-wan" is my "out" interface to internet. You should modify it to reflect your network configuration)

/ip firewall filter
add action=reject chain=forward dst-address=10.0.0.0/8 out-interface=eth10-wan

Note: for security purpose this rule should be first in firewall configuration. e.g. it is evaluated at first for each packet.

On other hand it is good practice to have similar rule in firewall. In case of problem with connection well explainable ICMP message is returned:

 $ ping -c 3 10.10.10.10
PING 10.10.10.10 (10.10.10.10) 56(84) bytes of data.
From 10.1.1.1: icmp_seq=1 Destination Net Unreachable
From 10.1.1.1: icmp_seq=2 Destination Net Unreachable
From 10.1.1.1: icmp_seq=3 Destination Net Unreachable

--- 10.10.10.10 ping statistics ---
3 packets transmitted, 0 received, +3 errors, 100% packet loss, time 1999ms
$

OVPN Server configuration

Import certificates (server)

As mentioned earlier certificates are required. MikroTik device acting as OVPN server require following files: ca.crt, server.domain.tld.crt and server.domain.tld.key. Once uploaded they can be imported using following command:

> /certificate 
/certificate> import passphrase="" file-name=ca.crt 
     certificates-imported: 1
     private-keys-imported: 0
            files-imported: 1
       decryption-failures: 0
  keys-with-no-certificate: 0

/certificate> import passphrase="" file-name=server.domain.tld.crt 
     certificates-imported: 1
     private-keys-imported: 0
            files-imported: 1
       decryption-failures: 0
  keys-with-no-certificate: 0

/certificate> import passphrase="" file-name=server.domain.tld.key 
     certificates-imported: 0
     private-keys-imported: 1
            files-imported: 1
       decryption-failures: 0
  keys-with-no-certificate: 0

/certificate> 

Note: If you don't select pass phrase then MikroTik ask for it, even if key is not encrypted. In that case just hit return.

Normally certificates get name "certX" where X represent sequential number. For better handling i recommend to rename them. Details can be checked by /certificate print and then certificates can be renamed using:

/certificate
set cert1 name=CA
set cert2 name=server.domain.tld 

Note: CRL is indicated in /certificate print by L in front of certificate.

Cleanup

As of ROS 6.6 certificates are stored in trust store. It is not necessary to keep them also as files in ROS file system. I recommend to remove them from file system once they are imported to trust store. It is easy to remove "files" from router:

/file remove ca.crt
/file remove server.domain.tld.crt
/file remove server.domain.tld.key

After that verify file listing by issuing /file print

Allow VPN connection to pass through firewall

Each my firewall is configured in way that i start with "drop all" for everything and then add rules for allowed communication. I recommend this approach as unclasified (unknown) traffic is simply dropped. If some connection is required then it should be explicitly defined. VPN connection should be allowed by using this rule (or something similar):

/ip firewall filter
add chain=input comment=OpenVPN dst-port=1194 protocol=tcp

Note: this rule should be placed on reasonable position in firewall rules. Exact position depend on specific implementation. Use move after adding this rule or place it directly on correct position in firewall rule list.

Create VPN profile

MikroTik use little bit specific implementation of profiles. This can be confusing for beginners but it is not so hard to understand it. Basically there are 3 sources of configuration for connection:

  • interface default profile
  • connection specific profile
  • user specific entry

Some configuration attributes are present on all levels, for example remote IP address (pool). You can specify it per user, per profile or per interface. Some configuration attributes are present only on specific level, for example encryption type. It don't make sense to configure encryption per user as security should be consistent (and also it is not possible technically).

So first of all we need VPN profile, which will set defaults for incoming connections.

/ppp profile
add change-tcp-mss=yes dns-server=<dns ip> local-address=<vpn ip> name=OVPN-server \
    only-one=yes use-compression=no use-encryption=required use-mpls=no \
    use-vj-compression=no

Note1: Van Jacobson compression is not recommended, so enable it only if you really need it.

Note2: Enable compression only if you are transferring compressible data. (HTTPs, SSH, video files, JPG images, ... can't be compressed so it waste CPU cycles on router board.)

Note3: local-address is address of VPN interface, not address of physical interface where OVPN server is listening

Add VPN user (client)

Password is required for successful connection so it is necessary to create user entry in security table. If static allocation is in place (as in this example) then /ppp secret is also place for user->IP definition.

/ppp secret
add name=<client> password=<password> profile=OVPN-server \
    remote-address=<VPN client IP> service=ovpn 

Note: remote-address is IP address provided for client to be used on VPN interface, not IP address of client itself.

Fix name of user in configuration (interface name)

VPN interface is also managed dynamically on OVPN server. This mean that connected user get auto-generated interface name. If per user (connection) filtering is required then it is necessary to "fix" name of interface on server. In this example every time "client" connect to OVPN Server interface with name "vpn-client" is associated to it's connection:

/interface ovpn-server
add name=vpn-client user=client 

I am using following naming scheme:

/interface ovpn-server
add name=vpn-from-lukas user=lukas

Setup VPN server and enable it

MikroTik support 4 types of encryption (ROS 6.18). You can select one or more from following list: aes128, aes192, aes256, blowfish128. If server allow more that one cipher then it is up to client to select one. Initially I configured aes256 only, but I faced performance problem with it. While I was doing research I found that none of them are "un-secure" and preference is more marketing than real reasons. Therefore i returned configuration back to default (blowfish128,aes128) which gained me additional ~6Mbit/s on line. It is not so much but on long running job it make significant difference.

/interface ovpn-server server
set auth=sha1 certificate=server.domain.tld cipher=blowfish128,aes128 \
    default-profile=OVPN-server enabled=yes keepalive-timeout=60 \
    max-mtu=1400 require-client-certificate=yes

Note1: Encryption is enforced via profile added earlier (OVPN-server).

Note2: default option for VPN type is "ip" in most situation it is better to use L3 VPN connection rather that L2 called "ethernet". This article describe "ip" connection using VPN.

Grant access for VPN clients through firewall

Depending on firewall configuration it is necessary to add rules also for connected VPN clients. This may vary depending on configuration so i keep this step only as note here.

OVPN Client configuration

Import certificates (client)

Client need following files: ca.crt, client.crt and client.key. Keys can be imported in same way as on server:

> /certificate
/certificate> import passphrase="" file-name=ca.crt 
     certificates-imported: 1
     private-keys-imported: 0
            files-imported: 1
       decryption-failures: 0
  keys-with-no-certificate: 0

/certificate> import passphrase="" file-name=client.crt 
     certificates-imported: 1
     private-keys-imported: 0
            files-imported: 1
       decryption-failures: 0
  keys-with-no-certificate: 0

/certificate> import passphrase="" file-name=client.key 
     certificates-imported: 0
     private-keys-imported: 1
            files-imported: 1
       decryption-failures: 0
  keys-with-no-certificate: 0

/certificate>

Renaming of certificates is also same as on server:

/certificate
set cert1 name=CA
set cert2 name=client

Configure client

Configuration on client is inhered from server (options which can be signaled from server). Profile is optional, but to be 100% sure that configuration is same i created profile also on client:

/ppp profile
add change-tcp-mss=yes name=OVPN-client use-compression=no \
    use-encryption=required use-mpls=no use-vj-compression=no

VPN connection is finalized by adding OVPN client details:

/interface ovpn-client
add add-default-route=no auth=sha1 certificate=<client> cipher=blowfish128 \
    connect-to=<OVPN server wan IP> disabled=no name=vpn-to-server \
    password=<password> profile=OVPN-client user=<client>

Note1: by default interface is disabled, so add disabled=no or enable interface after adding it.

Note2: select chiper according to your preference.

Verify connection

At this point VPN connection should be established and working simple check can be performed by monitor command:

/interface ovpn-client
monitor 0

Output of it should show connected and correct "encoding" (encryption type). For example like this:

    status: connected
    uptime: 1m24s
  encoding: AES-256-CBC/SHA1
       mtu: 1500

Details can be checked also in server and client logs. It should contain something like this:

  • Server:
ovpn,info TCP connection established from 192.168.56.102 
ovpn,info <ovpn-0>: dialing... 
ovpn,info <ovpn-0>: using encoding - AES-256-CBC/SHA1 
ovpn,info vpn-client: connected 
ovpn,info,account client logged in, 192.168.1.2
  • Client:
ovpn,info vpn-to-server: initializing... 
ovpn,info vpn-to-server: dialing... 
ovpn,info vpn-to-server: using encoding - AES-256-CBC/SHA1 
ovpn,info vpn-to-server: connected 

There should be IP address dynamically assigned by server on client:

/ip address print

Flags: X - disabled, I - invalid, D - dynamic 
 #   ADDRESS            NETWORK         INTERFACE                                                      
 0   192.168.56.102/24  192.168.56.0    ether1                                                         
 1 D 192.168.1.2/32     192.168.1.1     vpn-to-server                                                  

In this example 192.168.56.102 was set manually as interface address and 192.168.1.2 dynamically by VPN server on VPN interface (client side).