active_enquo 0.2.0 → 0.4.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 +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
|