riseup_vpn 0.1.0 → 1.1.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 061471e29bcb71a8ad7d20c0757f445ed026913613deb3a289bde237aa40022e
4
- data.tar.gz: 7a780713819d589fdf07e4abd684c6b341e61e5d0ffdf6ccc331693f35cc5301
3
+ metadata.gz: b9fb7defcb7a949292d44dc949acfdc4524eb599a21ec1c90f5f1680c8b9d0ff
4
+ data.tar.gz: dcbb98a3d236a6bf35d3fab3f073549836e98d976a747a4dd134417f6f3ad43e
5
5
  SHA512:
6
- metadata.gz: ae6548f93e8f3734c6578a0f593b979384735e34d07e84d6ffc6bf71ec741440086e0a480b2e19c7939072ee0876a33bf53b33224c3283e15ca7d913c98bd8d1
7
- data.tar.gz: 6e5c748da5d135578357540b95d0ea97fb8e92287408443dac4acd655da6a8cd8ecd7dcb8bec4cc0944ad419a9eb4770c76447590da7d2927fb8efcab1a62652
6
+ metadata.gz: 01bf804c22e6edde11abfd61073f3a9093d33b9463e05f5456aa66f7537e44e19ef37708979499ca8193b05045439acdba5b3a46337ef4edc219053e9603e373
7
+ data.tar.gz: 219ee6e1de2521e85e94a558e28dda6347b3cb74e288bb293f04a86599975f76e00f4477b1d73493562403971f2e0435ff9fb4bde77404a774a690bd489dbd7f
data/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.1.0] - 2025-04-22
4
+
5
+ ### New features
6
+
7
+ - add ability to import VPN client configs into Network Manager (Linux)
8
+
9
+ ## [1.0.0] - 2025-04-21
10
+
11
+ ### Breaking changes
12
+
13
+ - Namespace changes: `RiseupVpn` -> `RiseupVPN`, `RiseupVpn::Api` -> `RiseupVPN::API` and `Generator` -> `Manager`
14
+ - `#write_files` no longer takes an dir path arg
15
+ - All errors subclass `RiseupVPN::Error`
16
+
17
+ ### New features
18
+
19
+ - upgrade TLS to 1.3, if supported by client
20
+
3
21
  ## [0.1.0] - 2025-04-19
4
22
 
5
23
  - Initial release
data/README.md CHANGED
@@ -3,12 +3,12 @@
3
3
 
4
4
  # RiseupVPN
5
5
 
6
- A tool to create .ovpn ([OpenVPN](https://openvpn.net)) client config files for use with [RiseupVPN](https://riseup.net/vpn). Such files can be imported using, for example, the Debian [openvpn NetworkManager plugin](https://packages.debian.org/search?keywords=network-manager-openvpn). You will then be able to route your network connection through any of Riseup's 20+ VPN gateways! Screenshot below shows Linux Mint's Network Manager panel applet with an active VPN connection to RiseupVPN's Seattle gateway.
7
-
8
- This project was inspired by [this](https://sizeof.cat/project/riseupvpn-to-openvpn) very excellent web tool.
6
+ A tool to create .ovpn ([OpenVPN](https://openvpn.net)) client config files for use with [RiseupVPN](https://riseup.net/vpn). Such files can be imported using, for example, the Debian [openvpn NetworkManager plugin](https://packages.debian.org/search?keywords=network-manager-openvpn). You will then be able to route your network connection through any of Riseup's 20+ VPN gateways! The screenshot below shows Linux Mint's Network Manager panel applet with an active VPN connection to RiseupVPN's Seattle gateway.
9
7
 
10
8
  ![image info](assets/vpns.png)
11
9
 
10
+ This project was inspired by [this](https://sizeof.cat/project/riseupvpn-to-openvpn) very excellent web tool.
11
+
12
12
  ## Installation
13
13
 
14
14
  Install the gem and add to the application's Gemfile by executing:
@@ -28,27 +28,36 @@ gem install riseup_vpn
28
28
  ```ruby
29
29
  require 'riseup_vpn'
30
30
 
31
- generator = RiseupVpn::Generator.new
32
- # => instance_of RiseupVpn::Generator
31
+ @manager = RiseupVPN::Manager.new # takes an optional positional arg for the dir path. Default is '~/.config/riseup_vpn'
32
+ # => instance_of RiseupVPN::Manager
33
33
 
34
- generator.ovpns
34
+ @manager.ovpns
35
35
  # => {"vpn01-sea.riseup.net" => "client\ntls-client\ndev tun\nproto udp\nremote 204.13.164.252 1194 ...
36
36
 
37
- generator.ovpns.size
37
+ @manager.ovpns.size
38
38
  # => 21
39
39
 
40
- RiseupVpn::Generator::OPTS # returns a list of valid options which maybe passed at initialization.
41
- # => {proto: ["udp", "tcp"]} # e.g. RiseupVpn::Generator.new(proto: 'tcp') default is 'udp'
40
+ RiseupVPN::Manager::OPTS # returns a list of valid options which maybe passed as an optional hash at initialization.
41
+ # => {proto: ["udp", "tcp"]} defaults are the first values e.g. 'udp' for :proto
42
+ # For example; RiseupVPN::Manager.new('/some/path', proto: 'tcp')
42
43
 
43
- generator.write_files '~/.config/riseup_vpn' # dir must exist
44
+ @manager.write_files # (over)writes the VPN client config files to the dir path
44
45
  ```
45
- The last command creates the following files in the chosen directory:
46
+ The last command creates (or overwrites) the following files:
46
47
  - an .ovpn file for each VPN gateway
47
48
  - the Certificate Authority cert file (ca.crt)
48
49
  - the client private key file (client.key)
49
50
  - the client cert file (client.crt)
50
51
 
51
- Note: OpenVPN requires that cert & key files shared across .ovpn client config files are present in the same directory.
52
+ Note: The last 2 expire after 30 days and therefore need to be updated regularly.
53
+
54
+ Also note: OpenVPN requires that cert & key files shared across .ovpn client config files are present in the same directory.
55
+
56
+ On Linux, you can do this to make Riseup's VPNs available in Network Manager. It uses `nmcli` under the hood:
57
+ ```Ruby
58
+ @manager.import_ovpn_configs # writes the files and (re)imports the VPN configs into Network Manager
59
+ # the client key and cert files are updated too
60
+ ```
52
61
 
53
62
  ## \_why?
54
63
 
data/Rakefile CHANGED
@@ -5,8 +5,6 @@ require 'minitest/test_task'
5
5
  require 'rake/testtask'
6
6
  require 'rubocop/rake_task'
7
7
 
8
- # Minitest::TestTask.create
9
-
10
8
  namespace :test do
11
9
  Rake::TestTask.new do |t|
12
10
  t.name = 'api'
@@ -2,11 +2,9 @@
2
2
 
3
3
  require 'openssl'
4
4
 
5
- module RiseupVpn
5
+ module RiseupVPN
6
6
  # parses ca.cert, client cert/key & gateways data from riseup.net API
7
- module Api
8
- class ApiError < StandardError; end
9
-
7
+ module API
10
8
  PROVIDER = 'https://riseup.net/provider.json' # entry point for API
11
9
  API_URI = 'https://api.black.riseup.net' # 443
12
10
  API_VERSION = '3'
@@ -35,15 +33,15 @@ module RiseupVpn
35
33
  def get(url)
36
34
  URI(url).read
37
35
  rescue NoMethodError, URI::Error
38
- raise ApiError, "Bad URI: '#{url}'"
36
+ raise APIError, "Bad URI: '#{url}'"
39
37
  rescue OpenURI::HTTPError
40
- raise ApiError, "Failed to get url: '#{url}'"
38
+ raise APIError, "Failed to get url: '#{url}'"
41
39
  end
42
40
 
43
41
  def get_json(url)
44
42
  JSON.parse get(url)
45
43
  rescue JSON::ParserError
46
- raise ApiError, "Failed to parse JSON at: '#{url}'"
44
+ raise APIError, "Failed to parse JSON at: '#{url}'"
47
45
  end
48
46
  end
49
47
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RiseupVPN
4
+ class Error < StandardError; end
5
+
6
+ class APIError < Error; end
7
+ end
@@ -1,57 +1,38 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'openssl'
4
3
  require 'yaml'
5
4
 
6
5
  require_relative 'api'
6
+ require_relative 'utils'
7
7
 
8
- module RiseupVpn
9
- # create a .ovpn config file for a gateway
8
+ module RiseupVPN
9
+ # queries API and builds ovpn config files, CA cert and client key & cert
10
10
  class Generator
11
+ include Utils
12
+
11
13
  TEMPLATE = YAML.load_file File.join(__dir__, 'template_ovpn.yml')
12
14
  OPTS = { proto: %w[udp tcp] }.freeze # default is 1st value
13
- LINE_METHODS = %i[remote verify-x509-name].freeze
15
+ LINE_METHODS = %i[remote tls-version-min verify-x509-name].freeze
14
16
 
15
- attr_reader :ovpns
17
+ attr_reader :ovpns, :ca_crt, :client_key, :client_crt
16
18
 
17
19
  def initialize(opts = {})
18
20
  @opts = validate_options opts.transform_keys!(&:to_sym)
19
- @server_config = server_config
20
21
  @template = template
22
+ @server_config = server_config
21
23
  @ovpns = generate_ovpns # hash { gateway_name => string }
22
- end
23
-
24
- def write_files(dir)
25
- dir = File.expand_path dir.to_s
26
- write_ovpn_files dir
27
- write_ca_file dir
28
- write_client_key_and_crt_files dir
29
- rescue Errno::ENOENT, Errno::EACCES
30
- raise ArgumentError, "Dir #{dir} doesn't exist or isn't writable"
24
+ @ca_crt = API.ca_crt
25
+ @client_key, @client_crt = API.client_key_and_crt
31
26
  end
32
27
 
33
28
  private
34
29
 
35
- def write_ovpn_files(dir)
36
- ovpns.each { |k, v| File.write(File.join(dir, "#{k}.ovpn"), v) }
37
- end
38
-
39
- def write_ca_file(dir)
40
- File.write File.join(dir, 'ca.crt'), Api.ca_crt.to_s.chomp
41
- end
42
-
43
- def write_client_key_and_crt_files(dir)
44
- client_key, client_crt = Api.client_key_and_crt
45
- File.write File.join(dir, 'client.key'), client_key.to_s.chomp
46
- File.write File.join(dir, 'client.crt'), client_crt.to_s.chomp
47
- end
48
-
49
30
  def gateways
50
- Api.eip['gateways']
31
+ API.eip['gateways']
51
32
  end
52
33
 
53
34
  def server_config
54
- Api.eip['openvpn_configuration'].transform_keys(&:to_sym)
35
+ API.eip['openvpn_configuration'].transform_keys(&:to_sym)
55
36
  end
56
37
 
57
38
  def validate_options(opts)
@@ -69,17 +50,16 @@ module RiseupVpn
69
50
  end
70
51
 
71
52
  def template
72
- return TEMPLATE.except(*%i[up down]) if RiseupVpn.os == 'darwin'
73
- return TEMPLATE if RiseupVpn.os == 'linux'
53
+ return TEMPLATE.except(*%i[up down]) if Utils.os == 'darwin'
74
54
 
75
- raise RiseupVpnError, "Unsupported OS: '#{RiseupVpn.os}'"
55
+ TEMPLATE # assumed Linux, see Utils::SUPPORTED_PLATFORMS
76
56
  end
77
57
 
78
58
  def generate_line(key, val, gateway)
79
59
  return "#{key} #{val}" if val # hard coded key/value in template file
80
60
  return "#{key} #{@opts[key]}" if @opts[key]
81
- return server_config_line(key) if @server_config[key]
82
61
  return send(regularize_method_name(key), gateway) if LINE_METHODS.include? key
62
+ return server_config_line(key) if @server_config[key]
83
63
 
84
64
  key # hard coded key only in template file
85
65
  end
@@ -98,6 +78,10 @@ module RiseupVpn
98
78
  "remote #{gateway['ip_address']} #{@opts[:proto] == 'udp' ? 1194 : 80}" # TODO: check port 80 in json
99
79
  end
100
80
 
81
+ def tls_version_min(_gateway)
82
+ "tls-version-min #{Utils.tls_version}"
83
+ end
84
+
101
85
  def verify_x509_name(gateway)
102
86
  "verify-x509-name #{name(gateway)} name"
103
87
  end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'generator'
4
+
5
+ module RiseupVPN
6
+ # manages config files
7
+ class Manager
8
+ DEFAULT_DIR = '~/.config/riseup_vpn'
9
+ OPTS = Generator::OPTS
10
+
11
+ def initialize(dir = DEFAULT_DIR, opts = {})
12
+ @dir = File.expand_path dir.to_s
13
+ @generator = Generator.new(opts)
14
+ end
15
+
16
+ def ovpns
17
+ @generator.ovpns
18
+ end
19
+
20
+ def write_files
21
+ write_ovpn_files
22
+ write_ca_crt
23
+ write_client_key
24
+ write_client_crt
25
+ rescue Errno::ENOENT, Errno::EACCES
26
+ raise ArgumentError, "Dir #{@dir} doesn't exist or isn't writable"
27
+ end
28
+
29
+ def import_ovpn_configs
30
+ raise NoMethodError, 'nmcli does not appear to be installed' unless Utils.nmcli?
31
+
32
+ write_files
33
+ Dir["#{@dir}/*.ovpn"].each { |f| Nmcli.import_openvpn f }
34
+ end
35
+
36
+ private
37
+
38
+ def write_ovpn_files
39
+ @generator.ovpns.each { |k, v| File.write(File.join(@dir, "#{k}.ovpn"), v) }
40
+ end
41
+
42
+ def write_ca_crt
43
+ File.write File.join(@dir, 'ca.crt'), @generator.ca_crt.to_s.chomp
44
+ end
45
+
46
+ def write_client_key
47
+ File.write File.join(@dir, 'client.key'), @generator.client_key.to_s.chomp
48
+ end
49
+
50
+ def write_client_crt
51
+ File.write File.join(@dir, 'client.crt'), @generator.client_crt.to_s.chomp
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'generator'
4
+
5
+ module RiseupVPN
6
+ # in the absence of an nmcli gem..
7
+ module Nmcli
8
+ def self.nmcli(command_string)
9
+ `nmcli #{command_string}`
10
+ end
11
+
12
+ def self.import_openvpn(file)
13
+ nmcli "con import type openvpn file #{file}"
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openssl'
4
+
5
+ module RiseupVPN
6
+ # utilties for internal use
7
+ module Utils
8
+ SUPPORTED_PLATFORMS = %w[darwin linux].freeze
9
+
10
+ def self.os
11
+ os = Gem::Platform.local.os
12
+ return os if SUPPORTED_PLATFORMS.include? os
13
+
14
+ raise Error, "Unsupported OS: '#{os}'"
15
+ end
16
+
17
+ def self.tls_version
18
+ "1.#{OpenSSL::VERSION.to_i}".to_f
19
+ end
20
+
21
+ def self.nmcli?
22
+ !`which nmcli`.empty?
23
+ end
24
+ end
25
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module RiseupVpn
4
- VERSION = '0.1.0'
3
+ module RiseupVPN
4
+ VERSION = '1.1.0'
5
5
  end
data/lib/riseup_vpn.rb CHANGED
@@ -3,11 +3,13 @@
3
3
  require 'json'
4
4
  require 'open-uri'
5
5
 
6
- # namespace
7
- module RiseupVpn
8
- class RiseupVpnError < StandardError; end
6
+ require_relative 'riseup_vpn/api'
7
+ require_relative 'riseup_vpn/errors'
8
+ require_relative 'riseup_vpn/generator'
9
+ require_relative 'riseup_vpn/manager'
10
+ require_relative 'riseup_vpn/nmcli'
11
+ require_relative 'riseup_vpn/version'
9
12
 
10
- def self.os
11
- @os ||= Gem::Platform.local.os
12
- end
13
+ # namespace
14
+ module RiseupVPN
13
15
  end
data/sig/riseup_vpn.rbs CHANGED
@@ -1,4 +1,4 @@
1
- module RiseupVpn
1
+ module RiseupVPN
2
2
  VERSION: String
3
3
  # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: riseup_vpn
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - MatzFan
@@ -23,8 +23,12 @@ files:
23
23
  - assets/vpns.png
24
24
  - lib/riseup_vpn.rb
25
25
  - lib/riseup_vpn/api.rb
26
+ - lib/riseup_vpn/errors.rb
26
27
  - lib/riseup_vpn/generator.rb
28
+ - lib/riseup_vpn/manager.rb
29
+ - lib/riseup_vpn/nmcli.rb
27
30
  - lib/riseup_vpn/template_ovpn.yml
31
+ - lib/riseup_vpn/utils.rb
28
32
  - lib/riseup_vpn/version.rb
29
33
  - sig/riseup_vpn.rbs
30
34
  - tmp/.placeholder