active_enquo 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +68 -55
  3. data/lib/active_enquo.rb +35 -6
  4. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9c0103ba2927e84bdc1c6fda73e7103ffe090316fac200d7d429a95e4f4632f0
4
- data.tar.gz: 11d5f5d66a753fe18f79f198c7b243d9fa3b194929c6e2e63c6f4a93ce25ed06
3
+ metadata.gz: e643c69efa8243d95e44c59a6d49f20c40a23dc3a914da1d79342fa5d3969ca9
4
+ data.tar.gz: fcb70caad8b2d949fca672198e269e574924cd63748dfa221164ec6a1eb7842d
5
5
  SHA512:
6
- metadata.gz: 693f14e986cca46c810505664833cd4e211c170cd287d43fbbb429ba7a5ef0e830404d71597a314e97cdde4fe911edf14066f9af446510ce82c05b3e73507c6d
7
- data.tar.gz: afba7f08679b1b45f6484f9a3bf1b870fc2a824edec9b8b33483f40b7661c940f55d8135992885011d25a4c57d6c02ecb5f069ebde0bed0d59daec85d6ad301d
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 this extension, you must be running Postgres 10 or higher, with the [`pg_enquo`](https://github.com/enquo/pg_enquo) extension enabled in the database you're working in.
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
- gem install active_enquo
22
-
23
- There's also the wonders of [the Gemfile](http://bundler.io):
21
+ ```sh
22
+ gem install active_enquo
23
+ # OR
24
+ echo "gem 'active_enquo'" >> Gemfile
25
+ ```
24
26
 
25
- gem 'active_enquo'
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
- rake install
31
+ # Configuration
30
32
 
31
- Or, if you've eschewed the convenience of Rubygems entirely, then you
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
- # Configuration
36
+ ## Step 1: Generate a Root Key
36
37
 
37
- 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.
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
- With this key in hand, you can store it in the Rails credential store, like this:
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
- This only works if you are using Rails, of course; if you're using ActiveRecord by itself, you must set the root key yourself during application initialization.
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
- However, this leaves the key exposed to anyone who comes along and takes a glance at your code.
71
- Losing control of this root key is catastrophic for the security of your system.
72
- Instead, you should use a secrets vault of some sort to store the key, and pass it into your initialization code somehow.
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
- Start by creating a column in your database that uses one of the [available enquo types](https://github.com/enquo/pg_enquo/doc/data_types), with a Rails migration:
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, :age, :enquo_bigint
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", age: 42}])
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.age # => 42
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
- You can query for records with an exact age:
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(:age, 42))
138
+ User.where(age: User.enquo(:date_of_birth, Date(1970, 1, 1)))
116
139
  ```
117
140
 
118
- Or you can query for records with an age of less than 50:
141
+ Or you can query for users born less than 50 years ago:
119
142
 
120
143
  ```ruby
121
- # This is AR's idiomatic way of saying "less-than"
122
- User.where(age: User.enquo(:age, ...50))
144
+ User.where(age: User.enquo(:date, Date.today - 50.years)..)
123
145
  ```
124
146
 
125
- *However*, if you examine the record in the database, it's encrypted:
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 age FROM users WHERE username='cbloggs';
150
+ psql> SELECT date_of_birth FROM users WHERE username='cbloggs';
129
151
  age
130
152
  -------
131
- {"ct":[<lots of numbers>],"ore":[<lots and LOTS of numbers>]}
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 isn't able to `ORDER BY` or index columns.
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
- ### SECURITY ALERT
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
- In particular, extra data is stored in the value which can be used by an attacker to:
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
- These are some of the things that are definitely planned to be added to ActiveEnquo in the nearish future.
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
- * **Other data types**: while you can go a long way with strings and bigints, part of the power of SQL is the sheer variety of interesting data types it has, and the variety of things you can do with them.
192
- So more integer types, floats, decimals, dates, times, timestamps, and more, are all on the cards for future development.
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/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
@@ -151,17 +166,31 @@ module ActiveEnquo
151
166
  end
152
167
  end
153
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
182
+ end
154
183
  end
155
184
 
156
185
  ActiveSupport.on_load(:active_record) do
157
186
  ::ActiveRecord::Base.send :include, ActiveEnquo::ActiveRecord::ModelExtension
158
187
 
188
+ ::ActiveRecord::ConnectionAdapters::Table.include ActiveEnquo::ActiveRecord::TableDefinitionExtension
189
+ ::ActiveRecord::ConnectionAdapters::TableDefinition.include ActiveEnquo::ActiveRecord::TableDefinitionExtension
190
+
159
191
  ::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend ActiveEnquo::Postgres::ConnectionAdapter
160
- # ::ActiveRecord::Type.register(:enquo_bigint, ActiveEnquo::Type::Bigint, adapter: :postgresql)
161
192
 
162
- unless ActiveRecord::VERSION::MAJOR == 7
163
- ::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:enquo_bigint] = {}
164
- ::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:enquo_date] = {}
165
- ::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:enquo_text] = {}
166
- end
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" }
167
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.3.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-10-07 00:00:00.000000000 Z
11
+ date: 2022-11-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: enquo-core