secure_db_fields 0.1.1 → 0.1.2
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 +1 -1
- data/db_deployment/mysql/README.md +6 -7
- data/db_deployment/mysql/bin/common +25 -2
- data/db_deployment/mysql/bin/enable +2 -0
- data/db_deployment/mysql/bin/status +50 -6
- data/docs/implementation_notes.md +1 -1
- data/exe/secure_db_fields +4 -3
- data/ext/secure_db_fields/secure_db_fields_core.h +1 -1
- data/lib/secure_db_fields/db_deployment.rb +96 -8
- data/lib/secure_db_fields/keys.rb +17 -1
- data/lib/secure_db_fields/version.rb +1 -1
- data/mysql_udf/test/run_e2e.sh +29 -28
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '069dd6143d3148a6257bae40ed42db39bfd44658e8bb416f2aa02ee3b4d72017'
|
|
4
|
+
data.tar.gz: 000657ed15f6e5f7ba0f6da4d1bd2a9a026981236f0a4f42a52a735d7b2318c5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 40f26a4ba2d838e85f5076f7a61326b182b3f35b73b8c354304449f47a8c7b87a2ee2869d08503d2f5f8a595dcf838e78f27ba66d3b0d58b8432c12c834451eb
|
|
7
|
+
data.tar.gz: 0db619099f71e3795886b7e6d694690079ef7deac98c6b1b5f7d6a8e5b321b559302564169d61377c1c891c5634659a447f924c7d75874198d103cad504822be
|
data/README.md
CHANGED
|
@@ -82,7 +82,7 @@ make enable
|
|
|
82
82
|
make status
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
-
|
|
85
|
+
The command creates or reuses `config/secure_db_fields/keys.env` and includes that same key contract in the DBA archive. `make install` installs it on the DB host as `/etc/secure_db_fields/keys.env`, so the application and MySQL use the same keys without manual HEX copying.
|
|
86
86
|
|
|
87
87
|
Readable view SQL can be generated from the application side as well:
|
|
88
88
|
|
|
@@ -6,18 +6,17 @@ This archive is produced on an application host with:
|
|
|
6
6
|
secure_db_fields db package mysql --output secure_db_fields-mysql.tar.gz --force
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
Copy the archive to the MySQL 5.7 host, extract it, then run:
|
|
9
|
+
The archive contains the MySQL UDF source and the same `keys.env` contract used by the application. Copy the archive to the MySQL 5.7 host, extract it, then run:
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
12
|
make verify
|
|
13
|
-
make doctor
|
|
14
|
-
|
|
15
|
-
make enable
|
|
16
|
-
make status
|
|
13
|
+
make doctor MYSQL_DEFAULTS_FILE=/root/secure-db-fields-mysql.cnf MYSQL_SOCKET=/run/mysqld/mysqld.sock
|
|
14
|
+
make install MYSQL_DEFAULTS_FILE=/root/secure-db-fields-mysql.cnf MYSQL_SOCKET=/run/mysqld/mysqld.sock
|
|
15
|
+
make enable MYSQL_DEFAULTS_FILE=/root/secure-db-fields-mysql.cnf MYSQL_SOCKET=/run/mysqld/mysqld.sock
|
|
16
|
+
make status MYSQL_DEFAULTS_FILE=/root/secure-db-fields-mysql.cnf MYSQL_SOCKET=/run/mysqld/mysqld.sock
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
(or set `SECURE_DB_FIELDS_KEY_FILE` for mysqld) before using decrypt/search UDFs.
|
|
19
|
+
`make install` installs `etc/secure_db_fields/keys.env` from this bundle to `/etc/secure_db_fields/keys.env` with `0640 root:mysql` permissions.
|
|
21
20
|
|
|
22
21
|
Installed SQL functions:
|
|
23
22
|
|
|
@@ -12,6 +12,7 @@ SDF_UDF_NAMES=(
|
|
|
12
12
|
secure_phone_prefix_bidx
|
|
13
13
|
)
|
|
14
14
|
SDF_KEY_FILE="${SECURE_DB_FIELDS_KEY_FILE:-/etc/secure_db_fields/keys.env}"
|
|
15
|
+
SDF_BUNDLED_KEY_FILE="$ROOT/etc/secure_db_fields/keys.env"
|
|
15
16
|
|
|
16
17
|
sdf_die() {
|
|
17
18
|
echo "secure_db_fields mysql: $*" >&2
|
|
@@ -156,6 +157,14 @@ sdf_smoke_check() {
|
|
|
156
157
|
esac
|
|
157
158
|
}
|
|
158
159
|
|
|
160
|
+
sdf_key_smoke_check() {
|
|
161
|
+
local digest
|
|
162
|
+
digest="$(sdf_query "SELECT HEX(secure_phone_bidx('+77771234567'));" | tr -d '\r\n')"
|
|
163
|
+
if ! printf '%s' "$digest" | grep -Eq '^[0-9A-F]{64}$'; then
|
|
164
|
+
sdf_die "key smoke check failed; secure_phone_bidx returned ${digest:-empty}"
|
|
165
|
+
fi
|
|
166
|
+
}
|
|
167
|
+
|
|
159
168
|
sdf_library_checksum() {
|
|
160
169
|
local library="$1"
|
|
161
170
|
if [ ! -f "$library" ]; then
|
|
@@ -180,16 +189,30 @@ sdf_check_key_file_permissions() {
|
|
|
180
189
|
fi
|
|
181
190
|
}
|
|
182
191
|
|
|
192
|
+
sdf_validate_key_file_values() {
|
|
193
|
+
local file="${1:-$SDF_KEY_FILE}"
|
|
194
|
+
grep -Eq '^SDF_ACTIVE_KEY_ID=[1-9][0-9]*$' "$file" || sdf_die "key file missing SDF_ACTIVE_KEY_ID: $file"
|
|
195
|
+
if ! grep -Eq '^SDF_ENC_KEY_1_HEX=[0-9a-fA-F]{64}$|^SDF_ENC_KEY_HEX=[0-9a-fA-F]{64}$' "$file"; then
|
|
196
|
+
sdf_die "key file missing SDF_ENC_KEY_1_HEX or SDF_ENC_KEY_HEX: $file"
|
|
197
|
+
fi
|
|
198
|
+
grep -Eq '^SDF_BIDX_KEY_HEX=[0-9a-fA-F]{64}$' "$file" || sdf_die "key file missing SDF_BIDX_KEY_HEX: $file"
|
|
199
|
+
grep -Eq '^SDF_BIDX_PHONE_KEY_HEX=[0-9a-fA-F]{64}$' "$file" || sdf_die "key file missing SDF_BIDX_PHONE_KEY_HEX: $file"
|
|
200
|
+
grep -Eq '^SDF_BIDX_PHONE_P7_KEY_HEX=[0-9a-fA-F]{64}$' "$file" || sdf_die "key file missing SDF_BIDX_PHONE_P7_KEY_HEX: $file"
|
|
201
|
+
}
|
|
202
|
+
|
|
183
203
|
sdf_prepare_key_file() {
|
|
184
204
|
local dir
|
|
185
205
|
dir="$(dirname "$SDF_KEY_FILE")"
|
|
186
206
|
install -d -m 0750 -o root -g mysql "$dir"
|
|
187
|
-
if [
|
|
188
|
-
install -m 0640 -o root -g mysql
|
|
207
|
+
if [ -f "$SDF_BUNDLED_KEY_FILE" ]; then
|
|
208
|
+
install -m 0640 -o root -g mysql "$SDF_BUNDLED_KEY_FILE" "$SDF_KEY_FILE"
|
|
209
|
+
elif [ ! -e "$SDF_KEY_FILE" ]; then
|
|
210
|
+
sdf_die "key file is missing and bundle does not contain $SDF_BUNDLED_KEY_FILE"
|
|
189
211
|
fi
|
|
190
212
|
chown root:mysql "$SDF_KEY_FILE"
|
|
191
213
|
chmod 0640 "$SDF_KEY_FILE"
|
|
192
214
|
sdf_check_key_file_permissions "$SDF_KEY_FILE"
|
|
215
|
+
sdf_validate_key_file_values "$SDF_KEY_FILE"
|
|
193
216
|
}
|
|
194
217
|
|
|
195
218
|
sdf_build_udf() {
|
|
@@ -42,12 +42,14 @@ if sdf_state_is_clean_absent; then
|
|
|
42
42
|
sdf_read_udf_state
|
|
43
43
|
sdf_state_is_clean_enabled || sdf_die 'registration did not create the expected UDF state'
|
|
44
44
|
sdf_smoke_check
|
|
45
|
+
sdf_key_smoke_check
|
|
45
46
|
echo 'SecureDBFields MySQL UDFs are enabled. Apply generated admin-view SQL separately.'
|
|
46
47
|
exit 0
|
|
47
48
|
fi
|
|
48
49
|
|
|
49
50
|
if sdf_state_is_clean_enabled; then
|
|
50
51
|
sdf_smoke_check
|
|
52
|
+
sdf_key_smoke_check
|
|
51
53
|
echo 'SecureDBFields MySQL UDFs are already enabled; no changes made.'
|
|
52
54
|
exit 0
|
|
53
55
|
fi
|
|
@@ -1,11 +1,55 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
set -euo pipefail
|
|
3
|
-
source "$(dirname "$0")/common"
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
5
|
+
MYSQL="${MYSQL:-mysql}"
|
|
6
|
+
DEFAULTS_FILE=""
|
|
7
|
+
MYSQL_SOCKET="${MYSQL_SOCKET:-}"
|
|
8
|
+
|
|
9
|
+
usage() {
|
|
10
|
+
cat <<'TEXT'
|
|
11
|
+
Usage: MYSQL_SOCKET=/run/mysqld/mysqld.sock ./bin/status [--mysql PATH] [--defaults-extra-file PATH]
|
|
12
|
+
TEXT
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
while [ "$#" -gt 0 ]; do
|
|
16
|
+
case "$1" in
|
|
17
|
+
--mysql)
|
|
18
|
+
[ "$#" -ge 2 ] || { echo '--mysql requires a path' >&2; exit 2; }
|
|
19
|
+
MYSQL="$2"; shift 2 ;;
|
|
20
|
+
--defaults-extra-file)
|
|
21
|
+
[ "$#" -ge 2 ] || { echo '--defaults-extra-file requires a path' >&2; exit 2; }
|
|
22
|
+
DEFAULTS_FILE="$2"; shift 2 ;;
|
|
23
|
+
--help|-h)
|
|
24
|
+
usage; exit 0 ;;
|
|
25
|
+
*)
|
|
26
|
+
echo "secure_db_fields mysql status: unknown option: $1" >&2; exit 2 ;;
|
|
27
|
+
esac
|
|
28
|
+
done
|
|
29
|
+
|
|
30
|
+
source "$ROOT/bin/common"
|
|
31
|
+
sdf_resolve_mysql
|
|
32
|
+
sdf_mysql_args
|
|
33
|
+
sdf_require_mysql57
|
|
34
|
+
sdf_read_udf_state
|
|
35
|
+
|
|
36
|
+
plugin_dir="$(sdf_plugin_dir)"
|
|
37
|
+
printf 'plugin_dir=%s\n' "$plugin_dir"
|
|
38
|
+
printf 'registered_functions=%s\n' "$SDF_EXPECTED_OWNED"
|
|
39
|
+
|
|
40
|
+
if sdf_state_is_clean_enabled; then
|
|
41
|
+
echo 'state=enabled'
|
|
42
|
+
sdf_smoke_check
|
|
43
|
+
if sdf_key_smoke_check >/dev/null 2>&1; then
|
|
44
|
+
echo 'key_smoke=ok'
|
|
45
|
+
else
|
|
46
|
+
echo 'key_smoke=failed'
|
|
47
|
+
fi
|
|
48
|
+
sdf_query 'SELECT secure_db_fields_version();'
|
|
49
|
+
elif sdf_state_is_clean_absent; then
|
|
50
|
+
echo 'state=disabled'
|
|
9
51
|
else
|
|
10
|
-
echo '
|
|
52
|
+
echo 'state=partial_or_conflicting'
|
|
53
|
+
printf '%s\n' "${SDF_REGISTERED_ROWS[@]}"
|
|
54
|
+
exit 2
|
|
11
55
|
fi
|
|
@@ -60,4 +60,4 @@ The Ruby extension has separate scalar and batch paths:
|
|
|
60
60
|
- Ruby encrypt/decrypt paths allocate the destination Ruby String first and call
|
|
61
61
|
`sdf_*_aes_256_gcm_into`, avoiding native `malloc -> rb_str_new copy -> free`.
|
|
62
62
|
|
|
63
|
-
The
|
|
63
|
+
The hot-path notes below describe the 0.1.1 implementation-level optimizations. Version 0.1.2 adds the DBA bundle key-contract flow without changing the envelope format.
|
data/exe/secure_db_fields
CHANGED
|
@@ -35,12 +35,13 @@ module SecureDBFields
|
|
|
35
35
|
<<~TEXT
|
|
36
36
|
Usage:
|
|
37
37
|
secure_db_fields --version
|
|
38
|
-
secure_db_fields db package mysql [--output PATH] [--force]
|
|
38
|
+
secure_db_fields db package mysql [--output PATH] [--keys PATH] [--force]
|
|
39
39
|
secure_db_fields db view mysql --table DB.TABLE --field NAME:ENC_COLUMN \\
|
|
40
40
|
--uid-column secure_row_uid --view DB.VIEW --columns ID,CREATED_AT [--output PATH]
|
|
41
41
|
|
|
42
|
-
`db package mysql` creates a DBA handoff archive
|
|
43
|
-
|
|
42
|
+
`db package mysql` creates a DBA handoff archive containing the shared
|
|
43
|
+
key contract used by the application. Extract it on the MySQL 5.7 host
|
|
44
|
+
and run `make doctor`, `make install`, `make enable`, `make status`.
|
|
44
45
|
TEXT
|
|
45
46
|
end
|
|
46
47
|
end
|
|
@@ -13,7 +13,7 @@ require_relative "version"
|
|
|
13
13
|
module SecureDBFields
|
|
14
14
|
module DBDeployment
|
|
15
15
|
ROOT = File.expand_path("../..", __dir__)
|
|
16
|
-
BUNDLE_FORMAT_VERSION =
|
|
16
|
+
BUNDLE_FORMAT_VERSION = 2
|
|
17
17
|
MYSQL_TARGET = "mysql"
|
|
18
18
|
|
|
19
19
|
class Error < StandardError; end
|
|
@@ -49,15 +49,15 @@ module SecureDBFields
|
|
|
49
49
|
def help
|
|
50
50
|
<<~TEXT
|
|
51
51
|
Usage:
|
|
52
|
-
secure_db_fields db package mysql [--output PATH] [--force]
|
|
52
|
+
secure_db_fields db package mysql [--output PATH] [--keys PATH] [--force]
|
|
53
53
|
|
|
54
54
|
secure_db_fields db view mysql --table DB.TABLE --field NAME:ENC_COLUMN \\
|
|
55
55
|
--uid-column secure_row_uid --view DB.VIEW --columns ID,CREATED_AT \\
|
|
56
56
|
[--as NAME] [--output PATH]
|
|
57
57
|
|
|
58
|
-
`package` creates a self-contained
|
|
59
|
-
extracts it on the database host and
|
|
60
|
-
|
|
58
|
+
`package` creates a self-contained DBA bundle with the same key contract
|
|
59
|
+
used by the application. The DBA extracts it on the database host and
|
|
60
|
+
runs `make doctor`, `sudo make install`, then `make enable`.
|
|
61
61
|
|
|
62
62
|
`view` emits an admin readable view. It is for projection/display only;
|
|
63
63
|
indexed search must use physical bidx columns and helper procedures or
|
|
@@ -72,10 +72,11 @@ module SecureDBFields
|
|
|
72
72
|
raise OptionParser::MissingArgument, "TARGET must be mysql" if target.nil?
|
|
73
73
|
raise OptionParser::InvalidArgument, "TARGET must be mysql" unless target == MYSQL_TARGET
|
|
74
74
|
|
|
75
|
-
options = { output: nil, force: false }
|
|
75
|
+
options = { output: nil, force: false, keys: nil }
|
|
76
76
|
parser = OptionParser.new do |opts|
|
|
77
77
|
opts.banner = "Usage: secure_db_fields db package mysql [OPTIONS]"
|
|
78
78
|
opts.on("-o", "--output PATH", "write the deployment archive to PATH") { |value| options[:output] = value }
|
|
79
|
+
opts.on("--keys PATH", "use or create the shared key contract at PATH") { |value| options[:keys] = value }
|
|
79
80
|
opts.on("-f", "--force", "overwrite an existing archive") { options[:force] = true }
|
|
80
81
|
opts.on("-h", "--help", "show this help") { puts opts; return 0 }
|
|
81
82
|
end
|
|
@@ -83,12 +84,96 @@ module SecureDBFields
|
|
|
83
84
|
raise OptionParser::InvalidOption, argv.join(" ") unless argv.empty?
|
|
84
85
|
|
|
85
86
|
output = options[:output] || "secure_db_fields-mysql-#{SecureDBFields::VERSION}.tar.gz"
|
|
86
|
-
|
|
87
|
+
contract = KeyContract.ensure!(options[:keys] || KeyContract.default_path)
|
|
88
|
+
DeploymentBundle.new(output, force: options[:force], key_file: contract.path).write!
|
|
87
89
|
puts File.expand_path(output)
|
|
88
90
|
0
|
|
89
91
|
end
|
|
90
92
|
end
|
|
91
93
|
|
|
94
|
+
class KeyContract
|
|
95
|
+
DEFAULT_PATH = "config/secure_db_fields/keys.env"
|
|
96
|
+
HEX = /\A[0-9a-fA-F]{64}\z/.freeze
|
|
97
|
+
REQUIRED_HEX_KEYS = %w[SDF_BIDX_KEY_HEX SDF_BIDX_PHONE_KEY_HEX SDF_BIDX_PHONE_P7_KEY_HEX].freeze
|
|
98
|
+
|
|
99
|
+
attr_reader :path
|
|
100
|
+
|
|
101
|
+
def self.default_path
|
|
102
|
+
File.expand_path(DEFAULT_PATH, Dir.pwd)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def self.ensure!(path)
|
|
106
|
+
full = File.expand_path(path)
|
|
107
|
+
if File.exist?(full)
|
|
108
|
+
validate!(full)
|
|
109
|
+
else
|
|
110
|
+
write_new!(full)
|
|
111
|
+
end
|
|
112
|
+
new(full)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def self.write_new!(path)
|
|
116
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
117
|
+
enc = SecureRandom.hex(32)
|
|
118
|
+
bidx = SecureRandom.hex(32)
|
|
119
|
+
phone = bidx
|
|
120
|
+
phone_p7 = bidx
|
|
121
|
+
body = <<~ENV
|
|
122
|
+
SDF_ACTIVE_KEY_ID=1
|
|
123
|
+
SDF_ENC_KEY_1_HEX=#{enc}
|
|
124
|
+
SDF_ENC_KEY_HEX=#{enc}
|
|
125
|
+
SDF_BIDX_KEY_HEX=#{bidx}
|
|
126
|
+
SDF_BIDX_PHONE_KEY_HEX=#{phone}
|
|
127
|
+
SDF_BIDX_PHONE_P7_KEY_HEX=#{phone_p7}
|
|
128
|
+
ENV
|
|
129
|
+
temporary = File.join(File.dirname(path), "secure-db-fields-keys-#{Process.pid}-#{SecureRandom.hex(8)}.tmp")
|
|
130
|
+
File.open(temporary, File::WRONLY | File::CREAT | File::EXCL, 0o600) { |file| file.write(body) }
|
|
131
|
+
File.chmod(0o600, temporary)
|
|
132
|
+
File.rename(temporary, path)
|
|
133
|
+
ensure
|
|
134
|
+
File.delete(temporary) if defined?(temporary) && File.exist?(temporary)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def self.validate!(path)
|
|
138
|
+
stat = File.stat(path)
|
|
139
|
+
raise Error, "key contract must be a regular file: #{path}" unless stat.file?
|
|
140
|
+
raise Error, "key contract permissions are too open: #{path}" unless (stat.mode & 0o077).zero?
|
|
141
|
+
values = parse(path)
|
|
142
|
+
active = values["SDF_ACTIVE_KEY_ID"]
|
|
143
|
+
raise Error, "key contract missing SDF_ACTIVE_KEY_ID: #{path}" unless active && active.match?(/\A[1-9][0-9]*\z/)
|
|
144
|
+
unless hex?(values["SDF_ENC_KEY_#{active}_HEX"]) || hex?(values["SDF_ENC_KEY_HEX"])
|
|
145
|
+
raise Error, "key contract missing SDF_ENC_KEY_#{active}_HEX or SDF_ENC_KEY_HEX: #{path}"
|
|
146
|
+
end
|
|
147
|
+
REQUIRED_HEX_KEYS.each do |name|
|
|
148
|
+
raise Error, "key contract missing #{name}: #{path}" unless hex?(values[name])
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def self.parse(path)
|
|
153
|
+
values = {}
|
|
154
|
+
File.readlines(path, chomp: true).each do |line|
|
|
155
|
+
text = line.strip
|
|
156
|
+
next if text.empty? || text.start_with?("#")
|
|
157
|
+
key, value = text.split("=", 2)
|
|
158
|
+
next unless key && value
|
|
159
|
+
values[key.strip] = value.strip
|
|
160
|
+
end
|
|
161
|
+
values
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def self.hex?(value)
|
|
165
|
+
value && HEX.match?(value)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def initialize(path)
|
|
169
|
+
@path = path
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def data
|
|
173
|
+
File.binread(path)
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
92
177
|
class ViewCommand
|
|
93
178
|
IDENTIFIER = /\A[A-Za-z_][A-Za-z0-9_]*\z/.freeze
|
|
94
179
|
QUALIFIED_IDENTIFIER = /\A[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)?\z/.freeze
|
|
@@ -221,9 +306,10 @@ module SecureDBFields
|
|
|
221
306
|
|
|
222
307
|
attr_reader :output
|
|
223
308
|
|
|
224
|
-
def initialize(output, force: false)
|
|
309
|
+
def initialize(output, force: false, key_file:)
|
|
225
310
|
@output = output
|
|
226
311
|
@force = force
|
|
312
|
+
@key_file = key_file
|
|
227
313
|
end
|
|
228
314
|
|
|
229
315
|
def write!
|
|
@@ -239,6 +325,7 @@ module SecureDBFields
|
|
|
239
325
|
end
|
|
240
326
|
add_text(tar, "VERSION", SecureDBFields::VERSION + "\n")
|
|
241
327
|
add_text(tar, "BUNDLE_FORMAT", BUNDLE_FORMAT_VERSION.to_s + "\n")
|
|
328
|
+
add_bytes(tar, File.join(bundle_name, "etc/secure_db_fields/keys.env"), File.binread(@key_file), 0o600)
|
|
242
329
|
add_text(tar, "SHA256SUMS", checksums)
|
|
243
330
|
end
|
|
244
331
|
end
|
|
@@ -275,6 +362,7 @@ module SecureDBFields
|
|
|
275
362
|
end
|
|
276
363
|
entries << ["VERSION", SecureDBFields::VERSION + "\n"]
|
|
277
364
|
entries << ["BUNDLE_FORMAT", BUNDLE_FORMAT_VERSION.to_s + "\n"]
|
|
365
|
+
entries << ["etc/secure_db_fields/keys.env", File.binread(@key_file)]
|
|
278
366
|
|
|
279
367
|
entries.sort_by(&:first).map do |dst, data|
|
|
280
368
|
"#{Digest::SHA256.hexdigest(data)} #{dst}"
|
|
@@ -3,10 +3,26 @@
|
|
|
3
3
|
module SecureDBFields
|
|
4
4
|
class Keyring
|
|
5
5
|
DEFAULT_PATH = "/etc/secure_db_fields/keys.env"
|
|
6
|
+
APP_RELATIVE_PATH = "config/secure_db_fields/keys.env"
|
|
6
7
|
|
|
7
8
|
attr_reader :path
|
|
8
9
|
|
|
9
|
-
def
|
|
10
|
+
def self.default_path
|
|
11
|
+
env = ENV["SECURE_DB_FIELDS_KEY_FILE"]
|
|
12
|
+
return env if env && !env.empty?
|
|
13
|
+
|
|
14
|
+
if defined?(Rails) && Rails.respond_to?(:root) && Rails.root
|
|
15
|
+
rails_path = Rails.root.join(APP_RELATIVE_PATH).to_s
|
|
16
|
+
return rails_path if File.exist?(rails_path)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
app_path = File.expand_path(APP_RELATIVE_PATH, Dir.pwd)
|
|
20
|
+
return app_path if File.exist?(app_path)
|
|
21
|
+
|
|
22
|
+
DEFAULT_PATH
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def initialize(path = self.class.default_path)
|
|
10
26
|
@path = path
|
|
11
27
|
@values = parse_file(path)
|
|
12
28
|
end
|
data/mysql_udf/test/run_e2e.sh
CHANGED
|
@@ -7,8 +7,12 @@ PLATFORM="${SDF_MYSQL_PLATFORM:-linux/amd64}"
|
|
|
7
7
|
WAIT_SECONDS="${SDF_MYSQL_WAIT_SECONDS:-120}"
|
|
8
8
|
CONTAINER="sdf-mysql57-e2e-${RANDOM}-${RANDOM}"
|
|
9
9
|
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
10
|
+
GEM_VERSION="$(ruby -I "$ROOT/lib" -rsecure_db_fields/version -e 'print SecureDBFields::VERSION')"
|
|
11
|
+
PACKAGE_ROOT="secure_db_fields-${GEM_VERSION}"
|
|
12
|
+
BUNDLE_ROOT_NAME="secure_db_fields-mysql-${GEM_VERSION}"
|
|
10
13
|
TMPDIR="$(mktemp -d)"
|
|
11
14
|
ARCHIVE="$TMPDIR/secure_db_fields-mysql.tar.gz"
|
|
15
|
+
CONTRACT="$TMPDIR/keys.env"
|
|
12
16
|
PACKED="$TMPDIR/packed"
|
|
13
17
|
BUNDLE_PARENT="/opt"
|
|
14
18
|
KEY_HEX_A="6161616161616161616161616161616161616161616161616161616161616161"
|
|
@@ -29,18 +33,32 @@ prepare_packaged_gem() {
|
|
|
29
33
|
rm -rf "$PACKED"
|
|
30
34
|
mkdir -p "$PACKED"
|
|
31
35
|
(cd "$ROOT" && gem build secure_db_fields.gemspec >/dev/null)
|
|
32
|
-
gem unpack "$ROOT/secure_db_fields
|
|
36
|
+
gem unpack "$ROOT/secure_db_fields-${GEM_VERSION}.gem" --target "$PACKED" >/dev/null
|
|
33
37
|
}
|
|
34
38
|
|
|
35
39
|
ruby_from_packaged() {
|
|
36
|
-
ruby -I "$PACKED/
|
|
40
|
+
ruby -I "$PACKED/$PACKAGE_ROOT/lib" "$PACKED/$PACKAGE_ROOT/exe/secure_db_fields" "$@"
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
write_contract() {
|
|
44
|
+
cat > "$CONTRACT" <<EOF_CONTRACT
|
|
45
|
+
SDF_ACTIVE_KEY_ID=1
|
|
46
|
+
SDF_ENC_KEY_1_HEX=$KEY_HEX_A
|
|
47
|
+
SDF_ENC_KEY_HEX=$KEY_HEX_A
|
|
48
|
+
SDF_BIDX_KEY_HEX=$KEY_HEX_B
|
|
49
|
+
SDF_BIDX_PHONE_KEY_HEX=$KEY_HEX_B
|
|
50
|
+
SDF_BIDX_PHONE_P7_KEY_HEX=$KEY_HEX_C
|
|
51
|
+
EOF_CONTRACT
|
|
52
|
+
chmod 0600 "$CONTRACT"
|
|
37
53
|
}
|
|
38
54
|
|
|
39
55
|
build_deployment_bundle() {
|
|
40
|
-
|
|
56
|
+
write_contract
|
|
57
|
+
ruby_from_packaged db package mysql --output "$ARCHIVE" --keys "$CONTRACT" --force
|
|
41
58
|
test -s "$ARCHIVE"
|
|
42
|
-
tar -tzf "$ARCHIVE" | grep -Fx
|
|
43
|
-
tar -tzf "$ARCHIVE" | grep -Fx
|
|
59
|
+
tar -tzf "$ARCHIVE" | grep -Fx "$BUNDLE_ROOT_NAME/Makefile" >/dev/null
|
|
60
|
+
tar -tzf "$ARCHIVE" | grep -Fx "$BUNDLE_ROOT_NAME/bin/install" >/dev/null
|
|
61
|
+
tar -tzf "$ARCHIVE" | grep -Fx "$BUNDLE_ROOT_NAME/etc/secure_db_fields/keys.env" >/dev/null
|
|
44
62
|
if tar -tzf "$ARCHIVE" | grep -E '/\.DS_Store$' >/dev/null; then
|
|
45
63
|
echo 'deployment bundle must not contain .DS_Store' >&2
|
|
46
64
|
exit 2
|
|
@@ -67,32 +85,16 @@ install_mysql_build_tools() {
|
|
|
67
85
|
}
|
|
68
86
|
|
|
69
87
|
install_bundle_on_db_host() {
|
|
70
|
-
local bundle_root="$BUNDLE_PARENT/secure_db_fields-mysql-0.1.1"
|
|
71
88
|
docker cp "$ARCHIVE" "$CONTAINER:/tmp/secure_db_fields-mysql.tar.gz"
|
|
72
|
-
docker exec -u 0 "$CONTAINER" sh -ceu
|
|
73
|
-
rm -rf
|
|
74
|
-
tar -xzf /tmp/secure_db_fields-mysql.tar.gz -C
|
|
75
|
-
cd
|
|
89
|
+
docker exec -u 0 "$CONTAINER" sh -ceu "
|
|
90
|
+
rm -rf '$BUNDLE_PARENT/$BUNDLE_ROOT_NAME'
|
|
91
|
+
tar -xzf /tmp/secure_db_fields-mysql.tar.gz -C '$BUNDLE_PARENT'
|
|
92
|
+
cd '$BUNDLE_PARENT/$BUNDLE_ROOT_NAME'
|
|
76
93
|
make verify
|
|
77
94
|
make doctor
|
|
78
95
|
make install
|
|
79
96
|
make enable
|
|
80
97
|
make status
|
|
81
|
-
'
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
write_key_file() {
|
|
85
|
-
docker exec -u 0 "$CONTAINER" sh -ceu "
|
|
86
|
-
install -d -m 0750 -o root -g mysql /etc/secure_db_fields
|
|
87
|
-
cat > /etc/secure_db_fields/keys.env <<'EOF'
|
|
88
|
-
SDF_ACTIVE_KEY_ID=1
|
|
89
|
-
SDF_ENC_KEY_1_HEX=$KEY_HEX_A
|
|
90
|
-
SDF_BIDX_KEY_HEX=$KEY_HEX_B
|
|
91
|
-
SDF_BIDX_PHONE_KEY_HEX=$KEY_HEX_B
|
|
92
|
-
SDF_BIDX_PHONE_P7_KEY_HEX=$KEY_HEX_C
|
|
93
|
-
EOF
|
|
94
|
-
chown root:mysql /etc/secure_db_fields/keys.env
|
|
95
|
-
chmod 0640 /etc/secure_db_fields/keys.env
|
|
96
98
|
"
|
|
97
99
|
}
|
|
98
100
|
|
|
@@ -187,7 +189,6 @@ until docker exec "$CONTAINER" mysqladmin ping --silent 2>/dev/null; do
|
|
|
187
189
|
done
|
|
188
190
|
|
|
189
191
|
install_mysql_build_tools
|
|
190
|
-
write_key_file
|
|
191
192
|
install_bundle_on_db_host
|
|
192
193
|
make_fixture_sql
|
|
193
194
|
|
|
@@ -195,7 +196,7 @@ docker exec -i "$CONTAINER" mysql --default-character-set=utf8mb4 < "$TMPDIR/app
|
|
|
195
196
|
docker exec -i "$CONTAINER" mysql --default-character-set=utf8mb4 < "$TMPDIR/view.sql"
|
|
196
197
|
|
|
197
198
|
version="$(q 'SELECT secure_db_fields_version();')"
|
|
198
|
-
assert_contains 'version' "$version"
|
|
199
|
+
assert_contains 'version' "$version" "secure_db_fields $GEM_VERSION"
|
|
199
200
|
assert_eq 'valid envelope' '1' "$(q 'SELECT secure_db_fields_is_valid_envelope(phone_enc) FROM app.clients WHERE id=1;')"
|
|
200
201
|
assert_eq 'envelope key id' '1' "$(q 'SELECT secure_db_fields_envelope_key_id(phone_enc) FROM app.clients WHERE id=1;')"
|
|
201
202
|
assert_eq 'decrypt field' '+77771234567' "$(q "SELECT secure_db_fields_decrypt_field(phone_enc, 'clients', 'phone', secure_row_uid) FROM app.clients WHERE id=1;")"
|
|
@@ -210,7 +211,7 @@ assert_contains 'EXPLAIN uses exact bidx index' "$explain_exact" 'idx_clients_ph
|
|
|
210
211
|
explain_prefix="$(q "EXPLAIN SELECT id FROM app.clients WHERE phone_bidx_p7 = secure_phone_prefix_bidx('+77771234567', 7);")"
|
|
211
212
|
assert_contains 'EXPLAIN uses prefix bidx index' "$explain_prefix" 'idx_clients_phone_bidx_p7'
|
|
212
213
|
|
|
213
|
-
docker exec -u 0 "$CONTAINER" sh -ceu
|
|
214
|
+
docker exec -u 0 "$CONTAINER" sh -ceu "cd '$BUNDLE_PARENT/$BUNDLE_ROOT_NAME' && CONFIRM=REMOVE_SECURE_DB_FIELDS make uninstall"
|
|
214
215
|
assert_eq 'functions removed' '0' "$(q "SELECT COUNT(*) FROM mysql.func WHERE dl = 'secure_db_fields_mysql.so';")"
|
|
215
216
|
|
|
216
217
|
echo 'secure_db_fields MySQL 5.7 UDF e2e: ok'
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: secure_db_fields
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Roman Khaidarov
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-07-
|
|
11
|
+
date: 2026-07-09 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: minitest
|