smartvpn-http-hooks 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -0
  3. data/.rspec +1 -0
  4. data/Gemfile +3 -0
  5. data/Gemfile.lock +39 -0
  6. data/LICENSE +19 -0
  7. data/README.md +22 -0
  8. data/bin/smartvpn-activate +6 -0
  9. data/bin/smartvpn-authenticate +8 -0
  10. data/bin/smartvpn-connect +6 -0
  11. data/bin/smartvpn-disconnect +7 -0
  12. data/lib/api/activation.rb +39 -0
  13. data/lib/api/authentication.rb +27 -0
  14. data/lib/api/billing.rb +44 -0
  15. data/lib/api/connect.rb +21 -0
  16. data/lib/api/connection.rb +66 -0
  17. data/lib/api/disconnect.rb +21 -0
  18. data/lib/exceptions/option_not_found.rb +2 -0
  19. data/lib/openvpn_password_authenticator.rb +18 -0
  20. data/lib/option/base.rb +30 -0
  21. data/lib/option/i2p.rb +28 -0
  22. data/lib/option/proxy.rb +66 -0
  23. data/lib/option/repository.rb +18 -0
  24. data/lib/signer.rb +7 -0
  25. data/lib/smartvpn-http-hooks.rb +19 -0
  26. data/lib/system/openvpn_status.rb +19 -0
  27. data/lib/system/openvpn_status_log_parser.rb +30 -0
  28. data/lib/system/openvpn_status_log_reader.rb +29 -0
  29. data/smartvpn-http-hooks.gemspec +17 -0
  30. data/spec/fixtures/active_connections.txt +10 -0
  31. data/spec/fixtures/multiple_connections.txt +11 -0
  32. data/spec/lib/api/activation_spec.rb +30 -0
  33. data/spec/lib/api/authentication_spec.rb +29 -0
  34. data/spec/lib/api/billing_spec.rb +71 -0
  35. data/spec/lib/api/connect_spec.rb +40 -0
  36. data/spec/lib/api/connection_spec.rb +59 -0
  37. data/spec/lib/api/disconnect_spec.rb +40 -0
  38. data/spec/lib/openvpn_password_authenticator_spec.rb +52 -0
  39. data/spec/lib/option/base_spec.rb +26 -0
  40. data/spec/lib/option/i2p_spec.rb +19 -0
  41. data/spec/lib/option/proxy_spec.rb +32 -0
  42. data/spec/lib/option/repository_spec.rb +25 -0
  43. data/spec/lib/signer_spec.rb +21 -0
  44. data/spec/lib/system/openvpn_status_log_parser_spec.rb +25 -0
  45. data/spec/lib/system/openvpn_status_log_reader_spec.rb +33 -0
  46. data/spec/lib/system/openvpn_status_spec.rb +22 -0
  47. data/spec/spec_helper.rb +9 -0
  48. metadata +135 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 452d36d90e1e0b42c1115a570b99d67a73aad680
4
+ data.tar.gz: 3bac4bacc5059ac0045b4c73517eceaf0e0b9e1c
5
+ SHA512:
6
+ metadata.gz: e3d5f961cdea917deb8860d5e2132cafc713f36e8ff128fb25dbc0216fd4ae35059a452cfb0dfdcfda37bf22411487d03f2b31690be43981b426853a8a6e9b85
7
+ data.tar.gz: a4cb8b5fd0f11ce8955eb0633742de163a437ec91bea0d542868936d07665d8ae0bc2615062040501a91e2489a7640d2553bf2a1f29312d83fe2f92d9e00474b
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ .idea/
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,39 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ smartvpn-http-hooks (1.0.7)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ addressable (2.3.5)
10
+ crack (0.4.2)
11
+ safe_yaml (~> 1.0.0)
12
+ diff-lcs (1.2.5)
13
+ metaclass (0.0.4)
14
+ mocha (1.0.0)
15
+ metaclass (~> 0.0.1)
16
+ rspec (2.14.1)
17
+ rspec-core (~> 2.14.0)
18
+ rspec-expectations (~> 2.14.0)
19
+ rspec-mocks (~> 2.14.0)
20
+ rspec-core (2.14.8)
21
+ rspec-expectations (2.14.5)
22
+ diff-lcs (>= 1.1.3, < 2.0)
23
+ rspec-mocks (2.14.6)
24
+ safe_yaml (1.0.1)
25
+ webmock (1.17.4)
26
+ addressable (>= 2.2.7)
27
+ crack (>= 0.3.2)
28
+
29
+ PLATFORMS
30
+ ruby
31
+
32
+ DEPENDENCIES
33
+ mocha
34
+ rspec
35
+ smartvpn-http-hooks!
36
+ webmock
37
+
38
+ BUNDLED WITH
39
+ 1.17.1
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2015 SmartVPN.biz
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,22 @@
1
+ # smartvpn-http-hooks
2
+
3
+ This ruby gem is wrapper for OpenVPN server daemon.
4
+
5
+ The main purpose of this gem - is to interact with remote API, located at billing system.
6
+
7
+ This allows to authenticate user, track his connects/disconnects, apply hooks on connect/disconnect.
8
+
9
+ ### Hooks
10
+
11
+ Built-in hooks allow to implement following features for specific user:
12
+
13
+ * Automatic routing of I2P sites through I2P router
14
+ * Automatic routing of TOR sites through TOR router
15
+ * Applying selected proxy for all HTTP traffic of specific user
16
+
17
+ ### Disclaimer
18
+
19
+ This is OpenSource, Free software. You may use it anyway you want. It is provided AS IS.
20
+
21
+ Check out LICENSE file for more info.
22
+
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require 'smartvpn-http-hooks'
4
+
5
+ api = Api::Activation.new
6
+ api.activate
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require 'smartvpn-http-hooks'
4
+
5
+ api_adapter_class = Api::Authentication
6
+
7
+ authenticator = OpenvpnPasswordAuthenticator.new(ARGV, api_adapter_class)
8
+ authenticator.authenticate
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require 'smartvpn-http-hooks'
4
+
5
+ connection = Api::Connect.new
6
+ connection.invoke
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require 'smartvpn-http-hooks'
4
+
5
+ connection = Api::Disconnect.new
6
+ connection.invoke
7
+
@@ -0,0 +1,39 @@
1
+ require File.expand_path('../billing', __FILE__)
2
+
3
+ module Api
4
+ class Activation < Billing
5
+
6
+ def activate
7
+ if activated_successfully?
8
+ save_auth_key
9
+ else
10
+ raise "Can't activate server at billing"
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def activated_successfully?
17
+ success_api_call?
18
+ end
19
+
20
+ def save_auth_key
21
+ key = JSON.parse(api_call_result.body)["auth_key"]
22
+ File.open('/etc/openvpn/auth_key', 'w') { |file| file.write(key) }
23
+ end
24
+
25
+ def signed_data
26
+ data
27
+ end
28
+
29
+ def data
30
+ {
31
+ hostname: hostname
32
+ }
33
+ end
34
+
35
+ def action
36
+ "activate"
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,27 @@
1
+ require File.expand_path('../billing', __FILE__)
2
+
3
+ module Api
4
+ class Authentication < Billing
5
+ def initialize(login, password)
6
+ @login, @password = login, password
7
+ end
8
+
9
+ def valid_credentials?
10
+ success_api_call?
11
+ end
12
+
13
+ private
14
+
15
+ def data
16
+ {
17
+ login: @login,
18
+ password: @password,
19
+ hostname: hostname
20
+ }
21
+ end
22
+
23
+ def action
24
+ "auth"
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,44 @@
1
+ require "socket"
2
+ require 'net/http'
3
+ require 'json'
4
+ require File.expand_path('../../signer', __FILE__)
5
+
6
+ module Api
7
+ class Billing
8
+ API_HOST = '/etc/openvpn/API_HOST'
9
+ KEY_PATH = '/etc/openvpn/auth_key'
10
+
11
+ def host_with_port
12
+ host = File.read(API_HOST)
13
+ "http://#{host.strip}"
14
+ end
15
+
16
+ def auth_key
17
+ File.read(KEY_PATH)
18
+ end
19
+
20
+ def hostname
21
+ Socket.gethostname
22
+ end
23
+
24
+ def success_api_call?
25
+ api_call_result.code == '200'
26
+ end
27
+
28
+ def response
29
+ JSON.parse(api_call_result.body)
30
+ end
31
+
32
+ def api_call_result
33
+ @api_result ||= Net::HTTP.post_form(uri, signed_data)
34
+ end
35
+
36
+ def uri
37
+ URI("#{host_with_port}/api/#{action}")
38
+ end
39
+
40
+ def signed_data
41
+ data.merge!({ signature: Signer.sign_hash(data, auth_key) })
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,21 @@
1
+ module Api
2
+ class Connect < Connection
3
+ def invoke
4
+ invoke_if_valid_api_call do
5
+ activate_options
6
+ end
7
+ end
8
+
9
+ def action
10
+ 'connect'
11
+ end
12
+
13
+ private
14
+
15
+ def activate_options
16
+ options.each do |code, data|
17
+ data[:option_class].activate(common_name, data[:attributes])
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,66 @@
1
+ require File.expand_path('../billing', __FILE__)
2
+
3
+ module Api
4
+ class Connection < Billing
5
+ def invoke_if_valid_api_call(&block)
6
+ yield if success_api_call?
7
+ trigger_script_return
8
+ end
9
+
10
+ def trigger_script_return
11
+ if success_api_call?
12
+ exit 0
13
+ else
14
+ exit 1
15
+ end
16
+ end
17
+
18
+ def common_name
19
+ response["common_name"]
20
+ end
21
+
22
+ def options
23
+ result = {}
24
+ option_codes.reduce(result) do |options_with_codes, option_code|
25
+ options_with_codes[option_code] =
26
+ {
27
+ option_class: Option::Repository.find_by_code(option_code),
28
+ attributes: attributes_for_option(option_code)
29
+ }
30
+ options_with_codes
31
+ end
32
+ result
33
+ end
34
+
35
+ private
36
+
37
+ def data
38
+ {
39
+ hostname: hostname,
40
+ traffic_in: traffic_in,
41
+ traffic_out: traffic_out,
42
+ login: ENV['common_name']
43
+ }
44
+ end
45
+
46
+ def option_codes
47
+ response["options"]
48
+ end
49
+
50
+ def option_attributes
51
+ response["option_attributes"]
52
+ end
53
+
54
+ def attributes_for_option(code)
55
+ option_attributes[code]
56
+ end
57
+
58
+ def traffic_in
59
+ ENV['bytes_received'] || "0"
60
+ end
61
+
62
+ def traffic_out
63
+ ENV['bytes_sent'] || "0"
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,21 @@
1
+ module Api
2
+ class Disconnect < Connection
3
+ def invoke
4
+ invoke_if_valid_api_call do
5
+ deactivate_options
6
+ end
7
+ end
8
+
9
+ def action
10
+ 'disconnect'
11
+ end
12
+
13
+ private
14
+
15
+ def deactivate_options
16
+ options.each do |code, data|
17
+ data[:option_class].deactivate(common_name, data[:attributes])
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,2 @@
1
+ class OptionNotFound < StandardError
2
+ end
@@ -0,0 +1,18 @@
1
+ class OpenvpnPasswordAuthenticator
2
+ attr_accessor :api
3
+
4
+ def initialize(args, api_adapter_class)
5
+ content = File.read(args[0])
6
+ @login, @password = content.split("\n")
7
+ @api = api_adapter_class.new(@login, @password)
8
+ end
9
+
10
+ def authenticate
11
+ if @api.valid_credentials?
12
+ exit 0
13
+ else
14
+ exit 1
15
+ end
16
+ end
17
+
18
+ end
@@ -0,0 +1,30 @@
1
+ module Option
2
+ class Base
3
+ attr_accessor :common_name, :attributes
4
+
5
+ class << self
6
+ def activate(common_name, attributes={})
7
+ option_object = new(common_name, attributes)
8
+ option_object.activate!
9
+ end
10
+
11
+ def deactivate(common_name, attributes={})
12
+ option_object = new(common_name, attributes)
13
+ option_object.deactivate!
14
+ end
15
+ end
16
+
17
+ def initialize(common_name, attributes)
18
+ @common_name = common_name
19
+ @attributes = attributes
20
+ end
21
+
22
+ def virtual_ip
23
+ System::OpenvpnStatus.current_virtual_address
24
+ end
25
+
26
+ def server_virtual_ip
27
+ System::OpenvpnStatus.server_virtual_address
28
+ end
29
+ end
30
+ end
data/lib/option/i2p.rb ADDED
@@ -0,0 +1,28 @@
1
+ module Option
2
+ class I2p < Base
3
+ def activate!
4
+ add_firewall_routes
5
+ end
6
+
7
+ def deactivate!
8
+ remove_firewall_routes
9
+ end
10
+
11
+ private
12
+
13
+ def add_firewall_routes
14
+ system "/sbin/iptables -t filter -D INPUT -p tcp -m tcp --dport 8118 -j DROP"
15
+ system "/sbin/iptables -t filter -A INPUT -s #{virtual_ip} -p tcp -m tcp --dport 8118 -j ACCEPT"
16
+ system "/sbin/iptables -t filter -A INPUT -p tcp -m tcp --dport 8118 -j DROP"
17
+ system "/sbin/iptables -t nat -A PREROUTING -p udp -m udp -s #{virtual_ip} --dport 53 -j DNAT --to-destination #{server_virtual_ip}:53"
18
+ system "/sbin/iptables -t nat -A PREROUTING -p tcp -m tcp -s #{virtual_ip} --dport 53 -j DNAT --to-destination #{server_virtual_ip}:53"
19
+ true
20
+ end
21
+
22
+ def remove_firewall_routes
23
+ system "/sbin/iptables -D INPUT -s #{virtual_ip} -p tcp -m tcp --dport 8118 -j ACCEPT"
24
+ system "/sbin/iptables -t nat -D PREROUTING -p udp -m udp -s #{virtual_ip} --dport 53 -j DNAT --to-destination #{server_virtual_ip}:53"
25
+ system "/sbin/iptables -t nat -D PREROUTING -p tcp -m tcp -s #{virtual_ip} --dport 53 -j DNAT --to-destination #{server_virtual_ip}:53"
26
+ end
27
+ end
28
+ end