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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fc6de3e861f5cd9e32b59bcc95042fd67fe0302433ca390aca51dcc7d2073b53
4
- data.tar.gz: 79735074ab96ccaa1dc31384d313b075ced36630b8253276f1a393ce433ae090
3
+ metadata.gz: e643c69efa8243d95e44c59a6d49f20c40a23dc3a914da1d79342fa5d3969ca9
4
+ data.tar.gz: fcb70caad8b2d949fca672198e269e574924cd63748dfa221164ec6a1eb7842d
5
5
  SHA512:
6
- metadata.gz: ec344ac14ee119cb38f1718f22b4f45d34fbccf20917196c47c3cdcd90ead5c050cab422899bdceca5a4fb6c8cd270e058815374b9504698bc1dbb67383ec101
7
- data.tar.gz: 6e127eb2ba8a7a027191fe83827a0af663f82f760e3364ffc025b650a3d58ab0ba1f1897f9bd667373f7ad9e103a835941437473e5c01c51ac9f387707fa7676
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/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.4"
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
- unless ActiveRecord::VERSION::MAJOR == 7
148
- ::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:enquo_bigint] = {}
149
- 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" }
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.2.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-04 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
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0.4'
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.4'
26
+ version: '0.6'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: activerecord
29
29
  requirement: !ruby/object:Gem::Requirement