A flexible OpenVPN plugin to handle user using a MySQL database backend
Switch branches/tags
Nothing to show
Clone or download
chantra remove unused variable
escaped_username_length and escaped_password_length are not used (they are assigned the returned value of `mysql_real_escape_string` but we never used that value. It is safe to just remove those variables
Latest commit 7f4a33e Jan 10, 2014

README

Dependencies:
------------

Debian:
libmysql-client-dev
libtool

CentOS:
mysql-devel
libtool

Installation:
-------------
./autogen.sh
./configure
make
make install

Optionally, if you only ran make, you can copy the .so files
from src/.libs/ like:
$ sudo mkdir /etc/openvpn/mysql-auth
$ sudo cp -a src/.libs/libopenvpn-mysql-auth.so* /etc/openvpn/mysql-auth/

Then add the following line to your openvpn config file:
plugin /etc/openvpn/mysql-auth/libopenvpn-mysql-auth.so -c /etc/openvpn/mysql-auth/mysql-auth.conf

Options:
--------
1) openvpn config file
You can pass options to libopenvpn-mysql-auth.so:
-c /path/to/config    default: mysql-auth.conf within openvpn directory (will depend on --cd argument passed to openvpn)
-d                    enable debugging info, default: no


2) mysql-auth.conf
The configuration must follow this rule:
option value

Available Options:
++++++++++++++++++
DB connection infos:
Required:
---------
hostname    hostname to connect to (or "none" if using unix socket)
port        port to connect to, default 0 (will connect to MySQL standard port)
login       username used to establish connection to DB
password    password used to establish connection to DB
db          database to conenct to
s_path      path to unix socket (or "none" if using TCP connection)

either hostname or s_path can be omitted, but at least one of them need to be set.


Once this is done, you will need to set the following option to authenticate users, check access permissions,
set up firewall rule, log connections....

There are all optional, and will enable different features if there are set or not.
In order to make this plugin as flexible as possible so it can adapt to any existing databases,
*expandable variable* are available, see below

Authentication:
---------------
User/Password authentication is done if at least *auth_user_pass_verify_query* is set up.
It goes through 3 steps:
  - check user/password
  - check user is allowed to access (if auth_user_pass_verify_user_access_query exists)
  - if not yet allowed, check if groups the user belong to is allowed to access
        (auth_user_pass_verify_group_access_query)
* auth_user_pass_verify_query     
                                Query to check if user/password matches. To succeed, 1 and only
                                1 row must be returned. The value of 1st field in row will be used
                                as {{user_id}} expandable variable
    ex: SELECT id FROM users WHERE username={{escaped_username}} AND password=SHA1('{{escaped_password}}')

* auth_user_pass_verify_user_access_query
                                After a user is authenticated, this query will check if the user is 
                                allowed to access the service.
                                If omitted, no user_access check will be performed.
                                Access will be granted if at least 1 row is returned
    ex: SELECT access_id FROM user_access WHERE user_access.user_id = {{user_id}}

* auth_user_pass_verify_group_access_query
                                Same as above, but for groups. Will only be called:
                                if defined and user access did not succeed
                                OR
                                if defined and user_access_query was not defined


Packet Filtering:
----------------
Packet filtering can be enabled in 2 manners:
1) using a single query to get default clients/subnets rules and clients/subnets rules
2) using a query for each item needed, namely default clients rules, default subnets rules, clients rules, subnets rules

1) is used if enable_pf_user_rules_query or enable_pf_group_rules_query is defined
2) is used if:
 - enable_pf_clients_user_default_rules_query, enable_pf_subnets_user_default_rules_query, enable_pf_clients_user_rules_query AND enable_pf_subnets_user_rules_query are defined
 OR
 - enable_pf_clients_group_default_rules_query, enable_pf_subnets_group_default_rules_query, enable_pf_clients_group_rules_query AND enable_pf_subnets_group_rules_query are defined  

If either enable_pf_user_rules_query or enable_pf_group_rules_query is defined, query from 2) will not be tried.

When resolving the rules that apply to a specific VPN connection, during auth-user-pass-verify, the plugin will first attempt to get the rules from the *user* rules,
If it did not succeed, the plugin will try the rules for the *group* .
Finally, if after this, the plugin did not manage to get rules for that specific connections, the default values from
default_pf_rules_clients, default_pf_rules_subnets, pf_rules_clients, pf_rules_subnets will be used.
By default, these options have the following default:
default_pf_rules_clients (DROP)
default_pf_rules_subnets (DROP)
pf_rules_clients          (NULL)
pf_rules_subnets          (NULL)

Here are the options that are available for packet filtering:
- Service wide:
* default_pf_rules_clients
                          If no default client behaviour for this specific connection
                          could be found, this value will be used.
                          Accepted values: ACCEPT,DROP
                          Default: DROP
* default_pf_rules_subnets
                          If no default subnets behaviour for this specific connection
                          could be found, this value will be used.
                          Accepted values: ACCEPT,DROP
                          Default: DROP
* pf_rules_clients
                          If no clients rules for this specific connection could be found this will be used
                          Accepted value: any text that follow Packet Filtering grammar (see below)
                          Default: NULL
* pf_rules_subnets
                          If no subnets rules for this specific connection could be found this will be used
                          Accepted value: any text that follow Packet Filtering grammar (see below)
                          Default: NULL

- Single query
enable_pf_user_rules_query
                          Query that will retrieve PF rules for that connection.
                          The 4 first fields of MySQL results will be used and must follow
                          this order: default_clients_rule, default_subnets_rule, clients_rules, subnets_rules
              Ex: SELECT f.default_clients, f.default_subnets, f.rules_clients, f.rules_subnets FROM firewall_rules f, user_firewall_rules uf WHERE f.firewall_rule_id=uf.firewall_rule_id AND uf.user_id={{user_id}} 
enable_pf_group_rules_query
                          Query that will retrieve PF rules for that connection.
                          The 4 first fields of MySQL results will be used and must follow
                          this order: default_clients_rule, default_subnets_rule, clients_rules, subnets_rules
              Ex: SELECT f.default_clients, f.default_subnets, f.rules_clients, f.rules_subnets FROM firewall_rules f, group_firewall_rules gf, group_users gu WHERE f.firewall_rule_id=gf.firewall_rule_id AND gf.group_id=gu.group_id AND gu.user_id={{user_id}} 

default_clients and default_subnets MUST BE ACCEPT or DROP (will default to drop if unknown value)
clients_rules and subnets_rules must follow Packet Filtering grammar (see below). If NULL, no rules will be used.



- Multiple queries
In multiple query mode, each query must return its value in first row, first field.
The options are:
* enable_pf_clients_user_default_rules_query
  ex: SELECT f.default_clients FROM firewall_rules f, user_firewall_rules uf WHERE f.firewall_rule_id=uf.firewall_rule_id AND uf.user_id={{user_id}} 
* enable_pf_clients_user_rules_query
  ex: SELECT f.rules_clients FROM firewall_rules f, user_firewall_rules uf WHERE f.firewall_rule_id=uf.firewall_rule_id AND uf.user_id={{user_id}}
* enable_pf_subnets_user_default_rules_query
  ex: SELECT f.default_subnets FROM firewall_rules f, user_firewall_rules uf WHERE f.firewall_rule_id=uf.firewall_rule_id AND uf.user_id={{user_id}}
* enable_pf_subnets_user_rules_query
  ex: SELECT f.rules_subnets FROM firewall_rules f, user_firewall_rules uf WHERE f.firewall_rule_id=uf.firewall_rule_id AND uf.user_id={{user_id}}
AND
* enable_pf_clients_group_default_rules_query
  ex: SELECT f.default_clients FROM firewall_rules f, group_firewall_rules gf, group_users gu WHERE f.firewall_rule_id=gf.firewall_rule_id AND gf.group_id=gu.group_id AND gu.user_id={{user_id}}
* enable_pf_clients_group_rules_query
  ex: SELECT f.rules_clients FROM firewall_rules f, group_firewall_rules gf, group_users gu WHERE f.firewall_rule_id=gf.firewall_rule_id AND gf.group_id=gu.group_id AND gu.user_id={{user_id}}
* enable_pf_subnets_group_default_rules_query
  ex: SELECT f.default_subnets FROM firewall_rules f, group_firewall_rules gf, group_users gu WHERE f.firewall_rule_id=gf.firewall_rule_id AND gf.group_id=gu.group_id AND gu.user_id={{user_id}}
* enable_pf_subnets_group_rules_query
  ex: SELECT f.rules_subnets FROM firewall_rules f, group_firewall_rules gf, group_users gu WHERE f.firewall_rule_id=gf.firewall_rule_id AND gf.group_id=gu.group_id AND gu.user_id={{user_id}}

NOTE: At most one row should be returned. In case more than one row is returned, the result will be discarded.
That would mean that in case of "group" rules, the user can belong to only 1 group

Expandable Variables:
+++++++++++++++++++++
Expandable variable should be surrounded by double curly brackets "{{var}}" in the queries, the following
are *potentially* available depending on the callback being called:

time_now            actual unix timestamp (always available)
username            username sent by client, start to be available when OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY
                    callback is triggered
password            only available in OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY
escaped_username
escaped_password    same as above, but SQL safe
trusted_ip          IP the client is connecting with (available with OPENVPN_PLUGIN_CLIENT_CONNECT)
trusted_port        Port the client is conencting from (available with OPENVPN_PLUGIN_CLIENT_CONNECT)
time_unix           Timestamo at which client successfully connected (available with OPENVPN_PLUGIN_CLIENT_CONNECT)
ifconfig_pool_remote_ip
                    VPN IP given to client (available with OPENVPN_PLUGIN_CLIENT_CONNECT)
time_duration       Time the connection lasted (available with OPENVPN_PLUGIN_CLIENT_DISCONNECT)
bytes_sent          Total number of bytes sent to client during VPN session (available with OPENVPN_PLUGIN_CLIENT_DISCONNECT)
bytes_received      Total number of bytes received from client during VPN session (available with OPENVPN_PLUGIN_CLIENT_DISCONNECT)

More detailed info from:
man openvpn
in section "Environmental Variables"


Callback calls:
+++++++++++++++

To check in what order the callback will be called, see 
http://svn.openvpn.net/projects/openvpn/branches/BETA21/openvpn/openvpn-plugin.h

Packet Filtering grammar (from openvpn-plugin.h)
------------------------------------------------
 * Packet filter file grammar:
 *
 * [CLIENTS DROP|ACCEPT]
 * {+|-}common_name1
 * {+|-}common_name2
 * . . .
 * [SUBNETS DROP|ACCEPT]
 * {+|-}subnet1
 * {+|-}subnet2
 * . . .
 * [END]
 *
 * Subnet: IP-ADDRESS | IP-ADDRESS/NUM_NETWORK_BITS
 *
 * CLIENTS refers to the set of clients (by their common-name) which
 * this instance is allowed ('+') to connect to, or is excluded ('-')
 * from connecting to.  Note that in the case of client-to-client
 * connections, such communication must be allowed by the packet filter
 * configuration files of both clients.
 *
 * SUBNETS refers to IP addresses or IP address subnets which this
 * instance may connect to ('+') or is excluded ('-') from connecting
 * to.
 *
 * DROP or ACCEPT defines default policy when there is no explicit match
 * for a common-name or subnet.  The [END] tag must exist.  A special
 * purpose tag called [KILL] will immediately kill the client instance.
 * A given client or subnet rule applies to both incoming and outgoing
 * packets.