Skip to content

Commit

Permalink
Add secret encrypted to api keys model
Browse files Browse the repository at this point in the history
  • Loading branch information
Maksym Naichuk committed May 13, 2020
1 parent ab590fd commit ea2869c
Show file tree
Hide file tree
Showing 15 changed files with 99 additions and 27 deletions.
6 changes: 0 additions & 6 deletions app/api/v2/entities/api_key.rb
Expand Up @@ -13,12 +13,6 @@ class APIKey < API::V2::Entities::Base
expose :created_at
expose :updated_at
end

private

def secret
SecretStorage.get_secret(object.kid)
end
end
end
end
5 changes: 2 additions & 3 deletions app/api/v2/resource/api_keys.rb
Expand Up @@ -48,14 +48,13 @@ def otp_protected!
declared_params = declared(unified_params, include_missing: false)
.except(:totp_code)
.merge(scope: params[:scope]&.split(','))
.merge(secret: SecureRandom.hex(16))

api_key = current_user.api_keys.new(declared_params)

APIKey.transaction do
raise ActiveRecord::Rollback unless api_key.save

SecretStorage.store_secret(SecureRandom.hex(16), api_key.kid)
rescue SecretStorage::Error
rescue Vault::VaultError
api_key.errors.add(:api_key, 'could_not_save_secret')
raise ActiveRecord::Rollback
end
Expand Down
8 changes: 7 additions & 1 deletion app/models/api_key.rb
Expand Up @@ -3,6 +3,12 @@
class APIKey < ApplicationRecord
self.table_name = :apikeys

include Vault::EncryptedModel

vault_lazy_decrypt!

vault_attribute :secret

ALGORITHMS = ['HS256'].freeze

serialize :scope, Array
Expand All @@ -18,7 +24,7 @@ class APIKey < ApplicationRecord
algorithm: 'RS256'
}.freeze

validates :user_id, :kid, presence: true
validates :user_id, :kid, :secret, presence: true
validates :algorithm, inclusion: { in: ALGORITHMS }

belongs_to :user
Expand Down
3 changes: 1 addition & 2 deletions app/services/api_keys_verifier.rb
Expand Up @@ -10,9 +10,8 @@ def initialize(params = {})

def verify_hmac_payload?
data = @nonce.to_s + @kid
secret = SecretStorage.get_secret(@kid).data[:value]
algorithm = 'SHA' + @api_key.algorithm[2..4]
true_signature = OpenSSL::HMAC.hexdigest(algorithm, secret, data)
true_signature = OpenSSL::HMAC.hexdigest(algorithm, @api_key.secret, data)
true_signature == @signature
end
end
4 changes: 2 additions & 2 deletions app/services/totp_service.rb
Expand Up @@ -61,11 +61,11 @@ def with_human_error
private

def totp_key(uid)
"totp/keys/#{uid}"
"totp/keys/#{Vault.application}_#{uid}"
end

def totp_code_key(uid)
"totp/code/#{uid}"
"totp/code/#{Vault.application}_#{uid}"
end

def read_data(key)
Expand Down
1 change: 1 addition & 0 deletions config/initializers/vault.rb
Expand Up @@ -3,6 +3,7 @@
require 'vault/rails'

Vault.configure do |config|
config.enabled = Rails.env.production?
config.address = Barong::App.config.vault_address
config.token = Barong::App.config.vault_token
config.ssl_verify = false
Expand Down
5 changes: 5 additions & 0 deletions db/migrate/20200507104423_add_encrypted_secret.rb
@@ -0,0 +1,5 @@
class AddEncryptedSecret < ActiveRecord::Migration[5.2]
def change
add_column :apikeys, :secret_encrypted, :string, limit: 1024, after: :scope
end
end
3 changes: 2 additions & 1 deletion db/schema.rb
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 2020_03_18_152130) do
ActiveRecord::Schema.define(version: 2020_05_07_104423) do

create_table "activities", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
t.bigint "user_id", null: false
Expand All @@ -32,6 +32,7 @@
t.string "kid", null: false
t.string "algorithm", null: false
t.string "scope"
t.string "secret_encrypted", limit: 1024
t.string "state", default: "active", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
Expand Down
72 changes: 72 additions & 0 deletions docs/vault.md
@@ -0,0 +1,72 @@
# Vault configuration

## Introduction

This document describe how to create vault tokens in order to configure **barong-rails** to be able **to encrypt** and **to decrypt** secrets, **to renew** token and **to manage** totp.

## Connect to vault

Set those variables according to your deployment:
```bash
export VAULT_ADDR=http://127.0.0.1:8200
export VAULT_TOKEN=s.jyH1vmrOmkZ0FZZ0NZtgRenS
```

You can validate it works running the following command:
```bash
$ vault status

Type: shamir
Sealed: false
Key Shares: 1
Key Threshold: 1
Unseal Progress: 0
Unseal Nonce:
Version: 1.3.4
Cluster Name: vault-cluster-650930cf
Cluster ID: 9f40327d-ec71-9655-b728-7588ce47d0b4

High-Availability Enabled: false
```
## Create ACL groups

### Create the following policy files

**barong-rails.hcl**

```bash
# Manage the transit secrets engine
path "transit/keys/*" {
capabilities = [ "create", "read", "list" ]
}

# Encrypt engines secrets
path "transit/encrypt/opendax_apikeys_*" {
capabilities = [ "create", "read", "update" ]
}

# Decrypt engines secrets
path "transit/decrypt/opendax_apikeys_*" {
capabilities = [ "create", "read", "update" ]
}

# Renew tokens
path "auth/token/renew" {
capabilities = [ "update" ]
}

# Lookup tokens
path "auth/token/lookup" {
capabilities = [ "update" ]
}

# Generate otp code
path "totp/keys/opendax_*" {
capabilities = ["create", "read"]
}

# Verify an otp code
path "totp/code/opendax_*" {
capabilities = ["update"]
}
```
4 changes: 1 addition & 3 deletions spec/api/v2/auth/auth_spec.rb
Expand Up @@ -182,7 +182,7 @@
let(:otp_code) { '1357' }
let(:nonce) { (Time.now.to_f * 1000).to_i }
let(:kid) { api_key.kid }
let(:secret) { SecureRandom.hex(16) }
let(:secret) { api_key.secret }
let(:data) { nonce.to_s + kid }
let(:algorithm) { 'SHA' + api_key.algorithm[2..4]}
let(:signature) { OpenSSL::HMAC.hexdigest(algorithm, secret, data) }
Expand All @@ -192,8 +192,6 @@
SecretStorage.store_secret(secret, api_key.kid)
allow(TOTPService).to receive(:validate?)
.with(test_user.uid, otp_code) { true }
allow(SecretStorage).to receive(:get_secret)
.with(kid) { Vault::Secret.new(data: { value: secret }) }
end

context 'testing api key related errors' do
Expand Down
4 changes: 1 addition & 3 deletions spec/api/v2/auth/csrf_spec.rb
Expand Up @@ -106,7 +106,7 @@
let(:otp_code) { '1357' }
let(:nonce) { (Time.now.to_f * 1000).to_i }
let(:kid) { api_key.kid }
let(:secret) { SecureRandom.hex(16) }
let(:secret) { api_key.secret }
let(:data) { nonce.to_s + kid }
let(:algorithm) { 'SHA' + api_key.algorithm[2..4]}
let(:signature) { OpenSSL::HMAC.hexdigest(algorithm, secret, data) }
Expand All @@ -116,8 +116,6 @@
SecretStorage.store_secret(secret, api_key.kid)
allow(TOTPService).to receive(:validate?)
.with(test_user.uid, otp_code) { true }
allow(SecretStorage).to receive(:get_secret)
.with(kid) { Vault::Secret.new(data: { value: secret }) }
end

context 'with valid api keys' do
Expand Down
4 changes: 1 addition & 3 deletions spec/api/v2/auth/rbac_spec.rb
Expand Up @@ -74,7 +74,7 @@
context 'with api_keys' do
let!(:admin_api_key) { create :api_key, user: @admin }
let(:nonce) { (Time.now.to_f * 1000).to_i }
let(:secret) { SecureRandom.hex(16) }
let(:secret) { admin_api_key.secret }
let(:signature) { OpenSSL::HMAC.hexdigest('SHA256', secret, nonce.to_s + admin_api_key.kid ) }
let!(:turn_on_2fa) { @admin.update(otp: true) }

Expand All @@ -83,8 +83,6 @@
it 'allowes access with for api key owner for valid verb, owner role and path' do
allow(TOTPService).to receive(:validate?)
.with(@admin.uid, '1357') { true }
allow(SecretStorage).to receive(:get_secret)
.with(admin_api_key.kid) { Vault::Secret.new(data: { value: secret }) }

get '/api/v2/auth/api/v2/admin/users/list', headers: {
'X-Auth-Apikey' => admin_api_key.kid,
Expand Down
2 changes: 1 addition & 1 deletion spec/api/v2/resource/api_keys_spec.rb
Expand Up @@ -105,7 +105,7 @@
end

it 'does not create api key if vault is down' do
allow(SecretStorage).to receive(:store_secret).and_raise(SecretStorage::Error)
allow(Vault::Rails).to receive(:encrypt).and_raise(Vault::VaultError)
expect { do_request }.not_to change { APIKey.count }
expect(response.status).to eq(422)
expect_body.to eq(errors: ["api_key.could_not_save_secret"])
Expand Down
1 change: 1 addition & 0 deletions spec/factories/api_keys.rb
Expand Up @@ -4,6 +4,7 @@
factory :api_key, class: 'APIKey' do
user
kid { Faker::Crypto.sha256 }
secret { SecureRandom.hex(16) }
scope { %w[trade] }
algorithm { 'HS256' }
end
Expand Down
4 changes: 2 additions & 2 deletions spec/services/vault_totp_spec.rb
Expand Up @@ -83,15 +83,15 @@

it 'creates secret' do
expect(described_class).to receive(:write_data)
.with('totp/keys/uid', create_params)
.with('totp/keys/barong_uid', create_params)
described_class.create(uid, email)
end
end

describe '.exist?' do
it 'creates secret' do
expect(described_class).to receive(:read_data)
.with('totp/keys/uid') { ['data'] }
.with('totp/keys/barong_uid') { ['data'] }
described_class.exist?(uid)
end
end
Expand Down

0 comments on commit ea2869c

Please sign in to comment.