Skip to content

Commit

Permalink
Integrate vault-rails
Browse files Browse the repository at this point in the history
  * Update migration
  • Loading branch information
dnfd committed Aug 9, 2019
1 parent fd3d9c3 commit d5d5a30
Show file tree
Hide file tree
Showing 11 changed files with 186 additions and 36 deletions.
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ gem 'peatio', '~> 0.6.1'
gem 'rack-cors', '~> 1.0.2', require: false
gem 'env-tweaks', '~> 1.0.0'
gem 'vault', '~> 0.12', require: false
gem 'vault-rails', '~> 0.5.0', git: 'http://github.com/rubykube/vault-rails'
gem 'bootsnap', '>= 1.1.0', require: false
gem 'net-http-persistent', '~> 3.0.1'

Expand Down
9 changes: 9 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
GIT
remote: http://github.com/rubykube/vault-rails
revision: ef9b8626e4bf41dcea8696c4aec91e543ddd80a5
specs:
vault-rails (0.5.0)
rails (>= 4.1)
vault (~> 0.5)

GEM
remote: https://rubygems.org/
specs:
Expand Down Expand Up @@ -489,6 +497,7 @@ DEPENDENCIES
validate_url (~> 1.0.4)
validates_lengths_from_database (~> 0.7.0)
vault (~> 0.12)
vault-rails (~> 0.5.0)!
webmock (~> 3.5)

RUBY VERSION
Expand Down
24 changes: 14 additions & 10 deletions app/models/payment_address.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@
# frozen_string_literal: true

class PaymentAddress < ApplicationRecord
include Vault::EncryptedModel
include BelongsToCurrency
include BelongsToAccount

vault_lazy_decrypt!

after_commit :enqueue_address_generation

validates :address, uniqueness: { scope: :currency_id }, if: :address?

serialize :details, JSON
vault_attribute :details, serialize: :json, default: {}
vault_attribute :secret

before_validation do
next if blockchain_api&.case_sensitive?
Expand Down Expand Up @@ -42,18 +46,18 @@ def to_cash_address
end

# == Schema Information
# Schema version: 20180925123806
# Schema version: 20190807092706
#
# Table name: payment_addresses
#
# id :integer not null, primary key
# currency_id :string(10) not null
# account_id :integer not null
# address :string(95)
# secret :string(128)
# details :string(1024) default({}), not null
# created_at :datetime not null
# updated_at :datetime not null
# id :integer not null, primary key
# currency_id :string(10) not null
# account_id :integer not null
# address :string(95)
# secret_encrypted :string(255)
# details_encrypted :string(1024)
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
Expand Down
47 changes: 31 additions & 16 deletions app/models/wallet.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,18 @@
class Wallet < ApplicationRecord
extend Enumerize

include Vault::EncryptedModel

vault_lazy_decrypt!

# We use this attribute values rules for wallet kinds:
# 1** - for deposit wallets.
# 2** - for fee wallets.
# 3** - for withdraw wallets (sorted by security hot < warm < cold).
ENUMERIZED_KINDS = { deposit: 100, fee: 200, hot: 310, warm: 320, cold: 330 }.freeze
enumerize :kind, in: ENUMERIZED_KINDS, scope: true

# Remove after admin panel deletion.
SETTING_ATTRIBUTES = %i[ uri
secret
bitgo_test_net
Expand All @@ -19,11 +24,21 @@ class Wallet < ApplicationRecord
bitgo_rest_api_root
bitgo_rest_api_access_token ].freeze

SETTING_ATTRIBUTES.each do |attribute|
define_method attribute do
self.settings[attribute.to_s]
end

define_method "#{attribute}=".to_sym do |value|
self.settings = self.settings.merge(attribute.to_s => value)
end
end

NOT_AVAILABLE = 'N/A'.freeze

include BelongsToCurrency

store :settings, accessors: SETTING_ATTRIBUTES, coder: JSON
vault_attribute :settings, serialize: :json, default: {}

belongs_to :blockchain, foreign_key: :blockchain_key, primary_key: :key

Expand Down Expand Up @@ -97,24 +112,24 @@ def wallet_url
end

# == Schema Information
# Schema version: 20181126101312
# Schema version: 20190807092706
#
# Table name: wallets
#
# id :integer not null, primary key
# blockchain_key :string(32)
# currency_id :string(10)
# name :string(64)
# address :string(255) not null
# kind :integer not null
# nsig :integer
# gateway :string(20) default(""), not null
# settings :string(1000) default({}), not null
# max_balance :decimal(32, 16) default(0.0), not null
# parent :integer
# status :string(32)
# created_at :datetime not null
# updated_at :datetime not null
# id :integer not null, primary key
# blockchain_key :string(32)
# currency_id :string(10)
# name :string(64)
# address :string(255) not null
# kind :integer not null
# nsig :integer
# gateway :string(20) default(""), not null
# settings_encrypted :string(1024)
# max_balance :decimal(32, 16) default(0.0), not null
# parent :integer
# status :string(32)
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
Expand Down
1 change: 1 addition & 0 deletions bin/init_vault
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ echo $VAULT_TOKEN
docker exec -e "VAULT_TOKEN=${VAULT_TOKEN}" ${DOCKER_VAULT_ID} sh -c \
"vault secrets disable secret \
&& vault secrets enable -path=secret -version=1 kv \
&& vault secrets enable transit \
&& vault secrets enable totp"

2 changes: 2 additions & 0 deletions config/initializers/vault.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
# frozen_string_literal: true

require 'vault/totp'
require 'vault/rails'

Vault.configure do |config|
config.address = ENV.fetch('VAULT_URL', 'http://127.0.0.1:8200')
config.token = ENV.fetch('VAULT_TOKEN')
config.ssl_verify = false
config.timeout = 60
config.application = ENV.fetch('VAULT_APP_NAME', 'peatio')
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
class AddEncryptedSecretToPaymentAddress < ActiveRecord::Migration[5.2]
def up
secrets = PaymentAddress.pluck(:id, :secret)
details = PaymentAddress.pluck(:id, :details)
settings = Wallet.pluck(:id, :settings)

remove_column :payment_addresses, :secret
add_column :payment_addresses, :secret_encrypted , :string, after: :address

remove_column :payment_addresses, :details
add_column :payment_addresses, :details_encrypted , :string, limit: 1024, after: :secret_encrypted

remove_column :wallets, :settings
add_column :wallets, :settings_encrypted , :string, limit: 1024, after: :gateway

secrets.each do |s|
atr = PaymentAddress.__vault_attributes[:secret]
enc = Vault::Rails.encrypt(atr[:path], atr[:key], s[1])
execute "UPDATE payment_addresses SET #{atr[:encrypted_column]} = '#{enc}' WHERE id = #{s[0]}"
end

details.each do |d|
atr = PaymentAddress.__vault_attributes[:details]
enc = Vault::Rails.encrypt(atr[:path], atr[:key], d[1])
execute "UPDATE payment_addresses SET #{atr[:encrypted_column]} = '#{enc}' WHERE id = #{d[0]}"
end

settings.each do |s|
atr = Wallet.__vault_attributes[:settings]
enc = Vault::Rails.encrypt(atr[:path], atr[:key], s[1])
execute "UPDATE wallets SET #{atr[:encrypted_column]} = '#{enc}' WHERE id = #{s[0]}"
end
end

def down
secrets = PaymentAddress.pluck(:id, :secret_encrypted)
details = PaymentAddress.pluck(:id, :details_encrypted)
settings = Wallet.pluck(:id, :settings_encrypted)

add_column :payment_addresses, :secret, :string, limit: 128, after: :address
remove_column :payment_addresses, :secret_encrypted , :string, after: :address

add_column :payment_addresses, :details, :string, limit: 1.kilobyte, null: false, default: '{}', after: :secret
remove_column :payment_addresses, :details_encrypted , :string, limit: 1024, after: :secret_encrypted

add_column :wallets, :settings, :string, limit: 1000, default: '{}', null: false, after: :gateway
remove_column :wallets, :settings_encrypted , :string, limit: 1024, after: :gateway

secrets.each do |s|
atr = PaymentAddress.__vault_attributes[:secret]
dec = Vault::Rails.decrypt(atr[:path], atr[:key], s[1])
execute "UPDATE payment_addresses SET secret = '#{dec}' WHERE id = #{s[0]}"
end

details.each do |d|
atr = PaymentAddress.__vault_attributes[:details]
dec = Vault::Rails.decrypt(atr[:path], atr[:key], d[1])
execute "UPDATE payment_addresses SET details = '#{dec}' WHERE id = #{d[0]}"
end

settings.each do |s|
atr = Wallet.__vault_attributes[:settings]
dec = Vault::Rails.decrypt(atr[:path], atr[:key], s[1])
execute "UPDATE wallets SET settings = '#{dec}' WHERE id = #{s[0]}"
end
end
end
8 changes: 4 additions & 4 deletions db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 2019_07_26_161540) do
ActiveRecord::Schema.define(version: 2019_08_07_092706) do

create_table "accounts", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
t.integer "member_id", null: false
Expand Down Expand Up @@ -207,8 +207,8 @@
t.string "currency_id", limit: 10, null: false
t.integer "account_id", null: false
t.string "address", limit: 95
t.string "secret", limit: 128
t.string "details", limit: 1024, default: "{}", null: false
t.string "secret_encrypted"
t.string "details_encrypted", limit: 1024
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["currency_id", "address"], name: "index_payment_addresses_on_currency_id_and_address", unique: true
Expand Down Expand Up @@ -277,7 +277,7 @@
t.integer "kind", null: false
t.integer "nsig"
t.string "gateway", limit: 20, default: "", null: false
t.string "settings", limit: 1000, default: "{}", null: false
t.string "settings_encrypted", limit: 1024
t.decimal "max_balance", precision: 32, scale: 16, default: "0.0", null: false
t.integer "parent"
t.string "status", limit: 32
Expand Down
2 changes: 1 addition & 1 deletion lib/peatio/vault/totp.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def with_human_error
raise ArgumentError, 'Block is required' unless block_given?
yield
rescue Vault::VaultError => e
Rails.logger.error { e }
::Rails.logger.error { e }
if e.message.include?('connection refused')
raise Error, '2FA server is under maintenance'
end
Expand Down
33 changes: 28 additions & 5 deletions spec/models/payment_address_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,38 @@
context '.create' do
let(:member) { create(:member, :level_3) }
let!(:account) { member.get_account(:btc) }
let(:secret) { 's3cr3t' }
let(:details) { { 'a' => 'b', 'b' => 'c' } }
let!(:addr) { create(:payment_address, :btc_address, secret: secret) }

after do
DatabaseCleaner.strategy = :truncation
end

it 'generate address after commit', clean_database_with_truncation: true do
it 'generate address after commit' do
AMQPQueue.expects(:enqueue)
.with(:deposit_coin_address, { account_id: account.id }, { persistent: true })
account.payment_address
end

it 'updates secret' do
expect {
addr.update(secret: 'new_secret')
}.to change { addr.reload.secret_encrypted }.and change { addr.reload.secret }.to 'new_secret'
end

it 'updates details' do
expect {
addr.update(details: details)
}.to change { addr.reload.details_encrypted }.and change { addr.reload.details }.to details
end

it 'long secret' do
expect {
addr.update(secret: Faker::String.random(1024))
}.to raise_error ActiveRecord::ValueTooLong
end

it 'long details' do
expect {
addr.update(details: { test: Faker::String.random(1024) })
}.to raise_error ActiveRecord::ValueTooLong
end
end
end
28 changes: 28 additions & 0 deletions spec/models/wallet_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,33 @@
expect(subject).to_not be_valid
expect(subject.errors.full_messages).to eq ['Name has already been taken']
end

it 'saves settings in encrypted column' do
subject.save
expect {
subject.uri = 'http://geth:8545/'
subject.save
}.to change { subject.settings_encrypted }
end

it 'does not update settings_encrypted before model is saved' do
subject.save
expect {
subject.uri = 'http://geth:8545/'
}.not_to change { subject.settings_encrypted }
end

it 'updates setting fields' do
expect {
subject.uri = 'http://geth:8545/'
}.to change { subject.settings['uri'] }.to 'http://geth:8545/'
end

it 'long encrypted secret' do
expect {
subject.secret = Faker::String.random(1024)
subject.save!
}.to raise_error ActiveRecord::ValueTooLong
end
end
end

0 comments on commit d5d5a30

Please sign in to comment.