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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 31ac55f3c53d7f1c6755f96f294cceacbc6f972891b46e809c1e4cc8222f5615
4
- data.tar.gz: 2e918c0bbdffc6d95a60c6eb39e7c8f2a3345939e71c2d6d85c9745139cccb92
3
+ metadata.gz: '069dd6143d3148a6257bae40ed42db39bfd44658e8bb416f2aa02ee3b4d72017'
4
+ data.tar.gz: 000657ed15f6e5f7ba0f6da4d1bd2a9a026981236f0a4f42a52a735d7b2318c5
5
5
  SHA512:
6
- metadata.gz: a872b172252d8df672af874f3dbacad680951fe6a178f428e4e7cad399a2d8b31d8bb62c9d5138ea354b26841b2e98b8c2e6a2c43fd37685d2bb4ee24b808727
7
- data.tar.gz: efcdc09a8877b2a790b991a6f55bd399df7c29babc811ff1d10da8b5f0e2eaa584cd85dc3056798e68c69cd84852253f0c8dbff53647ba7e32a79f3e4b6191cf
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
- Keys are intentionally not included in the archive. Provision `/etc/secure_db_fields/keys.env` on the DB host before using decrypt/search UDFs.
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
- sudo make install
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
- Keys are not included in this archive. The DBA must provision `/etc/secure_db_fields/keys.env`
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 [ ! -e "$SDF_KEY_FILE" ]; then
188
- install -m 0640 -o root -g mysql /dev/null "$SDF_KEY_FILE"
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
- printf 'plugin_dir=%s\n' "$(plugin_dir)"
6
- printf 'registered_functions=%s\n' "$(function_count)"
7
- if mysql_cmd -e 'SELECT secure_db_fields_version() AS version;' >/dev/null 2>&1; then
8
- mysql_cmd -e 'SELECT secure_db_fields_version() AS version;'
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 'secure_db_fields_version() is not enabled'
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 version remains `0.1.1`; these are implementation-level hot-path optimizations.
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. Extract it on the MySQL 5.7
43
- host and run `make doctor`, `sudo make install`, `make enable`, `make status`.
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
@@ -9,7 +9,7 @@
9
9
  #endif
10
10
  #include <openssl/sha.h>
11
11
 
12
- #define SDF_VERSION "0.1.1"
12
+ #define SDF_VERSION "0.1.2"
13
13
 
14
14
  #define SDF_MAGIC "MCEN"
15
15
  #define SDF_MAGIC_LEN 4
@@ -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 = 1
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 source bundle for the DBA. The DBA
59
- extracts it on the database host and runs `make doctor`, `sudo make
60
- install`, `make enable`, then `make status`.
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
- DeploymentBundle.new(output, force: options[:force]).write!
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 initialize(path = ENV.fetch("SECURE_DB_FIELDS_KEY_FILE", DEFAULT_PATH))
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SecureDBFields
4
- VERSION = "0.1.1"
4
+ VERSION = "0.1.2"
5
5
  end
@@ -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-0.1.1.gem" --target "$PACKED" >/dev/null
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/secure_db_fields-0.1.1/lib" "$PACKED/secure_db_fields-0.1.1/exe/secure_db_fields" "$@"
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
- ruby_from_packaged db package mysql --output "$ARCHIVE" --force
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 'secure_db_fields-mysql-0.1.1/Makefile' >/dev/null
43
- tar -tzf "$ARCHIVE" | grep -Fx 'secure_db_fields-mysql-0.1.1/bin/install' >/dev/null
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 /opt/secure_db_fields-mysql-0.1.1
74
- tar -xzf /tmp/secure_db_fields-mysql.tar.gz -C /opt
75
- cd /opt/secure_db_fields-mysql-0.1.1
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" 'secure_db_fields 0.1.1'
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 'cd /opt/secure_db_fields-mysql-0.1.1 && CONFIRM=REMOVE_SECURE_DB_FIELDS make uninstall'
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.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-08 00:00:00.000000000 Z
11
+ date: 2026-07-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest