active_enquo 0.2.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +68 -55
- data/active_enquo.gemspec +1 -1
- data/lib/active_enquo.rb +50 -4
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e643c69efa8243d95e44c59a6d49f20c40a23dc3a914da1d79342fa5d3969ca9
|
4
|
+
data.tar.gz: fcb70caad8b2d949fca672198e269e574924cd63748dfa221164ec6a1eb7842d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7ce8dd63cae62013d026ddf7200878975471bf30c157e059e50b269124e886a219ebb526581038bed3fed1de00500416bfdee7740497c1af2b775c90c46dfd53
|
7
|
+
data.tar.gz: 02a1944917d45d1517cd5143dbe56c05c28f8c8a2fffe5840f317ce865a944aa563a04560f3e39773eadb7f2ca5a0f54835bdab2f4765e5aacc456c9a88f88b8
|
data/README.md
CHANGED
@@ -3,12 +3,12 @@ This allows you to keep the data you store safe, by encrypting it, without compr
|
|
3
3
|
|
4
4
|
Sounds like magic?
|
5
5
|
Well, maybe a little bit.
|
6
|
-
Read our [how it works](https://enquo.org/how-it-works) if you're interested in the details, or read on for how to use it.
|
6
|
+
Read our [how it works](https://enquo.org/how-it-works) if you're interested in the gory cryptographic details, or read on for how to use it.
|
7
7
|
|
8
8
|
|
9
9
|
# Pre-requisites
|
10
10
|
|
11
|
-
In order to make use of
|
11
|
+
In order to make use of ActiveRecord extension, you must be running Postgres 11 or higher, with the [`pg_enquo`](https://github.com/enquo/pg_enquo) extension enabled in the database you're working in.
|
12
12
|
See [the `pg_enquo` installation guide](https://github.com/enquo/pg_enquo/tree/main/doc/installation.md) for instructions on how to install `pg_enquo`.
|
13
13
|
|
14
14
|
Also, if you're installing this gem from source, you'll need a reasonably recent [Rust](https://rust-lang.org) toolchain installed.
|
@@ -16,33 +16,41 @@ Also, if you're installing this gem from source, you'll need a reasonably recent
|
|
16
16
|
|
17
17
|
# Installation
|
18
18
|
|
19
|
-
It's a gem:
|
19
|
+
It's a gem, so the usual methods should work Just Fine:
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
|
21
|
+
```sh
|
22
|
+
gem install active_enquo
|
23
|
+
# OR
|
24
|
+
echo "gem 'active_enquo'" >> Gemfile
|
25
|
+
```
|
24
26
|
|
25
|
-
|
27
|
+
On macOS, and Linux `x86-64`/`aarch64`, you'll get a pre-built binary gem that contains everything you need.
|
28
|
+
For other platforms, you'll need to have Rust 1.59.0 or later installed in order to build the native code portion of the gem.
|
26
29
|
|
27
|
-
If you're the sturdy type that likes to run from git:
|
28
30
|
|
29
|
-
|
31
|
+
# Configuration
|
30
32
|
|
31
|
-
|
32
|
-
presumably know what to do already.
|
33
|
+
The only setting that ActiveEnquo needs is to be given a "root" key, which is used to derive the keys which are used to actually encrypt data.
|
33
34
|
|
34
35
|
|
35
|
-
|
36
|
+
## Step 1: Generate a Root Key
|
36
37
|
|
37
|
-
The
|
38
|
-
This key ***MUST*** be generated by a cryptographically-secure random number generator, and must also be 64 hex digits in length.
|
38
|
+
The ActiveEnquo root key ***MUST*** be generated by a cryptographically-secure random number generator, and must also be 64 hex digits in length.
|
39
39
|
A good way to generate this key is with the `SecureRandom` module:
|
40
40
|
|
41
41
|
```sh
|
42
42
|
ruby -r securerandom -e 'puts SecureRandom.hex(32)'
|
43
43
|
```
|
44
44
|
|
45
|
-
|
45
|
+
|
46
|
+
## Step 2: Configure Your Application
|
47
|
+
|
48
|
+
With this key in hand, you need to store it somewhere.
|
49
|
+
|
50
|
+
|
51
|
+
### Using Rails Credential Store (Recommended)
|
52
|
+
|
53
|
+
The recommended way to store your root key, at present, is in the [Rails credentials store](https://guides.rubyonrails.org/security.html#custom-credentials).
|
46
54
|
|
47
55
|
1. Open up the Rails credentials editor:
|
48
56
|
|
@@ -59,7 +67,11 @@ With this key in hand, you can store it in the Rails credential store, like this
|
|
59
67
|
|
60
68
|
3. Save and exit the editor. Commit the changes to your revision control system.
|
61
69
|
|
62
|
-
|
70
|
+
|
71
|
+
### Direct Assignment (Only If You Must)
|
72
|
+
|
73
|
+
Using the Rails credential store only works if you are using Rails, of course.
|
74
|
+
If you're using ActiveRecord by itself, you must set the root key yourself during application initialization.
|
63
75
|
You do this by assigning a `RootKey` to `ActiveEnquo.root_key`, like this:
|
64
76
|
|
65
77
|
```ruby
|
@@ -67,9 +79,12 @@ You do this by assigning a `RootKey` to `ActiveEnquo.root_key`, like this:
|
|
67
79
|
ActiveEnquo.root_key = ActiveEnquo::RootKey::Static.new("0000000000000000000000000000000000000000000000000000000000000000")
|
68
80
|
```
|
69
81
|
|
70
|
-
|
71
|
-
|
72
|
-
|
82
|
+
Preferably, you would pass the key into your application via, say, an environment variable, and then immediately clear the environment variable:
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
ActiveEnquo.root_key = ActiveEnquo::RootKey::Static.new(ENV.fetch("ENQUO_ROOT_KEY"))
|
86
|
+
ENV.delete("ENQUO_ROOT_KEY")
|
87
|
+
```
|
73
88
|
|
74
89
|
Support for cloud keystores, such as AWS KMS, GCP KMS, Azure KeyVault, and HashiCorp Vault, will be implemented sooner or later.
|
75
90
|
If you have a burning desire to see that more on the "sooner" end than "later", PRs are welcome.
|
@@ -77,67 +92,72 @@ If you have a burning desire to see that more on the "sooner" end than "later",
|
|
77
92
|
|
78
93
|
# Usage
|
79
94
|
|
80
|
-
|
95
|
+
We try to make using ActiveEnquo as simple as possible.
|
96
|
+
|
97
|
+
|
98
|
+
## Create Your Encrypted Column
|
99
|
+
|
100
|
+
Start by creating a column in your database that uses one of the [available `enquo_*` types](https://github.com/enquo/pg_enquo/tree/main/doc/data_types), with a Rails migration:
|
81
101
|
|
82
102
|
```ruby
|
83
103
|
class AddEncryptedBigintColumn < ActiveRecord::Migration[6.0]
|
84
104
|
def change
|
85
|
-
add_column :users, :
|
105
|
+
add_column :users, :date_of_birth, :enquo_date
|
86
106
|
end
|
87
107
|
end
|
88
108
|
```
|
89
109
|
|
110
|
+
Apply this migration in the usual fashion (`rails db:migrate`).
|
111
|
+
|
90
112
|
|
91
113
|
## Reading and Writing
|
92
114
|
|
93
|
-
You can now use that attribute in your models as you would normally.
|
94
|
-
For example, insert a new record:
|
115
|
+
You can now, without any further ado, use that attribute in your models as you would normally.
|
116
|
+
For example, you can insert a new record:
|
95
117
|
|
96
118
|
```ruby
|
97
|
-
User.create!([{name: "Clara Bloggs", username: "cbloggs",
|
119
|
+
User.create!([{name: "Clara Bloggs", username: "cbloggs", date_of_birth: Date(1970, 1, 1)}])
|
98
120
|
```
|
99
121
|
|
100
122
|
When you retrieve a record, the value is there for you to read:
|
101
123
|
|
102
124
|
```ruby
|
103
|
-
User.where(username: "cbloggs").first.
|
125
|
+
User.where(username: "cbloggs").first.date_of_birth.to_s # => "1970-01-01"
|
104
126
|
```
|
105
127
|
|
106
|
-
So far, nothing more spectacular than what AR Encryption will get you.
|
107
|
-
The fun begins now...
|
108
|
-
|
109
128
|
|
110
129
|
## Querying
|
111
130
|
|
112
|
-
|
131
|
+
This is where things get *neat*.
|
132
|
+
|
133
|
+
Performing a query on Enquo-encrypted data is done the same way as on unencrypted data, with one exception: you need to wrap the values of the query in `<Model>.enquo` calls, as in the examples below.
|
134
|
+
|
135
|
+
You can query for records that have the exact value you're looking for:
|
113
136
|
|
114
137
|
```ruby
|
115
|
-
User.where(age: User.enquo(:
|
138
|
+
User.where(age: User.enquo(:date_of_birth, Date(1970, 1, 1)))
|
116
139
|
```
|
117
140
|
|
118
|
-
Or you can query for
|
141
|
+
Or you can query for users born less than 50 years ago:
|
119
142
|
|
120
143
|
```ruby
|
121
|
-
|
122
|
-
User.where(age: User.enquo(:age, ...50))
|
144
|
+
User.where(age: User.enquo(:date, Date.today - 50.years)..)
|
123
145
|
```
|
124
146
|
|
125
|
-
|
147
|
+
This doesn't seem so magical, until you take a peek in the database, and realise that *all the data is still encrypted*:
|
126
148
|
|
127
149
|
```sh
|
128
|
-
psql> SELECT
|
150
|
+
psql> SELECT date_of_birth FROM users WHERE username='cbloggs';
|
129
151
|
age
|
130
152
|
-------
|
131
|
-
{"
|
153
|
+
{"v1":{"a":[<lots of numbers>],"y":[<lots and LOTS of numbers>],<etc etc>}}
|
132
154
|
```
|
133
155
|
|
134
|
-
And that, as they say, is that.
|
135
|
-
|
136
156
|
|
137
157
|
## Indexing and Ordering
|
138
158
|
|
139
|
-
To maintain [security](https://enquo.org/about/threat-models#snapshot-security), ActiveEnquo
|
140
|
-
This is fine for many situations -- many columns don't need indexes.
|
159
|
+
To maintain [security by default](https://enquo.org/about/threat-models#snapshot-security), ActiveEnquo doesn't provide the ability to `ORDER BY` or index columns by default.
|
160
|
+
This is fine for many situations -- many columns don't need indexes or to be ordered in a query.
|
141
161
|
|
142
162
|
For those columns that *do* need indexes or `ORDER BY` support, you can enable support for them by setting the `enable_reduced_security_operations` flag on the attribute, like this:
|
143
163
|
|
@@ -148,10 +168,11 @@ class User < ApplicationRecord
|
|
148
168
|
end
|
149
169
|
```
|
150
170
|
|
151
|
-
###
|
171
|
+
### Security Considerations
|
152
172
|
|
153
173
|
As the name implies, "reduced security operations" require that the security of the data in the column be lower than [Enquo's default security properties](https://enquo.org/about/threat-models#snapshot-security).
|
154
|
-
|
174
|
+
Specifically, extra data needs to be stored in the value to enable indexing and ordering.
|
175
|
+
This extra data can be used by an attacker to:
|
155
176
|
|
156
177
|
* Identify all rows which have the same value for the column (although not what that value actually *is*); and
|
157
178
|
|
@@ -174,22 +195,14 @@ class User < ApplicationRecord
|
|
174
195
|
end
|
175
196
|
```
|
176
197
|
|
198
|
+
More accurate indications of the disk space requirements for the supported data types can be found in [the description of each data type](https://github.com/enquo/pg_enquo/tree/main/doc/data_types).
|
177
199
|
|
178
|
-
# Future Developments
|
179
200
|
|
180
|
-
|
181
|
-
|
182
|
-
* **Support for key rotation**: this isn't tricky, just a bit fiddly.
|
183
|
-
Encrypted values have a "key ID" associated with them, so we can find which values are out-of-date, but multiple keys need to useable for decryption.
|
184
|
-
Closely related to this is **support for renaming columns**, which is problematic because keys are derived based on the column name.
|
185
|
-
Again, key IDs (to find values encrypted with the previous column name) and the ability to attempt decryption with a key based on the previous column name sorts this out.
|
186
|
-
|
187
|
-
* **Strings**: a great deal of the sensitive data that needs protecting is in the form of strings.
|
188
|
-
Querying strings is somewhat more involved than numeric data, and so the means of encrypting strings such that they're queryable *and* secure are more complex.
|
189
|
-
Enquo cannot be a really useful library for supporting encrypted querying until at least some common string query operations are supported, though, so it is important that this be implemented.
|
201
|
+
# Future Developments
|
190
202
|
|
191
|
-
|
192
|
-
|
203
|
+
ActiveEnquo is far from finished.
|
204
|
+
Many more features are coming in the future.
|
205
|
+
See [the Enquo project roadmap](https://enquo.org/roadmap) for details of what we're still intending to implement.
|
193
206
|
|
194
207
|
|
195
208
|
# Contributing
|
data/active_enquo.gemspec
CHANGED
@@ -27,7 +27,7 @@ Gem::Specification.new do |s|
|
|
27
27
|
|
28
28
|
s.required_ruby_version = ">= 2.7.0"
|
29
29
|
|
30
|
-
s.add_runtime_dependency "enquo-core", "~> 0.
|
30
|
+
s.add_runtime_dependency "enquo-core", "~> 0.6"
|
31
31
|
s.add_runtime_dependency "activerecord", ">= 6"
|
32
32
|
|
33
33
|
s.add_development_dependency "bundler"
|
data/lib/active_enquo.rb
CHANGED
@@ -29,6 +29,7 @@ module ActiveEnquo
|
|
29
29
|
if t.is_a?(::ActiveEnquo::Type)
|
30
30
|
relation = self.class.arel_table.name
|
31
31
|
value = @attributes.fetch_value(attr_name, &block)
|
32
|
+
return nil if value.nil?
|
32
33
|
field = ::ActiveEnquo.root.field(relation, attr_name)
|
33
34
|
begin
|
34
35
|
t.decrypt(value, @attributes.fetch_value(@primary_key).to_s, field)
|
@@ -81,6 +82,20 @@ module ActiveEnquo
|
|
81
82
|
end
|
82
83
|
end
|
83
84
|
end
|
85
|
+
|
86
|
+
module TableDefinitionExtension
|
87
|
+
def enquo_bigint(name, **options)
|
88
|
+
column(name, :enquo_bigint, **options)
|
89
|
+
end
|
90
|
+
|
91
|
+
def enquo_date(name, **options)
|
92
|
+
column(name, :enquo_date, **options)
|
93
|
+
end
|
94
|
+
|
95
|
+
def enquo_text(name, **options)
|
96
|
+
column(name, :enquo_text, **options)
|
97
|
+
end
|
98
|
+
end
|
84
99
|
end
|
85
100
|
|
86
101
|
module Postgres
|
@@ -88,6 +103,7 @@ module ActiveEnquo
|
|
88
103
|
def initialize_type_map(m = type_map)
|
89
104
|
m.register_type "enquo_bigint", ActiveEnquo::Type::Bigint.new
|
90
105
|
m.register_type "enquo_date", ActiveEnquo::Type::Date.new
|
106
|
+
m.register_type "enquo_text", ActiveEnquo::Type::Text.new
|
91
107
|
|
92
108
|
super
|
93
109
|
end
|
@@ -135,16 +151,46 @@ module ActiveEnquo
|
|
135
151
|
end
|
136
152
|
end
|
137
153
|
end
|
154
|
+
|
155
|
+
class Text < Type
|
156
|
+
def type
|
157
|
+
:enquo_text
|
158
|
+
end
|
159
|
+
|
160
|
+
def encrypt(value, context, field, safety: true, no_query: false)
|
161
|
+
field.encrypt_text(value, context, safety: safety, no_query: no_query)
|
162
|
+
end
|
163
|
+
|
164
|
+
def decrypt(value, context, field)
|
165
|
+
field.decrypt_text(value, context)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
if defined?(Rails::Railtie)
|
171
|
+
class Initializer < Rails::Railtie
|
172
|
+
initializer "active_enquo.root_key" do |app|
|
173
|
+
if app
|
174
|
+
if root_key = app.credentials.active_enquo.root_key
|
175
|
+
ActiveEnquo.root_key = Enquo::RootKey::Static.new(root_key)
|
176
|
+
else
|
177
|
+
Rails.warn "Could not initialize ActiveEnquo, as no active_enquo.root_key credential was found for this environment"
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
138
182
|
end
|
139
183
|
end
|
140
184
|
|
141
185
|
ActiveSupport.on_load(:active_record) do
|
142
186
|
::ActiveRecord::Base.send :include, ActiveEnquo::ActiveRecord::ModelExtension
|
143
187
|
|
188
|
+
::ActiveRecord::ConnectionAdapters::Table.include ActiveEnquo::ActiveRecord::TableDefinitionExtension
|
189
|
+
::ActiveRecord::ConnectionAdapters::TableDefinition.include ActiveEnquo::ActiveRecord::TableDefinitionExtension
|
190
|
+
|
144
191
|
::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend ActiveEnquo::Postgres::ConnectionAdapter
|
145
|
-
# ::ActiveRecord::Type.register(:enquo_bigint, ActiveEnquo::Type::Bigint, adapter: :postgresql)
|
146
192
|
|
147
|
-
|
148
|
-
|
149
|
-
|
193
|
+
::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:enquo_bigint] = { name: "enquo_bigint" }
|
194
|
+
::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:enquo_date] = { name: "enquo_date" }
|
195
|
+
::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:enquo_text] = { name: "enquo_text" }
|
150
196
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_enquo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matt Palmer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-11-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: enquo-core
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '0.
|
19
|
+
version: '0.6'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '0.
|
26
|
+
version: '0.6'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: activerecord
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|