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)
- 1 Before you start
- 2 Generate valid certificates
- 2.1 easy-rsa installation
- 2.2 Prepare CA directory
- 2.3 Variables configuration (e.g. "vars")
- 2.4 Certificate Revocation List (optional)
- 2.5 CA directory cleanup
- 2.6 Generate CA certificate
- 2.7 Generate Diffie–Hellman
- 2.8 Generate server certificate
- 2.9 Generate client certificate
- 2.10 Content of CA directory
- 2.11 CRL verification (optional)
- 2.12 CRL file (optional)
- 3 OVPN Common configuration
- 4 OVPN Server configuration
- 5 OVPN Client configuration
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.
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"):
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.
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="firstname.lastname@example.org" 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
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
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
$ ./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 [email@example.com]: $
I changed server name simply by typing it while tool was asking for it. Other variables are confirmed by hitting return.
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.
$ ./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 [firstname.lastname@example.org]: 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:'email@example.com' 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 [firstname.lastname@example.org]: 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:'email@example.com' 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
- 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:
This "dummy" certificate is clearly marked as "
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>
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:
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.
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
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.)
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
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:
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
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.
At this point VPN connection should be established and working simple check can be performed by
/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:
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
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).