When logging into OpenVPN AS via it's web based frontend it automatically connects by instructing the VPN client to do so. I wondered how exactly it was doing this - so running Fiddler I observed it was using some kind of RPC call over HTTPS (on port 946) to a server named openvpn-client.openvpn.yourdomain.com - which funnily enough is a lookback address (172.27.232.2) on the local machine.
It appears that when installing the OpenVPN client on a users computer it add the host to the hosts file.
So me being me - I was not paticulary happy with the OpenVPN AS web-based interface and it's somewhat lack of aesthetic appeal I decided to implement by own version - this post briefly describes how the login process works so others can build their own versions if desired.
So on the OpenVPN AS web login - after the user has entered thier credentials - the browser sends an RPC call as follows to intruct the client to connect to the VPN server in non-interacrtive mode:
HTTP POST to https://openvpn-client.openvpn.yourdomain.com:946/RPC2
NOTE: The 'X-OpenVPN' header MUST be present in all RPC requests and must equal to '1'.
Headers:
Host: openvpn-client.openvpn.yourdomain.com:946
Connection: keep-alive
Content-Length: 784
X-OpenVPN: 1
Origin: https://openvpn-client.openvpn.yourdomain.com:946
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.116 Safari/537.36
Content-Type: text/xml
Accept: */*
DNT: 1
Referer: https://openvpn-client.openvpn.yourdomain.com:946/?_ts=1456406537206
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.8
Cookie: __utma=158273187.1431551106.1455640284.1456310756.1456331497.4; __utmc=158273187; __utmz=158273187.1456310756.3.2.utmcsr=google|utmccn=(organic)|utmcmd=organic|utmctr=(not%20provided)
<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>Connect</methodName>
<params>
<param>
<value>
<struct>
<member>
<name>type</name>
<value>
<string>dynamic</string>
</value>
</member>
<member>
<name>profile_id</name>
<value>
<string>openvpn_yourdomain_com_dynamic_p3151</string>
</value>
</member>
<member>
<name>non_interactive</name>
<value>
<boolean>0</boolean>
</value>
</member>
</struct>
</value>
</param>
<param>
<value>
<array>
<data>
<string>STATE</string>
<string>PASSWORD</string>
<string>ACTIVE</string>
<string>CERT_APPROVAL</string>
<string>INFO</string>
<string>CONNECTED_USER</string>
<string>BYTECOUNT</string>
<string>FATAL</string>
<string>SCRIPT</string>
<string>CHALLENGE</string>
<string>DELETE_PENDING</string>
</data>
</array>
</value>
</param>
<param>
<value>
<struct />
</value>
</param>
</params>
</methodCall>
We can replay this with something like Advanced Rest Client addon for chrome.
Response:
<?xml version='1.0'?>
<methodResponse>
<params>
<param>
<value><string>sess_openvpn_yourdomain_com_dynamic_p3151_vmXGe0u27f6ebAGD_1</string></value>
</param>
</params>
</methodResponse>
I beleive this is then returning a session ID that we can then use a reference point for any further RPC's we do.
It then sends a poll with our session ID:
<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>Poll</methodName>
<params>
<param>
<value>
<string>sess_openvpn_yourdomain_com_dynamic_p3151_vmXGe0u27f6ebAGD_1</string>
</value>
</param>
<param>
<value>
<int>10</int>
</value>
</param>
</params>
</methodCall>
We then get a reponse asking us for the credentials:
<?xml version="1.0" encoding="UTF-8"?>
<methodResponse>
<params>
<param>
<value>
<array>
<data>
<value>
<struct>
<member>
<name>status</name>
<value>
<string>need</string>
</value>
</member>
<member>
<name>timestamp</name>
<value>
<int>1456409871</int>
</value>
</member>
<member>
<name>need</name>
<value>
<array>
<data>
<value>
<string>username</string>
</value>
<value>
<string>password</string>
</value>
</data>
</array>
</value>
</member>
<member>
<name>type</name>
<value>
<string>PASSWORD</string>
</value>
</member>
<member>
<name>auth_type</name>
<value>
<string>Dynamic</string>
</value>
</member>
</struct>
</value>
</data>
</array>
</value>
</param>
</params>
</methodResponse>
So we send another RPC this time sending the login details:
<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>SubmitCreds</methodName>
<params>
<param>
<value>
<string>sess_openvpn_mydomain_com_dynamic_p3151_vmXGe0u27f6ebAGD_1</string>
</value>
</param>
<param>
<value>
<struct>
<member>
<name>username</name>
<value>
<string>myuser</string>
</value>
</member>
<member>
<name>password</name>
<value>
<string>SESS_ID_fWrz/SDV511111111ZDA==</string>
</value>
</member>
</struct>
</value>
</param>
<param>
<value>
<string>Dynamic</string>
</value>
</param>
<param>
<value>
<boolean>1</boolean>
</value>
</param>
</params>
</methodCall>
We then need to keep POST'ing the 'POLL' method until we get an XML node == '<string>CONNECTED</string>':
We need to look out (apply error handling) for '<string>pyovpn.client.asxmlcli.AuthError</string>' - which indicates that there is an authentication problem and as a result the VPN will drop!
We should also look out for '<string>twisted.internet.defer.TimeoutError</string>' which indicates a connection problem e.g. timeout, dns lookup problems and so on.
<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>Poll</methodName>
<params>
<param>
<value>
<string>sess_openvpn_yourdomain_com_dynamic_p3151_vmXGe0u27f6ebAGD_1</string>
</value>
</param>
<param>
<value>
<int>10</int>
</value>
</param>
</params>
</methodCall>
No comments:
Post a Comment