mecha_encrypt 0.1.0
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 +7 -0
- data/LICENSE +19 -0
- data/README.md +123 -0
- data/Rakefile +11 -0
- data/lib/mecha_encrypt/auditor/prober.rb +307 -0
- data/lib/mecha_encrypt/auditor/static.rb +357 -0
- data/lib/mecha_encrypt/configuration.rb +71 -0
- data/lib/mecha_encrypt/errors.rb +11 -0
- data/lib/mecha_encrypt/matrix_test.rb +119 -0
- data/lib/mecha_encrypt/railtie.rb +11 -0
- data/lib/mecha_encrypt/registry.rb +195 -0
- data/lib/mecha_encrypt/version.rb +6 -0
- data/lib/mecha_encrypt.rb +39 -0
- data/lib/tasks/mecha_encrypt.rake +180 -0
- metadata +83 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
|
|
5
|
+
module MechaEncrypt
|
|
6
|
+
class Registry
|
|
7
|
+
attr_reader :tables, :ignored_tables, :colorization
|
|
8
|
+
|
|
9
|
+
def initialize(config_path_or_hash = nil)
|
|
10
|
+
@tables = {}
|
|
11
|
+
@ignored_tables = []
|
|
12
|
+
@colorization = {}
|
|
13
|
+
|
|
14
|
+
if config_path_or_hash.is_a?(Hash)
|
|
15
|
+
load_from_hash(config_path_or_hash)
|
|
16
|
+
elsif config_path_or_hash
|
|
17
|
+
load_from_file(config_path_or_hash)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def load_from_file(path)
|
|
22
|
+
raise ConfigurationError, "Encryption configuration file missing: #{path}" unless File.exist?(path)
|
|
23
|
+
|
|
24
|
+
data = begin
|
|
25
|
+
YAML.load_file(path)
|
|
26
|
+
rescue => e
|
|
27
|
+
raise ConfigurationError, "Invalid YAML syntax in #{path}: #{e.message}"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
load_from_hash(data)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def load_from_hash(data)
|
|
34
|
+
unless data.is_a?(Hash) && data.key?("tables") && (data["tables"].is_a?(Hash) || data["tables"].is_a?(Array))
|
|
35
|
+
raise ConfigurationError, "Encryption config must have a top-level 'tables' mapping or list"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
@ignored_tables = parse_ignored_tables(data["ignored_tables"] || [])
|
|
39
|
+
@tables = parse_tables(data["tables"])
|
|
40
|
+
|
|
41
|
+
if data["debug_colorization"].is_a?(Hash)
|
|
42
|
+
@colorization = data["debug_colorization"].transform_keys(&:to_sym).transform_values do |v|
|
|
43
|
+
v.to_s.sub(/^:/, "").to_sym
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
@ignored_tables.freeze
|
|
48
|
+
@tables.freeze
|
|
49
|
+
self
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def table_registered?(table_name)
|
|
53
|
+
@tables.key?(table_name.to_s)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def table_columns(table_name)
|
|
57
|
+
@tables[table_name.to_s] || {}
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def column_entry(table_name, column_name)
|
|
61
|
+
table_columns(table_name)[column_name.to_s]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def column_registered?(table_name, column_name)
|
|
65
|
+
!column_entry(table_name, column_name).nil?
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def ignored_table_entry_for(table_name)
|
|
69
|
+
t_name = table_name.to_s
|
|
70
|
+
@ignored_tables.find do |entry|
|
|
71
|
+
File.fnmatch?(entry[:pattern], t_name)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def table_ignored?(table_name)
|
|
76
|
+
!ignored_table_entry_for(table_name).nil?
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def encrypted_columns
|
|
80
|
+
result = []
|
|
81
|
+
@tables.each_value do |cols|
|
|
82
|
+
cols.each_value do |col_entry|
|
|
83
|
+
result << col_entry if col_entry[:encrypted]
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
result
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
private
|
|
90
|
+
|
|
91
|
+
def parse_ignored_tables(raw_list)
|
|
92
|
+
unless raw_list.is_a?(Array)
|
|
93
|
+
raise ConfigurationError, "'ignored_tables' must be an Array"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
seen_patterns = {}
|
|
97
|
+
raw_list.each_with_index.map do |item, idx|
|
|
98
|
+
unless item.is_a?(Hash)
|
|
99
|
+
raise ConfigurationError, "ignored_tables entry at index #{idx} must be a Hash"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
pattern = (item["pattern"] || item["table"]).to_s.strip
|
|
103
|
+
if pattern.empty?
|
|
104
|
+
raise ConfigurationError, "ignored_tables entry at index #{idx} must specify a non-empty 'pattern' or 'table'"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
if seen_patterns.key?(pattern)
|
|
108
|
+
raise ConfigurationError, "Duplicate ignored_tables pattern '#{pattern}'"
|
|
109
|
+
end
|
|
110
|
+
seen_patterns[pattern] = true
|
|
111
|
+
|
|
112
|
+
{
|
|
113
|
+
pattern: pattern,
|
|
114
|
+
comment: item["comment"]&.to_s&.strip
|
|
115
|
+
}.freeze
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def parse_tables(raw_tables)
|
|
120
|
+
parsed = {}
|
|
121
|
+
|
|
122
|
+
if raw_tables.is_a?(Hash)
|
|
123
|
+
raw_tables.each do |table_name, raw_columns|
|
|
124
|
+
t_name = table_name.to_s.strip
|
|
125
|
+
if t_name.empty?
|
|
126
|
+
raise ConfigurationError, "Table name in 'tables' cannot be empty"
|
|
127
|
+
end
|
|
128
|
+
parsed[t_name] = parse_columns_for_table(t_name, raw_columns)
|
|
129
|
+
end
|
|
130
|
+
elsif raw_tables.is_a?(Array)
|
|
131
|
+
raw_tables.each_with_index do |entry, idx|
|
|
132
|
+
unless entry.is_a?(Hash) && entry["table"].is_a?(String) && !entry["table"].strip.empty?
|
|
133
|
+
raise ConfigurationError, "Table entry at index #{idx} must be a Hash with a non-empty 'table' name"
|
|
134
|
+
end
|
|
135
|
+
t_name = entry["table"].strip
|
|
136
|
+
if parsed.key?(t_name)
|
|
137
|
+
raise ConfigurationError, "Duplicate table entry '#{t_name}' in encryption configuration"
|
|
138
|
+
end
|
|
139
|
+
parsed[t_name] = parse_columns_for_table(t_name, entry["columns"])
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
parsed
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def parse_columns_for_table(table_name, raw_columns)
|
|
147
|
+
unless raw_columns.is_a?(Hash)
|
|
148
|
+
raise ConfigurationError, "Columns for table '#{table_name}' must be a Hash mapping column names to definitions"
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
cols = {}
|
|
152
|
+
raw_columns.each do |col_name, spec|
|
|
153
|
+
c_name = col_name.to_s.strip
|
|
154
|
+
if c_name.empty?
|
|
155
|
+
raise ConfigurationError, "Column name in table '#{table_name}' cannot be empty"
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
unless spec.is_a?(Hash)
|
|
159
|
+
raise ConfigurationError, "Definition for '#{table_name}.#{c_name}' must be a Hash"
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
unless [true, false].include?(spec["encrypted"])
|
|
163
|
+
raise ConfigurationError,
|
|
164
|
+
"Definition for '#{table_name}.#{c_name}' must explicitly specify boolean 'encrypted: true' or 'encrypted: false'"
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
encrypted = spec["encrypted"]
|
|
168
|
+
deterministic = if spec.key?("deterministic")
|
|
169
|
+
unless [true, false].include?(spec["deterministic"])
|
|
170
|
+
raise ConfigurationError,
|
|
171
|
+
"Definition for '#{table_name}.#{c_name}' has invalid 'deterministic' value (must be boolean true or false)"
|
|
172
|
+
end
|
|
173
|
+
spec["deterministic"]
|
|
174
|
+
else
|
|
175
|
+
false
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
if !encrypted && deterministic
|
|
179
|
+
raise ConfigurationError,
|
|
180
|
+
"Definition for '#{table_name}.#{c_name}' cannot specify 'deterministic: true' when 'encrypted: false'"
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
cols[c_name] = {
|
|
184
|
+
table: table_name,
|
|
185
|
+
column: c_name,
|
|
186
|
+
encrypted: encrypted,
|
|
187
|
+
deterministic: deterministic,
|
|
188
|
+
comment: spec["comment"]&.to_s&.strip
|
|
189
|
+
}.freeze
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
cols.freeze
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "mecha_encrypt/version"
|
|
4
|
+
require_relative "mecha_encrypt/errors"
|
|
5
|
+
require_relative "mecha_encrypt/configuration"
|
|
6
|
+
require_relative "mecha_encrypt/registry"
|
|
7
|
+
require_relative "mecha_encrypt/auditor/static"
|
|
8
|
+
require_relative "mecha_encrypt/auditor/prober"
|
|
9
|
+
require_relative "mecha_encrypt/railtie" if defined?(Rails::Railtie)
|
|
10
|
+
|
|
11
|
+
module MechaEncrypt
|
|
12
|
+
class << self
|
|
13
|
+
def configuration
|
|
14
|
+
@configuration ||= Configuration.new
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def configure
|
|
18
|
+
yield(configuration) if block_given?
|
|
19
|
+
reload!
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def reset_configuration!
|
|
23
|
+
@configuration = Configuration.new
|
|
24
|
+
@registry = nil
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def registry
|
|
28
|
+
@registry ||= Registry.new(configuration.config_path)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def reload!
|
|
32
|
+
@registry = Registry.new(configuration.config_path)
|
|
33
|
+
if @registry.colorization.any?
|
|
34
|
+
@configuration.debug_colorization = @configuration.debug_colorization.merge(@registry.colorization)
|
|
35
|
+
end
|
|
36
|
+
@registry
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "tiny_color"
|
|
4
|
+
require_relative "../mecha_encrypt"
|
|
5
|
+
|
|
6
|
+
class String
|
|
7
|
+
def dark_grey
|
|
8
|
+
respond_to?(:grey) ? grey : self
|
|
9
|
+
end unless method_defined?(:dark_grey)
|
|
10
|
+
|
|
11
|
+
def dark_gray
|
|
12
|
+
respond_to?(:gray) ? gray : self
|
|
13
|
+
end unless method_defined?(:dark_gray)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
desc "Print all database tables and columns in alphabetical order with their MechaEncrypt status"
|
|
17
|
+
task mecha_encrypt: :environment do
|
|
18
|
+
auditor = MechaEncrypt::Auditor::Static.new
|
|
19
|
+
result = auditor.audit
|
|
20
|
+
colors = MechaEncrypt.configuration.debug_colorization
|
|
21
|
+
|
|
22
|
+
colorize = ->(text, key) {
|
|
23
|
+
color = colors[key]
|
|
24
|
+
if color && text.respond_to?(color)
|
|
25
|
+
text.send(color)
|
|
26
|
+
else
|
|
27
|
+
text
|
|
28
|
+
end
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
all_cols = result.all_column_audits
|
|
32
|
+
max_col_len = [all_cols.map { |c| c.column.to_s.length }.max || 6, 6].max
|
|
33
|
+
max_type_len = [all_cols.map { |c| c.sql_type.to_s.length }.max || 8, 8].max
|
|
34
|
+
max_status_len = 25
|
|
35
|
+
|
|
36
|
+
puts "=== MechaEncrypt Database Column Encryption Registry ==="
|
|
37
|
+
|
|
38
|
+
result.table_audits.each do |table_audit|
|
|
39
|
+
model_label = table_audit.model ? " (#{table_audit.model})" : ""
|
|
40
|
+
puts "\n#{table_audit.table}#{model_label}"
|
|
41
|
+
|
|
42
|
+
if table_audit.ignored
|
|
43
|
+
padded_status = sprintf("%-#{max_status_len}s", "IGNORED TABLE")
|
|
44
|
+
puts sprintf(
|
|
45
|
+
" %-#{max_col_len}s %-#{max_type_len}s %s %s",
|
|
46
|
+
"*",
|
|
47
|
+
"-",
|
|
48
|
+
colorize.call(padded_status, :ignored),
|
|
49
|
+
table_audit.ignore_comment
|
|
50
|
+
)
|
|
51
|
+
next
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
puts sprintf(
|
|
55
|
+
" %-#{max_col_len}s %-#{max_type_len}s %-#{max_status_len}s %s",
|
|
56
|
+
"Column",
|
|
57
|
+
"SQL Type",
|
|
58
|
+
"Encryption Status",
|
|
59
|
+
"Comment / Details"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
table_audit.columns.each do |col|
|
|
63
|
+
padded_col = sprintf("%-#{max_col_len}s", col.column)
|
|
64
|
+
padded_type = sprintf("%-#{max_type_len}s", col.sql_type)
|
|
65
|
+
|
|
66
|
+
status_label = case col.status
|
|
67
|
+
when :encrypted
|
|
68
|
+
"ENCRYPTED"
|
|
69
|
+
when :deterministic
|
|
70
|
+
"ENCRYPTED (DETERMINISTIC)"
|
|
71
|
+
when :unencrypted
|
|
72
|
+
"NOT ENCRYPTED"
|
|
73
|
+
when :unmapped
|
|
74
|
+
"UNMAPPED"
|
|
75
|
+
when :violation
|
|
76
|
+
"VIOLATION"
|
|
77
|
+
else
|
|
78
|
+
col.status.to_s.upcase
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
padded_status = sprintf("%-#{max_status_len}s", status_label)
|
|
82
|
+
|
|
83
|
+
detail = if col.status == :violation || col.status == :unmapped
|
|
84
|
+
col.violation_message
|
|
85
|
+
else
|
|
86
|
+
col.comment.presence || "-"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
puts sprintf(
|
|
90
|
+
" %s %s %s %s",
|
|
91
|
+
colorize.call(padded_col, col.status),
|
|
92
|
+
padded_type,
|
|
93
|
+
colorize.call(padded_status, col.status),
|
|
94
|
+
detail
|
|
95
|
+
)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
namespace :mecha_encrypt do
|
|
101
|
+
namespace :verify do
|
|
102
|
+
desc "Statically verify database column encryption registry parity, model declarations, and comments"
|
|
103
|
+
task static: :environment do
|
|
104
|
+
puts "Running MechaEncrypt static database & model encryption audit..."
|
|
105
|
+
auditor = MechaEncrypt::Auditor::Static.new
|
|
106
|
+
result = auditor.audit
|
|
107
|
+
|
|
108
|
+
audited_tables_count = result.audited_tables.size
|
|
109
|
+
ignored_tables_count = result.ignored_tables.size
|
|
110
|
+
all_cols = result.all_column_audits
|
|
111
|
+
encrypted_count = all_cols.count { |c| c.status == :encrypted }
|
|
112
|
+
deterministic_count = all_cols.count { |c| c.status == :deterministic }
|
|
113
|
+
unencrypted_count = all_cols.count { |c| c.status == :unencrypted }
|
|
114
|
+
|
|
115
|
+
puts " -> #{audited_tables_count} tables audited (#{ignored_tables_count} framework/engine tables ignored via pattern)"
|
|
116
|
+
puts " -> #{all_cols.size} columns audited (#{encrypted_count} encrypted, #{deterministic_count} deterministic, #{unencrypted_count} unencrypted)"
|
|
117
|
+
|
|
118
|
+
if result.unmapped_tables.any?
|
|
119
|
+
puts "\nERROR: Found #{result.unmapped_tables.size} unregistered table(s) missing from config/mecha_encrypt.yml:".light_red
|
|
120
|
+
result.unmapped_tables.each { |t| puts " - #{t}" }
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
if result.unmapped_columns.any?
|
|
124
|
+
puts "\nERROR: Found #{result.unmapped_columns.size} unregistered database column(s) missing from config/mecha_encrypt.yml:".light_red
|
|
125
|
+
result.unmapped_columns.each { |c| puts " - #{c}" }
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
if result.unencrypted_in_model.any?
|
|
129
|
+
puts "\nERROR: Found #{result.unencrypted_in_model.size} column(s) registered as encrypted in YAML but NOT encrypted on the model:".light_red
|
|
130
|
+
result.unencrypted_in_model.each { |err| puts " - #{err}" }
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
if result.undeclared_encrypted_in_model.any?
|
|
134
|
+
puts "\nERROR: Found #{result.undeclared_encrypted_in_model.size} column(s) encrypted on the model but marked unencrypted in YAML:".light_red
|
|
135
|
+
result.undeclared_encrypted_in_model.each { |err| puts " - #{err}" }
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
if result.mode_mismatches.any?
|
|
139
|
+
puts "\nERROR: Found #{result.mode_mismatches.size} encryption determinism mismatch(es) between YAML and model:".light_red
|
|
140
|
+
result.mode_mismatches.each { |err| puts " - #{err}" }
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
if result.invalid_comments.any?
|
|
144
|
+
puts "\nERROR: Found #{result.invalid_comments.size} non-encrypted field/table entry(ies) without a valid comment:".light_red
|
|
145
|
+
result.invalid_comments.each { |err| puts " - #{err}" }
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
if result.dead_tables.any?
|
|
149
|
+
puts "\nERROR: Found #{result.dead_tables.size} dead/stale table(s) in config/mecha_encrypt.yml:".light_red
|
|
150
|
+
result.dead_tables.each { |t| puts " - #{t}" }
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
if result.dead_columns.any?
|
|
154
|
+
puts "\nERROR: Found #{result.dead_columns.size} dead/stale column(s) in config/mecha_encrypt.yml:".light_red
|
|
155
|
+
result.dead_columns.each { |c| puts " - #{c}" }
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
if result.dead_ignored_patterns.any?
|
|
159
|
+
puts "\nERROR: Found #{result.dead_ignored_patterns.size} dead ignored_tables pattern(s) matching 0 tables:".light_red
|
|
160
|
+
result.dead_ignored_patterns.each { |p| puts " - #{p}" }
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
if result.success?
|
|
164
|
+
puts "\n✓ Static database encryption audit passed: 0 unregistered columns, 0 model mismatches, 0 missing comments.".green
|
|
165
|
+
else
|
|
166
|
+
abort("\n✗ Static database encryption audit failed. Please resolve the errors above.")
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
desc "Run behavioral ciphertext round-trip and index safety test suite"
|
|
171
|
+
task behavioral: :environment do
|
|
172
|
+
puts "Running MechaEncrypt behavioral encryption & index safety test suite..."
|
|
173
|
+
matrix_test_path = File.expand_path("../mecha_encrypt/matrix_test.rb", __dir__)
|
|
174
|
+
system({ "MECHA_MATRIX_STANDALONE" => "1" }, "bin/rails", "test", matrix_test_path) || abort("Behavioral encryption verification failed.")
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
desc "Run both static schema/model verification and behavioral ciphertext verification"
|
|
179
|
+
task verify: %w[verify:static verify:behavioral]
|
|
180
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: mecha_encrypt
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Jeff Lunt
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: activesupport
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '7.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '7.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: tiny_color
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0'
|
|
40
|
+
description: MechaEncrypt enforces database column encryption parity in Rails applications
|
|
41
|
+
via a YAML schema registry, static schema-to-model auditing, and behavioral ciphertext
|
|
42
|
+
& index safety probing.
|
|
43
|
+
email:
|
|
44
|
+
- jefflunt@gmail.com
|
|
45
|
+
executables: []
|
|
46
|
+
extensions: []
|
|
47
|
+
extra_rdoc_files: []
|
|
48
|
+
files:
|
|
49
|
+
- LICENSE
|
|
50
|
+
- README.md
|
|
51
|
+
- Rakefile
|
|
52
|
+
- lib/mecha_encrypt.rb
|
|
53
|
+
- lib/mecha_encrypt/auditor/prober.rb
|
|
54
|
+
- lib/mecha_encrypt/auditor/static.rb
|
|
55
|
+
- lib/mecha_encrypt/configuration.rb
|
|
56
|
+
- lib/mecha_encrypt/errors.rb
|
|
57
|
+
- lib/mecha_encrypt/matrix_test.rb
|
|
58
|
+
- lib/mecha_encrypt/railtie.rb
|
|
59
|
+
- lib/mecha_encrypt/registry.rb
|
|
60
|
+
- lib/mecha_encrypt/version.rb
|
|
61
|
+
- lib/tasks/mecha_encrypt.rake
|
|
62
|
+
homepage: https://github.com/jefflunt/mecha_encrypt
|
|
63
|
+
licenses:
|
|
64
|
+
- MIT
|
|
65
|
+
metadata: {}
|
|
66
|
+
rdoc_options: []
|
|
67
|
+
require_paths:
|
|
68
|
+
- lib
|
|
69
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
70
|
+
requirements:
|
|
71
|
+
- - ">="
|
|
72
|
+
- !ruby/object:Gem::Version
|
|
73
|
+
version: 3.2.0
|
|
74
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
75
|
+
requirements:
|
|
76
|
+
- - ">="
|
|
77
|
+
- !ruby/object:Gem::Version
|
|
78
|
+
version: '0'
|
|
79
|
+
requirements: []
|
|
80
|
+
rubygems_version: 4.0.16
|
|
81
|
+
specification_version: 4
|
|
82
|
+
summary: Closed-by-default database column encryption registry and auditing for Rails
|
|
83
|
+
test_files: []
|