riseup_vpn 0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 061471e29bcb71a8ad7d20c0757f445ed026913613deb3a289bde237aa40022e
4
+ data.tar.gz: 7a780713819d589fdf07e4abd684c6b341e61e5d0ffdf6ccc331693f35cc5301
5
+ SHA512:
6
+ metadata.gz: ae6548f93e8f3734c6578a0f593b979384735e34d07e84d6ffc6bf71ec741440086e0a480b2e19c7939072ee0876a33bf53b33224c3283e15ca7d913c98bd8d1
7
+ data.tar.gz: 6e5c748da5d135578357540b95d0ea97fb8e92287408443dac4acd655da6a8cd8ecd7dcb8bec4cc0944ad419a9eb4770c76447590da7d2927fb8efcab1a62652
data/.rubocop.yml ADDED
@@ -0,0 +1,19 @@
1
+ ---
2
+ plugins:
3
+ - rubocop-minitest
4
+ - rubocop-performance
5
+ - rubocop-rake
6
+
7
+ AllCops:
8
+ TargetRubyVersion: 3.1
9
+ NewCops: enable
10
+
11
+ Style/HashSyntax:
12
+ Enabled: false # yuk Ruby 3.1
13
+
14
+ Style/RegexpLiteral:
15
+ Exclude:
16
+ - 'Guardfile'
17
+
18
+ Minitest/TestMethodName:
19
+ Enabled: true
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2025-04-19
4
+
5
+ - Initial release
data/Guardfile ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ # NOTE: rake runs threadsafe tests separately from others. Guard may show false positives when running all tests
4
+ guard :minitest, all_on_start: false do
5
+ watch(%r{^test/(.*)/?test_(.*)\.rb$})
6
+ watch(%r{^lib/(.*/)?([^/]+)\.rb$}) { |m| "test/#{m[1]}test_#{m[2]}.rb" }
7
+ # watch(%r{^test/test_helper\.rb$}) { 'test' } # See note. Run bundle exec rake instead.
8
+ ignore %r{^/}, /Guardfile/ # See note. Run bundle exec rake instead.
9
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 MatzFan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,77 @@
1
+ [![Gem Version](https://badge.fury.io/rb/riseup_vpn.svg)](https://badge.fury.io/rb/riseup_vpn)
2
+ [![Ruby Style Guide](https://img.shields.io/badge/code_style-rubocop-brightgreen.svg)](https://github.com/rubocop/rubocop)
3
+
4
+ # RiseupVPN
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.
9
+
10
+ ![image info](assets/vpns.png)
11
+
12
+ ## Installation
13
+
14
+ Install the gem and add to the application's Gemfile by executing:
15
+
16
+ ```bash
17
+ bundle add riseup_vpn
18
+ ```
19
+
20
+ If bundler is not being used to manage dependencies, install the gem by executing:
21
+
22
+ ```bash
23
+ gem install riseup_vpn
24
+ ```
25
+
26
+ ## Usage
27
+
28
+ ```ruby
29
+ require 'riseup_vpn'
30
+
31
+ generator = RiseupVpn::Generator.new
32
+ # => instance_of RiseupVpn::Generator
33
+
34
+ generator.ovpns
35
+ # => {"vpn01-sea.riseup.net" => "client\ntls-client\ndev tun\nproto udp\nremote 204.13.164.252 1194 ...
36
+
37
+ generator.ovpns.size
38
+ # => 21
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'
42
+
43
+ generator.write_files '~/.config/riseup_vpn' # dir must exist
44
+ ```
45
+ The last command creates the following files in the chosen directory:
46
+ - an .ovpn file for each VPN gateway
47
+ - the Certificate Authority cert file (ca.crt)
48
+ - the client private key file (client.key)
49
+ - the client cert file (client.crt)
50
+
51
+ Note: OpenVPN requires that cert & key files shared across .ovpn client config files are present in the same directory.
52
+
53
+ ## \_why?
54
+
55
+ I can use Riseup's white-labeled [desktop app](https://leap.se) to access Riseup's VPNs, so why the need?
56
+
57
+ - You can use this gem's Ruby interface to make Riseup's VPNs available to your apps!
58
+ - This approach saves you installing another app on your laptop/desktop (at least on Linux).
59
+ - Integrating Riseup's VPNs directly into your network manager means you can select individual gateways. The LEAP app currently allows selection by location only and there can be a wide disparity in connection speeds between them.
60
+ - You can configure your firewall to deny outgoing connections except on port 1194 (assuming UDP). LEAP's app makes DNS and HTTPS requests on startup to 1) get the gateway info and client key/cert and 2) determine the 'best' VPN for your location.
61
+ - This approach does mean you need to periodically update the client key/cert, as expiry is currently set for 30 days. A simple `cron` job can fix this and we are working into incorporating that functionality into this gem.
62
+
63
+ ## Development
64
+
65
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
66
+
67
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
68
+
69
+ ## Contributing
70
+
71
+ Known issues are recorded [here](https://gitlab.com/matzfan/riseup_vpn/-/issues).
72
+
73
+ Bug reports and pull requests are welcome on GitLab at https://gitlab.com/matzfan/riseup_vpn. For a pull request please checkout a suitably named feature branch before making and submitting changes.
74
+
75
+ ## License
76
+
77
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'minitest/test_task'
5
+ require 'rake/testtask'
6
+ require 'rubocop/rake_task'
7
+
8
+ # Minitest::TestTask.create
9
+
10
+ namespace :test do
11
+ Rake::TestTask.new do |t|
12
+ t.name = 'api'
13
+ t.description = 'Run only API tests'
14
+ t.libs << 'lib'
15
+ t.test_files = ['test/riseup_vpn/test_api.rb']
16
+ end
17
+
18
+ Rake::TestTask.new do |t|
19
+ t.name = 'ex_api'
20
+ t.description = 'Run tests without API tests'
21
+ t.libs << 'lib'
22
+ t.test_files = FileList['test/**/test_*.rb']
23
+ t.test_files = FileList['test/**/test_*.rb'] - ['test/riseup_vpn/test_api.rb']
24
+ end
25
+ end
26
+
27
+ desc 'Run the test suite'
28
+ task :test do
29
+ Rake::Task['test:api'].invoke
30
+ Rake::Task['test:ex_api'].invoke
31
+ end
32
+
33
+ RuboCop::RakeTask.new
34
+
35
+ task default: %i[test rubocop]
data/assets/vpns.png ADDED
Binary file
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openssl'
4
+
5
+ module RiseupVpn
6
+ # parses ca.cert, client cert/key & gateways data from riseup.net API
7
+ module Api
8
+ class ApiError < StandardError; end
9
+
10
+ PROVIDER = 'https://riseup.net/provider.json' # entry point for API
11
+ API_URI = 'https://api.black.riseup.net' # 443
12
+ API_VERSION = '3'
13
+ CA_CERT_URI = 'https://black.riseup.net/ca.crt'
14
+ CERT = 'cert' # not available from provider uri..
15
+ EIP = 'eip-service.json' # not available from provider uri..
16
+
17
+ class << self
18
+ def ca_crt
19
+ OpenSSL::X509::Certificate.new get(CA_CERT_URI)
20
+ end
21
+
22
+ def client_key_and_crt
23
+ str = get(File.join(API_URI, API_VERSION, CERT))
24
+ [OpenSSL::PKey.read(str), OpenSSL::X509::Certificate.new(str)]
25
+ end
26
+
27
+ def client_key
28
+ OpenSSL::PKey.read get(File.join(API_URI, API_VERSION, CERT))
29
+ end
30
+
31
+ def eip
32
+ get_json File.join(API_URI, API_VERSION, EIP)
33
+ end
34
+
35
+ def get(url)
36
+ URI(url).read
37
+ rescue NoMethodError, URI::Error
38
+ raise ApiError, "Bad URI: '#{url}'"
39
+ rescue OpenURI::HTTPError
40
+ raise ApiError, "Failed to get url: '#{url}'"
41
+ end
42
+
43
+ def get_json(url)
44
+ JSON.parse get(url)
45
+ rescue JSON::ParserError
46
+ raise ApiError, "Failed to parse JSON at: '#{url}'"
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openssl'
4
+ require 'yaml'
5
+
6
+ require_relative 'api'
7
+
8
+ module RiseupVpn
9
+ # create a .ovpn config file for a gateway
10
+ class Generator
11
+ TEMPLATE = YAML.load_file File.join(__dir__, 'template_ovpn.yml')
12
+ OPTS = { proto: %w[udp tcp] }.freeze # default is 1st value
13
+ LINE_METHODS = %i[remote verify-x509-name].freeze
14
+
15
+ attr_reader :ovpns
16
+
17
+ def initialize(opts = {})
18
+ @opts = validate_options opts.transform_keys!(&:to_sym)
19
+ @server_config = server_config
20
+ @template = template
21
+ @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"
31
+ end
32
+
33
+ private
34
+
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
+ def gateways
50
+ Api.eip['gateways']
51
+ end
52
+
53
+ def server_config
54
+ Api.eip['openvpn_configuration'].transform_keys(&:to_sym)
55
+ end
56
+
57
+ def validate_options(opts)
58
+ OPTS.each_key { |k| raise ArgumentError, "Valid #{k} opts: #{OPTS[k]}" if opts[k] && !OPTS[k].include?(opts[k]) }
59
+
60
+ OPTS.transform_values(&:first).merge opts
61
+ end
62
+
63
+ def generate_ovpns
64
+ gateways.inject({}) { |memo, gway| memo.merge(file_name(gway) => generate_ovpn(gway).join("\n")) }
65
+ end
66
+
67
+ def generate_ovpn(gateway)
68
+ @template.map { |k, v| generate_line(k.to_sym, v, gateway).to_s.strip }
69
+ end
70
+
71
+ def template
72
+ return TEMPLATE.except(*%i[up down]) if RiseupVpn.os == 'darwin'
73
+ return TEMPLATE if RiseupVpn.os == 'linux'
74
+
75
+ raise RiseupVpnError, "Unsupported OS: '#{RiseupVpn.os}'"
76
+ end
77
+
78
+ def generate_line(key, val, gateway)
79
+ return "#{key} #{val}" if val # hard coded key/value in template file
80
+ return "#{key} #{@opts[key]}" if @opts[key]
81
+ return server_config_line(key) if @server_config[key]
82
+ return send(regularize_method_name(key), gateway) if LINE_METHODS.include? key
83
+
84
+ key # hard coded key only in template file
85
+ end
86
+
87
+ def server_config_line(key)
88
+ return key if @server_config[key] == true # nobind & persist-key
89
+
90
+ "#{key} #{@server_config[key]}"
91
+ end
92
+
93
+ def regularize_method_name(sym)
94
+ sym.to_s.tr('-', '_').to_sym
95
+ end
96
+
97
+ def remote(gateway)
98
+ "remote #{gateway['ip_address']} #{@opts[:proto] == 'udp' ? 1194 : 80}" # TODO: check port 80 in json
99
+ end
100
+
101
+ def verify_x509_name(gateway)
102
+ "verify-x509-name #{name(gateway)} name"
103
+ end
104
+
105
+ def name(gateway)
106
+ gateway['host'].split('.').first
107
+ end
108
+
109
+ def file_name(gateway)
110
+ "#{name(gateway)}.riseup.net"
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,30 @@
1
+ ---
2
+ :client:
3
+ :tls-client:
4
+ :dev:
5
+ :proto:
6
+ :remote:
7
+ :auth:
8
+ :cipher:
9
+ :keepalive:
10
+ :tls-cipher:
11
+ :float:
12
+ :resolv-retry: infinite
13
+ :nobind:
14
+ :verb:
15
+ :persist-key:
16
+ :persist-tun:
17
+ :reneg-sec: 0
18
+ :pull:
19
+ :auth-nocache:
20
+ :script-security: 2
21
+ :up: /etc/openvpn/update-resolv-conf
22
+ :down: /etc/openvpn/update-resolv-conf
23
+ :tls-version-min:
24
+ :redirect-gateway: ipv6
25
+ :remote-cert-tls: server
26
+ :remote-cert-eku: "\"TLS Web Server Authentication\""
27
+ :verify-x509-name:
28
+ :ca: ca.crt
29
+ :cert: client.crt
30
+ :key: client.key
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RiseupVpn
4
+ VERSION = '0.1.0'
5
+ end
data/lib/riseup_vpn.rb ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'open-uri'
5
+
6
+ # namespace
7
+ module RiseupVpn
8
+ class RiseupVpnError < StandardError; end
9
+
10
+ def self.os
11
+ @os ||= Gem::Platform.local.os
12
+ end
13
+ end
@@ -0,0 +1,4 @@
1
+ module RiseupVpn
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
data/tmp/.placeholder ADDED
File without changes
metadata ADDED
@@ -0,0 +1,56 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: riseup_vpn
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - MatzFan
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: Gem to configure RiseupVPN
13
+ executables: []
14
+ extensions: []
15
+ extra_rdoc_files: []
16
+ files:
17
+ - ".rubocop.yml"
18
+ - CHANGELOG.md
19
+ - Guardfile
20
+ - LICENSE.txt
21
+ - README.md
22
+ - Rakefile
23
+ - assets/vpns.png
24
+ - lib/riseup_vpn.rb
25
+ - lib/riseup_vpn/api.rb
26
+ - lib/riseup_vpn/generator.rb
27
+ - lib/riseup_vpn/template_ovpn.yml
28
+ - lib/riseup_vpn/version.rb
29
+ - sig/riseup_vpn.rbs
30
+ - tmp/.placeholder
31
+ homepage: https://gitlab.com/matzfan/riseup_vpn
32
+ licenses:
33
+ - MIT
34
+ metadata:
35
+ homepage_uri: https://gitlab.com/matzfan/riseup_vpn
36
+ source_code_uri: https://gitlab.com/matzfan/riseup_vpn
37
+ changelog_uri: https://gitlab.com/matzfan/riseup_vpn/CHANGELOG.md
38
+ rubygems_mfa_required: 'true'
39
+ rdoc_options: []
40
+ require_paths:
41
+ - lib
42
+ required_ruby_version: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: 3.1.0
47
+ required_rubygems_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: '0'
52
+ requirements: []
53
+ rubygems_version: 3.6.7
54
+ specification_version: 4
55
+ summary: RiseupVPN configuration
56
+ test_files: []