active_enquo 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.editorconfig +23 -0
- data/.github/workflows/release.yml +31 -0
- data/.gitignore +5 -0
- data/.yardopts +1 -0
- data/CODE_OF_CONDUCT.md +48 -0
- data/CONTRIBUTING.md +9 -0
- data/LICENCE +19 -0
- data/README.md +224 -0
- data/active_enquo.gemspec +44 -0
- data/docs/DEVELOPMENT.md +24 -0
- data/lib/active_enquo.rb +120 -0
- metadata +225 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b740bd6f520103011cd211cfa06784c088de442c675b39a6730a3bd03b45b6fe
|
4
|
+
data.tar.gz: 3511374723b0718079b71dd0f2641c5306de18cf3d1108714dcf43776ab56d4f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: aade149dd6f25589426c2b5f7a642ce1d86d52a30ac68479a5a704886a8bb9a642cb90fa37d9639fcde0e296fc5eec22d4696ee1d221d4215c61644dc431d385
|
7
|
+
data.tar.gz: efecd652bfea10fd4cd0563c1c4dfacddccb3cd6c0d5aa45d6578cbecf3229a5b33ea14fa2000f5021fd69d82ebe19b2fb3d1ddeff3d05845154c0c3a8a6e4f3
|
data/.editorconfig
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
root = true
|
2
|
+
|
3
|
+
[*]
|
4
|
+
end_of_line = lf
|
5
|
+
insert_final_newline = true
|
6
|
+
charset = utf-8
|
7
|
+
trim_trailing_whitespace = true
|
8
|
+
|
9
|
+
[*.{yml,yaml}]
|
10
|
+
indent_style = space
|
11
|
+
indent_size = 2
|
12
|
+
|
13
|
+
[*.rb]
|
14
|
+
indent_style = tab
|
15
|
+
indent_size = 3
|
16
|
+
|
17
|
+
[Rakefile]
|
18
|
+
indent_style = tab
|
19
|
+
indent_size = 3
|
20
|
+
|
21
|
+
[*.md]
|
22
|
+
indent_style = space
|
23
|
+
indent_size = 2
|
@@ -0,0 +1,31 @@
|
|
1
|
+
name: "Release to RubyGems"
|
2
|
+
on:
|
3
|
+
release:
|
4
|
+
types: [created]
|
5
|
+
workflow_dispatch:
|
6
|
+
|
7
|
+
jobs:
|
8
|
+
upload:
|
9
|
+
runs-on: ubuntu-latest
|
10
|
+
name: "Upload"
|
11
|
+
|
12
|
+
steps:
|
13
|
+
- uses: actions/checkout@v2
|
14
|
+
with:
|
15
|
+
fetch-depth: 0
|
16
|
+
|
17
|
+
- name: Install ruby
|
18
|
+
uses: ruby/setup-ruby@v1
|
19
|
+
with:
|
20
|
+
ruby-version: '2.7'
|
21
|
+
bundler-cache: true
|
22
|
+
|
23
|
+
- name: Workaround for https://github.com/actions/checkout/issues/290
|
24
|
+
run: |
|
25
|
+
git fetch --force --tags
|
26
|
+
|
27
|
+
- name: Do The Needful
|
28
|
+
env:
|
29
|
+
GEM_HOST_API_KEY: ${{ secrets.RUBYGEMS_API_KEY }}
|
30
|
+
run: |
|
31
|
+
rake release
|
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--markup markdown
|
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# Contributor Code of Conduct
|
2
|
+
|
3
|
+
As contributors and maintainers of this project, and in the interest of
|
4
|
+
fostering an open and welcoming community, we pledge to respect all people who
|
5
|
+
contribute through reporting issues, posting feature requests, updating
|
6
|
+
documentation, submitting pull requests or patches, and other activities.
|
7
|
+
|
8
|
+
We are committed to making participation in this project a harassment-free
|
9
|
+
experience for everyone, regardless of level of experience, gender, gender
|
10
|
+
identity and expression, sexual orientation, disability, personal appearance,
|
11
|
+
body size, race, ethnicity, age, religion, or nationality.
|
12
|
+
|
13
|
+
Examples of unacceptable behavior by participants include:
|
14
|
+
|
15
|
+
* The use of sexualized language or imagery
|
16
|
+
* Personal attacks
|
17
|
+
* Trolling or insulting/derogatory comments
|
18
|
+
* Public or private harassment
|
19
|
+
* Publishing other's private information, such as physical or electronic
|
20
|
+
addresses, without explicit permission
|
21
|
+
* Other unethical or unprofessional conduct
|
22
|
+
|
23
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
24
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
25
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
26
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
27
|
+
threatening, offensive, or harmful.
|
28
|
+
|
29
|
+
By adopting this Code of Conduct, project maintainers commit themselves to
|
30
|
+
fairly and consistently applying these principles to every aspect of managing
|
31
|
+
this project. Project maintainers who do not follow or enforce the Code of
|
32
|
+
Conduct may be permanently removed from the project team.
|
33
|
+
|
34
|
+
This code of conduct applies both within project spaces and in public spaces
|
35
|
+
when an individual is representing the project or its community.
|
36
|
+
|
37
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
38
|
+
reported by contacting a project maintainer at coc@enquo.org. All complaints
|
39
|
+
will be reviewed and investigated and will result in a response that is deemed
|
40
|
+
necessary and appropriate to the circumstances. Maintainers are obligated to
|
41
|
+
maintain confidentiality with regard to the reporter of an incident.
|
42
|
+
|
43
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
44
|
+
version 1.3.0, available at
|
45
|
+
[http://contributor-covenant.org/version/1/3/0/][version]
|
46
|
+
|
47
|
+
[homepage]: http://contributor-covenant.org
|
48
|
+
[version]: http://contributor-covenant.org/version/1/3/0/
|
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
* If you have found a discrepancy in documented and observed behaviour, that is a bug.
|
2
|
+
Feel free to [report it as an issue](https://github.com/enquo/active_enquo/issues), providing sufficient detail to reproduce the problem.
|
3
|
+
|
4
|
+
* If you would like to add new behaviour, please submit a well-tested and well-documented [pull request](https://github.com/enquo/active_enquo/pulls).
|
5
|
+
|
6
|
+
* By making a contribution to this repository, you agree that the contribution is licenced under the terms of the [MIT licence](./LICENCE).
|
7
|
+
Further, you warrant that you hold all intellectual property rights in the contribution, or that you have the permission of the owner of those rights to make the contribution under these conditions.
|
8
|
+
|
9
|
+
* At all times, abide by the Code of Conduct (CODE_OF_CONDUCT.md).
|
data/LICENCE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,224 @@
|
|
1
|
+
[ActiveEnquo](https://enquo.org/active_enquo) is a Ruby on Rails ActiveRecord extension that works with the [pg_enquo Postgres extension](https://github.com/enquo/pg_enquo) to allow you to query encrypted data.
|
2
|
+
This allows you to keep the data you store safe, by encrypting it, without compromising on your application's ability to search for that data and work with it.
|
3
|
+
|
4
|
+
Sounds like magic?
|
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.
|
7
|
+
|
8
|
+
|
9
|
+
# Pre-requisites
|
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.
|
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
|
+
|
14
|
+
Also, if you're installing this gem from source, you'll need a reasonably recent [Rust](https://rust-lang.org) toolchain installed.
|
15
|
+
|
16
|
+
|
17
|
+
# Installation
|
18
|
+
|
19
|
+
It's a gem:
|
20
|
+
|
21
|
+
gem install active_enquo
|
22
|
+
|
23
|
+
There's also the wonders of [the Gemfile](http://bundler.io):
|
24
|
+
|
25
|
+
gem 'active_enquo'
|
26
|
+
|
27
|
+
If you're the sturdy type that likes to run from git:
|
28
|
+
|
29
|
+
rake install
|
30
|
+
|
31
|
+
Or, if you've eschewed the convenience of Rubygems entirely, then you
|
32
|
+
presumably know what to do already.
|
33
|
+
|
34
|
+
|
35
|
+
# Configuration
|
36
|
+
|
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.
|
39
|
+
A good way to generate this key is with the `SecureRandom` module:
|
40
|
+
|
41
|
+
```sh
|
42
|
+
ruby -r securerandom -e 'puts SecureRandom.hex(32)'
|
43
|
+
```
|
44
|
+
|
45
|
+
With this key in hand, you can store it in the Rails credential store, like this:
|
46
|
+
|
47
|
+
1. Open up the Rails credentials editor:
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
rails credentials:edit
|
51
|
+
```
|
52
|
+
|
53
|
+
2. Add a section to that file that looks like this:
|
54
|
+
|
55
|
+
```yaml
|
56
|
+
active_enquo:
|
57
|
+
root_key: "0000000000000000000000000000000000000000000000000000000000000000"
|
58
|
+
```
|
59
|
+
|
60
|
+
3. Save and exit the editor. Commit the changes to your revision control system.
|
61
|
+
|
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.
|
63
|
+
You do this by assigning a `RootKey` to `ActiveEnquo.root_key`, like this:
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
# DO NOT ACTUALLY PUT YOUR KEY DIRECTLY IN YOUR CODE!!!
|
67
|
+
ActiveEnquo.root_key = ActiveEnquo::RootKey::Static.new("0000000000000000000000000000000000000000000000000000000000000000")
|
68
|
+
```
|
69
|
+
|
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.
|
73
|
+
|
74
|
+
Support for cloud keystores, such as AWS KMS, GCP KMS, Azure KeyVault, and HashiCorp Vault, will be implemented sooner or later.
|
75
|
+
If you have a burning desire to see that more on the "sooner" end than "later", PRs are welcome.
|
76
|
+
|
77
|
+
|
78
|
+
# Usage
|
79
|
+
|
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:
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
class AddEncryptedBigintColumn < ActiveRecord::Migration[6.0]
|
84
|
+
def change
|
85
|
+
add_column :users, :age, :enquo_bigint
|
86
|
+
end
|
87
|
+
end
|
88
|
+
```
|
89
|
+
|
90
|
+
|
91
|
+
## Reading and Writing
|
92
|
+
|
93
|
+
You can now use that attribute in your models as you would normally.
|
94
|
+
For example, insert a new record:
|
95
|
+
|
96
|
+
```ruby
|
97
|
+
User.create!([{name: "Clara Bloggs", username: "cbloggs", age: 42}])
|
98
|
+
```
|
99
|
+
|
100
|
+
When you retrieve a record, the value is there for you to read:
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
User.where(username: "cbloggs").first.age # => 42
|
104
|
+
```
|
105
|
+
|
106
|
+
So far, nothing more spectacular than what AR Encryption will get you.
|
107
|
+
The fun begins now...
|
108
|
+
|
109
|
+
|
110
|
+
## Querying
|
111
|
+
|
112
|
+
You can query for records with an exact age:
|
113
|
+
|
114
|
+
```ruby
|
115
|
+
User.where(age: User.enquo(:age, 42))
|
116
|
+
```
|
117
|
+
|
118
|
+
Or you can query for records with an age of less than 50:
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
# This is AR's idiomatic way of saying "less-than"
|
122
|
+
User.where(age: User.enquo(:age, ...50))
|
123
|
+
```
|
124
|
+
|
125
|
+
*However*, if you examine the record in the database, it's encrypted:
|
126
|
+
|
127
|
+
```sh
|
128
|
+
psql> SELECT age FROM users WHERE username='cbloggs';
|
129
|
+
age
|
130
|
+
-------
|
131
|
+
{"ct":[<lots of numbers>],"ore":[<lots and LOTS of numbers>]}
|
132
|
+
```
|
133
|
+
|
134
|
+
And that, as they say, is that.
|
135
|
+
|
136
|
+
|
137
|
+
## Indexing and Ordering
|
138
|
+
|
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.
|
141
|
+
|
142
|
+
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
|
+
|
144
|
+
```ruby
|
145
|
+
class User < ApplicationRecord
|
146
|
+
# Enables indexing and ORDER BY for this column, at the cost of reduced security
|
147
|
+
enquo_attr :age, enable_reduced_security_operations: true
|
148
|
+
end
|
149
|
+
```
|
150
|
+
|
151
|
+
### SECURITY ALERT
|
152
|
+
|
153
|
+
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:
|
155
|
+
|
156
|
+
* Identify all rows which have the same value for the column (although not what that value actually *is*); and
|
157
|
+
|
158
|
+
* Perform inference attacks to try and determine the approximate or exact value for the column of some or all of the rows.
|
159
|
+
|
160
|
+
The practical implications of these attack vectors varies wildly between different types of data, which makes it harder to decide if it's reasonable to allow reduced security operations.
|
161
|
+
Our recommended rule-of-thumb is that if the features you need can *only* be implemented if you either enable reduced security operations, or leave the data unencrypted, then enable them.
|
162
|
+
Otherwise, leave the default as-is.
|
163
|
+
|
164
|
+
|
165
|
+
## Saving Disk Space
|
166
|
+
|
167
|
+
While the power of `ActiveEnquo` is based around being able to *query* encrypted data, not all columns necessarily need to be queried.
|
168
|
+
If so, you can reduce the disk space requirements for those columns by setting `no_query: true` for those columns:
|
169
|
+
|
170
|
+
```ruby
|
171
|
+
class User < ApplicationRecord
|
172
|
+
# Disables querying for this column, and saves a heap of bytes on your disk space
|
173
|
+
enquo_attr :age, no_query: true
|
174
|
+
end
|
175
|
+
```
|
176
|
+
|
177
|
+
|
178
|
+
# Future Developments
|
179
|
+
|
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.
|
190
|
+
|
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.
|
193
|
+
|
194
|
+
|
195
|
+
# Contributing
|
196
|
+
|
197
|
+
For general guidelines for contributions, see [CONTRIBUTING.md](CONTRIBUTING.md).
|
198
|
+
Detailed information on developing `ActiveEnquo`, including how to run the test suite, can be found in [docs/DEVELOPMENT.md](DEVELOPMENT.md).
|
199
|
+
|
200
|
+
|
201
|
+
# Licence
|
202
|
+
|
203
|
+
Unless otherwise stated, everything in this repo is covered by the following
|
204
|
+
licence statement:
|
205
|
+
|
206
|
+
Copyright (C) 2022 Matt Palmer <matt@enquo.org>
|
207
|
+
|
208
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
209
|
+
of this software and associated documentation files (the "Software"), to deal
|
210
|
+
in the Software without restriction, including without limitation the rights
|
211
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
212
|
+
copies of the Software, and to permit persons to whom the Software is
|
213
|
+
furnished to do so, subject to the following conditions:
|
214
|
+
|
215
|
+
The above copyright notice and this permission notice shall be included in
|
216
|
+
all copies or substantial portions of the Software.
|
217
|
+
|
218
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
219
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
220
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
221
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
222
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
223
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
224
|
+
THE SOFTWARE.
|
@@ -0,0 +1,44 @@
|
|
1
|
+
begin
|
2
|
+
require "git-version-bump"
|
3
|
+
rescue LoadError
|
4
|
+
nil
|
5
|
+
end
|
6
|
+
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.name = "active_enquo"
|
9
|
+
|
10
|
+
s.version = GVB.version rescue "0.0.0.1.NOGVB"
|
11
|
+
s.date = GVB.date rescue Time.now.strftime("%Y-%m-%d")
|
12
|
+
|
13
|
+
s.platform = Gem::Platform::RUBY
|
14
|
+
|
15
|
+
s.summary = "ActiveRecord integration for encrypted querying operations"
|
16
|
+
|
17
|
+
s.authors = ["Matt Palmer"]
|
18
|
+
s.email = ["matt@enquo.org"]
|
19
|
+
s.homepage = "https://enquo.org"
|
20
|
+
|
21
|
+
s.metadata["homepage_uri"] = s.homepage
|
22
|
+
s.metadata["source_code_uri"] = "https://github.com/enquo/active_enquo"
|
23
|
+
s.metadata["changelog_uri"] = "https://github.com/enquo/active_enquo/releases"
|
24
|
+
s.metadata["bug_tracker_uri"] = "https://github.com/enquo/active_enquo/issues"
|
25
|
+
|
26
|
+
s.files = `git ls-files -z`.split("\0").reject { |f| f =~ /^(G|spec|Rakefile)/ }
|
27
|
+
|
28
|
+
s.required_ruby_version = ">= 2.7.0"
|
29
|
+
|
30
|
+
s.add_runtime_dependency "enquo-core", "~> 0.3"
|
31
|
+
s.add_runtime_dependency "activerecord", ">= 6"
|
32
|
+
|
33
|
+
s.add_development_dependency "bundler"
|
34
|
+
s.add_development_dependency "github-release"
|
35
|
+
s.add_development_dependency "guard-rspec"
|
36
|
+
s.add_development_dependency "pg"
|
37
|
+
s.add_development_dependency "rake", "~> 13.0"
|
38
|
+
# Needed for guard
|
39
|
+
s.add_development_dependency "rb-inotify", "~> 0.9"
|
40
|
+
s.add_development_dependency "redcarpet"
|
41
|
+
s.add_development_dependency "rspec"
|
42
|
+
s.add_development_dependency "simplecov"
|
43
|
+
s.add_development_dependency "yard"
|
44
|
+
end
|
data/docs/DEVELOPMENT.md
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
Here are some notes on how to do development on `ActiveEnquo`.
|
2
|
+
|
3
|
+
# Setup
|
4
|
+
|
5
|
+
1. Checkout the repo.
|
6
|
+
|
7
|
+
2. Ensure you've got a Ruby 2.7+ installation as your default Ruby.
|
8
|
+
|
9
|
+
3. Run `bundle install`.
|
10
|
+
|
11
|
+
... and you're ready to go.
|
12
|
+
|
13
|
+
|
14
|
+
# Running the test suite
|
15
|
+
|
16
|
+
Run the test suite with `rake spec`.
|
17
|
+
|
18
|
+
You'll need a Postgres database with the [`pg_enquo`](https://github.com/enquo/pg_enquo) extension installed.
|
19
|
+
Use the [standard `PG*` environment variables](https://www.postgresql.org/docs/current/libpq-envars.html) to control the where the test suite connects to.
|
20
|
+
If you're running a "test" `pg_enquo` database with `cargo pgx run pgNN`, then this should do the trick:
|
21
|
+
|
22
|
+
```sh
|
23
|
+
$ PGHOST=localhost PGDATABASE=pg_enquo PGPORT=288NN rake spec
|
24
|
+
```
|
data/lib/active_enquo.rb
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
require "active_record/connection_adapters/postgresql_adapter"
|
2
|
+
require "active_support/lazy_load_hooks"
|
3
|
+
|
4
|
+
require "enquo"
|
5
|
+
|
6
|
+
module ActiveEnquo
|
7
|
+
def self.root_key=(k)
|
8
|
+
@root = Enquo::Root.new(k)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.root
|
12
|
+
if @root.nil?
|
13
|
+
raise RuntimeError, "The ActiveEnquo root key must be set before calling ActiveEnquo.root"
|
14
|
+
end
|
15
|
+
|
16
|
+
@root
|
17
|
+
end
|
18
|
+
|
19
|
+
RootKey = Enquo::RootKey
|
20
|
+
|
21
|
+
module ActiveRecord
|
22
|
+
module ModelExtension
|
23
|
+
extend ActiveSupport::Concern
|
24
|
+
|
25
|
+
def _read_attribute(attr_name, &block)
|
26
|
+
t = self.class.attribute_types[attr_name]
|
27
|
+
if t.is_a?(::ActiveEnquo::Type)
|
28
|
+
relation = self.class.arel_table.name
|
29
|
+
value = @attributes.fetch_value(attr_name, &block)
|
30
|
+
field = ::ActiveEnquo.root.field(relation, attr_name)
|
31
|
+
begin
|
32
|
+
t.decrypt(value, @attributes.fetch_value(@primary_key).to_s, field)
|
33
|
+
rescue Enquo::Error
|
34
|
+
# If the record had not yet been inserted into the database at the time the
|
35
|
+
# attribute was originally written, then that attribute's context will be empty.
|
36
|
+
# This is troublesome, but it's tricky to solve at this layer, so we'll have to
|
37
|
+
# take the risk and try and decryption with empty context.
|
38
|
+
t.decrypt(value, "", field)
|
39
|
+
end
|
40
|
+
else
|
41
|
+
super
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def _write_attribute(attr_name, value)
|
46
|
+
t = self.class.attribute_types[attr_name]
|
47
|
+
if t.is_a?(::ActiveEnquo::Type)
|
48
|
+
relation = self.class.arel_table.name
|
49
|
+
field = ::ActiveEnquo.root.field(relation, attr_name)
|
50
|
+
attr_opts = self.class.enquo_attribute_options.fetch(attr_name.to_sym, {})
|
51
|
+
safety = if attr_opts[:enable_reduced_security_operations]
|
52
|
+
:unsafe
|
53
|
+
end
|
54
|
+
db_value = t.encrypt(value, @attributes.fetch_value(@primary_key).to_s, field, safety: safety, no_query: attr_opts[:no_query])
|
55
|
+
@attributes.write_from_user(attr_name, db_value)
|
56
|
+
else
|
57
|
+
super
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
module ClassMethods
|
62
|
+
def enquo(attr_name, value)
|
63
|
+
t = self.attribute_types[attr_name.to_s]
|
64
|
+
if t.is_a?(::ActiveEnquo::Type)
|
65
|
+
relation = self.arel_table.name
|
66
|
+
field = ::ActiveEnquo.root.field(relation, attr_name)
|
67
|
+
t.encrypt(value, "", field, safety: :unsafe)
|
68
|
+
else
|
69
|
+
raise ArgumentError, "Cannot produce encrypted value on a non-enquo attribute '#{attr_name}'"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def enquo_attr(attr_name, opts)
|
74
|
+
enquo_attribute_options[attr_name] = @enquo_attribute_options[attr_name].merge(opts)
|
75
|
+
end
|
76
|
+
|
77
|
+
def enquo_attribute_options
|
78
|
+
@enquo_attribute_options ||= Hash.new({})
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
module Postgres
|
85
|
+
module ConnectionAdapter
|
86
|
+
def initialize_type_map(m = type_map)
|
87
|
+
m.register_type "enquo_bigint", ActiveEnquo::Type::Bigint.new
|
88
|
+
|
89
|
+
super
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
class Type < ::ActiveRecord::Type::Value
|
95
|
+
class Bigint < Type
|
96
|
+
def type
|
97
|
+
:enquo_bigint
|
98
|
+
end
|
99
|
+
|
100
|
+
def encrypt(value, context, field, safety: true, no_query: false)
|
101
|
+
field.encrypt_i64(value, context, safety: safety, no_query: no_query)
|
102
|
+
end
|
103
|
+
|
104
|
+
def decrypt(value, context, field)
|
105
|
+
field.decrypt_i64(value, context)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
ActiveSupport.on_load(:active_record) do
|
112
|
+
::ActiveRecord::Base.send :include, ActiveEnquo::ActiveRecord::ModelExtension
|
113
|
+
|
114
|
+
::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend ActiveEnquo::Postgres::ConnectionAdapter
|
115
|
+
# ::ActiveRecord::Type.register(:enquo_bigint, ActiveEnquo::Type::Bigint, adapter: :postgresql)
|
116
|
+
|
117
|
+
unless ActiveRecord::VERSION::MAJOR == 7
|
118
|
+
::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:enquo_bigint] = {}
|
119
|
+
end
|
120
|
+
end
|
metadata
ADDED
@@ -0,0 +1,225 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: active_enquo
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Matt Palmer
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-10-03 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: enquo-core
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.3'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activerecord
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '6'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '6'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: github-release
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: guard-rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: pg
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rake
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '13.0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '13.0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rb-inotify
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0.9'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0.9'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: redcarpet
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: rspec
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: simplecov
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: yard
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - ">="
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0'
|
181
|
+
description:
|
182
|
+
email:
|
183
|
+
- matt@enquo.org
|
184
|
+
executables: []
|
185
|
+
extensions: []
|
186
|
+
extra_rdoc_files: []
|
187
|
+
files:
|
188
|
+
- ".editorconfig"
|
189
|
+
- ".github/workflows/release.yml"
|
190
|
+
- ".gitignore"
|
191
|
+
- ".yardopts"
|
192
|
+
- CODE_OF_CONDUCT.md
|
193
|
+
- CONTRIBUTING.md
|
194
|
+
- LICENCE
|
195
|
+
- README.md
|
196
|
+
- active_enquo.gemspec
|
197
|
+
- docs/DEVELOPMENT.md
|
198
|
+
- lib/active_enquo.rb
|
199
|
+
homepage: https://enquo.org
|
200
|
+
licenses: []
|
201
|
+
metadata:
|
202
|
+
homepage_uri: https://enquo.org
|
203
|
+
source_code_uri: https://github.com/enquo/active_enquo
|
204
|
+
changelog_uri: https://github.com/enquo/active_enquo/releases
|
205
|
+
bug_tracker_uri: https://github.com/enquo/active_enquo/issues
|
206
|
+
post_install_message:
|
207
|
+
rdoc_options: []
|
208
|
+
require_paths:
|
209
|
+
- lib
|
210
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
211
|
+
requirements:
|
212
|
+
- - ">="
|
213
|
+
- !ruby/object:Gem::Version
|
214
|
+
version: 2.7.0
|
215
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
216
|
+
requirements:
|
217
|
+
- - ">="
|
218
|
+
- !ruby/object:Gem::Version
|
219
|
+
version: '0'
|
220
|
+
requirements: []
|
221
|
+
rubygems_version: 3.1.6
|
222
|
+
signing_key:
|
223
|
+
specification_version: 4
|
224
|
+
summary: ActiveRecord integration for encrypted querying operations
|
225
|
+
test_files: []
|