Skip to content

Commit dbfe569

Browse files
committed
Add generation of WireGuard client configurations
Add generation and storing of per-user sets of WireGuard configs. User can generate needed number of configurations for every device. To generate keys, 'wg' binary should be installed and accessible by PATH at the webserver. Task: #589
1 parent f1852b6 commit dbfe569

File tree

15 files changed

+280
-2
lines changed

15 files changed

+280
-2
lines changed

Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ gem 'haml-rails'
3939
gem 'rest-client'
4040
gem 'whenever', require: false
4141
gem 'rack-proxy'
42+
gem 'ipaddress'
4243

4344
#gem 'copycopter_client', '~> 2.0.1'
4445
# Use Capistrano for deployment

Gemfile.lock

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ GEM
166166
i18n (1.12.0)
167167
concurrent-ruby (~> 1.0)
168168
ice_nine (0.11.2)
169+
ipaddress (0.8.3)
169170
jbuilder (2.11.5)
170171
actionview (>= 5.0.0)
171172
activesupport (>= 5.0.0)
@@ -422,6 +423,7 @@ DEPENDENCIES
422423
groupdate
423424
haml
424425
haml-rails
426+
ipaddress
425427
jbuilder
426428
jquery-rails
427429
kaminari

app/controllers/hackers_controller.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ def remove_nfc
8787

8888
def edit
8989
@new_public_ssh_key = PublicSshKey.new(user: @user)
90+
@new_wg_config = WgConfig.new(user: @user)
9091
redirect_to root_path, alert: 'Ошибка' unless @user == current_user or current_user.admin?
9192
end
9293

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
class WgConfigsController < ApplicationController
2+
load_and_authorize_resource
3+
4+
before_action :set_user, only: [:create, :destroy, :export]
5+
before_action :authenticate_token!, only: [:export_peers]
6+
7+
def create
8+
@wgconfig = WgConfig.new(wg_config_params)
9+
10+
if @wgconfig.save
11+
redirect_to edit_user_path(@user, anchor: 'wg-configs'), notice: "Конфигурация WireGuard создана успешно"
12+
else
13+
redirect_to edit_user_path(@user),
14+
alert: "Ошибка создания конфигурации WireGuard: #{@wgconfig.errors.full_messages.join("\n")}"
15+
end
16+
end
17+
18+
def destroy
19+
wg = @user.wg_configs.find_by(id: params[:id])
20+
wg&.destroy
21+
redirect_to edit_user_path(@user, anchor: 'wg-configs')
22+
end
23+
24+
def export
25+
wg = @user.wg_configs.find_by(id: params[:id])
26+
27+
if wg
28+
send_data wg.to_s, filename: "wg-hackerspace-#{wg.name}.conf", type: "text/plain"
29+
else
30+
redirect_to user_path(@user), alert: "Конфигурация WireGuard не найдена"
31+
end
32+
end
33+
34+
def export_peers
35+
wgs = WgConfig.order(:user_id).select { |wg| wg.user.active? }
36+
37+
peers_config = wgs.map { |wg| wg.as_peer }.join("\n\n")
38+
39+
render plain: peers_config
40+
end
41+
42+
private
43+
44+
def authenticate_token!
45+
token = request.headers['Authorization']&.split&.last
46+
if not ActiveSupport::SecurityUtils.secure_compare(token || '', Rails.application.secrets.wireguard_token) then
47+
render plain: "Authorization required\n", status: :unauthorized
48+
end
49+
end
50+
51+
def wg_config_params
52+
params.require(:wg_config).permit(
53+
:name,
54+
:user_id
55+
)
56+
end
57+
58+
def set_user
59+
@user = User.find(params[:user_id])
60+
end
61+
end

app/models/ability.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ def initialize(user)
5252

5353
can [:ssh_keys, :detected_at_hackerspace, :find_by_mac, :useful], User
5454

55+
can [:export_peers], WgConfig
56+
5557
if user.present?
5658
can :chart, :main
5759
can :image, :uploader
@@ -60,6 +62,7 @@ def initialize(user)
6062
can :manage, Mac, user_id: user.id
6163
can :manage, NfcKey, user_id: user.id
6264
can :manage, PublicSshKey, user_id: user.id
65+
can :manage, WgConfig, user_id: user.id
6366

6467
can :read, Device
6568
can :add, Event

app/models/user.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ class User < ApplicationRecord
118118
has_many :payments
119119
has_many :nfc_keys
120120
has_many :public_ssh_keys
121+
has_many :wg_configs
121122
belongs_to :tariff
122123
belongs_to :guarantor1, class_name: 'User', optional: true
123124
belongs_to :guarantor2, class_name: 'User', optional: true

app/models/wg_config.rb

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
require 'ipaddress'
2+
require 'open3'
3+
4+
class WgConfig < ApplicationRecord
5+
6+
belongs_to :user
7+
8+
validates :name, presence: true
9+
validates :privatekey, presence: true
10+
validates :publickey, presence: true
11+
validates :name, uniqueness: { scope: :user_id }
12+
13+
after_initialize :init_keys
14+
15+
def generate_keys!
16+
self.privatekey = gen_privatekey
17+
self.publickey = gen_publickey(self.privatekey)
18+
end
19+
20+
def addresses(as_networks=true)
21+
raise "Model is not saved" if self.id.nil?
22+
23+
first = IPAddress Setting['wgFirstClientAddress']
24+
last = IPAddress Setting['wgLastClientAddress']
25+
26+
ip = IPAddress(first.to_i + self.id)
27+
28+
raise "Failed to assign IP: out of range" if ip > last
29+
30+
ip.netmask = Setting['wgNetmask'] if as_networks
31+
32+
ip.to_string
33+
end
34+
35+
def to_s
36+
lines = []
37+
lines << "# WireGuard config #{name}"
38+
lines << '[Interface]'
39+
lines << "PrivateKey = #{privatekey}"
40+
lines << "Address = #{self.addresses}"
41+
lines << ''
42+
lines << '[Peer]'
43+
lines << "PublicKey = #{Setting['wgServerPublicKey']}"
44+
lines << "Endpoint = #{Setting['wgServerEndpoint']}"
45+
lines << "AllowedIPs = #{Setting['wgAllowedIPs']}"
46+
47+
lines.join("\n")
48+
end
49+
50+
def as_peer
51+
s = []
52+
s << "# User #{self.user_id}, config '#{self.name}'"
53+
s << "[Peer]"
54+
s << "PublicKey = #{publickey}"
55+
s << "AllowedIPs = #{addresses(false)}"
56+
57+
s.join("\n")
58+
end
59+
60+
private
61+
62+
def init_keys
63+
generate_keys! if self.privatekey.blank?
64+
end
65+
66+
def gen_privatekey
67+
key, status = Open3.capture2("wg genkey")
68+
key.strip
69+
end
70+
71+
def gen_publickey(private_key)
72+
stdout_str = ''
73+
Open3.popen2("wg pubkey") do |stdin, stdout, _wait_thr|
74+
stdin.print(private_key)
75+
stdin.close
76+
stdout_str = stdout.gets
77+
end
78+
79+
stdout_str.strip
80+
end
81+
end

app/views/hackers/_form.html.haml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,3 +139,26 @@
139139
.form-group
140140
= submit_tag('Добавить публичный SSH ключ', class: 'btn btn-primary')
141141

142+
-# WireGuard
143+
%hr
144+
.row#wg-configs
145+
.col-lg-6
146+
- unless @user.wg_configs.empty?
147+
%h3 Конфигурации WireGuard
148+
- @user.wg_configs.each do |wg|
149+
.row.small
150+
.col-lg-4
151+
%p
152+
= wg.name
153+
.col-lg-1
154+
%p
155+
= link_to 'Удалить', user_wg_config_path(@user, wg), method: :delete, data: { confirm: 'Вы уверены?' }
156+
157+
.row
158+
.col-lg-6
159+
= form_for [@user, @new_wg_config] do |f|
160+
= f.hidden_field :user_id
161+
.form-group
162+
= f.text_field :name, class: 'form-control'
163+
.form-group
164+
= submit_tag('Создать конфигурацию WireGuard', class: 'btn btn-primary')

app/views/hackers/show.html.haml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,14 @@
7474
%h4= t('users.public_ssh_keys') + ':'
7575
- @user.public_ssh_keys.each do |public_ssh_key|
7676
.ssh-public-key= public_ssh_key.body
77+
78+
-if @user.wg_configs.any?
79+
.row.additional_info
80+
.col-md-8
81+
%h4= t('users.wg_configs') + ':'
82+
- @user.wg_configs.each do |wg|
83+
.wg_config.row
84+
.col-lg-3
85+
= wg.name
86+
.col-lg-2
87+
= link_to 'Скачать', user_wg_export_path(@user, wg), method: :post

config/locales/ru.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,7 @@ ru:
308308
telegram_username: Telegram
309309
github_username: Github
310310
public_ssh_keys: Public SSH keys
311+
wg_configs: Конфигурации WireGuard
311312
last_payment_ended_at: Оплачено по
312313
last_seen_in_hackerspace: Видели в ХС
313314
learner: Курсант

0 commit comments

Comments
 (0)