Class: SecureDBFields::DBDeployment::KeyContract

Inherits:
Object
  • Object
show all
Defined in:
lib/secure_db_fields/db_deployment.rb

Constant Summary collapse

DEFAULT_PATH =
"config/secure_db_fields/keys.env"
HEX =
/\A[0-9a-fA-F]{64}\z/.freeze
REQUIRED_HEX_KEYS =
%w[SDF_BIDX_KEY_HEX SDF_BIDX_PHONE_KEY_HEX SDF_BIDX_PHONE_P7_KEY_HEX].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path) ⇒ KeyContract

Returns a new instance of KeyContract.



168
169
170
# File 'lib/secure_db_fields/db_deployment.rb', line 168

def initialize(path)
  @path = path
end

Instance Attribute Details

#pathObject (readonly)

Returns the value of attribute path.



99
100
101
# File 'lib/secure_db_fields/db_deployment.rb', line 99

def path
  @path
end

Class Method Details

.default_pathObject



101
102
103
# File 'lib/secure_db_fields/db_deployment.rb', line 101

def self.default_path
  File.expand_path(DEFAULT_PATH, Dir.pwd)
end

.ensure!(path) ⇒ Object



105
106
107
108
109
110
111
112
113
# File 'lib/secure_db_fields/db_deployment.rb', line 105

def self.ensure!(path)
  full = File.expand_path(path)
  if File.exist?(full)
    validate!(full)
  else
    write_new!(full)
  end
  new(full)
end

.hex?(value) ⇒ Boolean

Returns:

  • (Boolean)


164
165
166
# File 'lib/secure_db_fields/db_deployment.rb', line 164

def self.hex?(value)
  value && HEX.match?(value)
end

.parse(path) ⇒ Object



152
153
154
155
156
157
158
159
160
161
162
# File 'lib/secure_db_fields/db_deployment.rb', line 152

def self.parse(path)
  values = {}
  File.readlines(path, chomp: true).each do |line|
    text = line.strip
    next if text.empty? || text.start_with?("#")
    key, value = text.split("=", 2)
    next unless key && value
    values[key.strip] = value.strip
  end
  values
end

.validate!(path) ⇒ Object

Raises:



137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/secure_db_fields/db_deployment.rb', line 137

def self.validate!(path)
  stat = File.stat(path)
  raise Error, "key contract must be a regular file: #{path}" unless stat.file?
  raise Error, "key contract permissions are too open: #{path}" unless (stat.mode & 0o077).zero?
  values = parse(path)
  active = values["SDF_ACTIVE_KEY_ID"]
  raise Error, "key contract missing SDF_ACTIVE_KEY_ID: #{path}" unless active && active.match?(/\A[1-9][0-9]*\z/)
  unless hex?(values["SDF_ENC_KEY_#{active}_HEX"]) || hex?(values["SDF_ENC_KEY_HEX"])
    raise Error, "key contract missing SDF_ENC_KEY_#{active}_HEX or SDF_ENC_KEY_HEX: #{path}"
  end
  REQUIRED_HEX_KEYS.each do |name|
    raise Error, "key contract missing #{name}: #{path}" unless hex?(values[name])
  end
end

.write_new!(path) ⇒ Object



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/secure_db_fields/db_deployment.rb', line 115

def self.write_new!(path)
  FileUtils.mkdir_p(File.dirname(path))
  enc = SecureRandom.hex(32)
  bidx = SecureRandom.hex(32)
  phone = bidx
  phone_p7 = bidx
  body = <<~ENV
    SDF_ACTIVE_KEY_ID=1
    SDF_ENC_KEY_1_HEX=#{enc}
    SDF_ENC_KEY_HEX=#{enc}
    SDF_BIDX_KEY_HEX=#{bidx}
    SDF_BIDX_PHONE_KEY_HEX=#{phone}
    SDF_BIDX_PHONE_P7_KEY_HEX=#{phone_p7}
  ENV
  temporary = File.join(File.dirname(path), "secure-db-fields-keys-#{Process.pid}-#{SecureRandom.hex(8)}.tmp")
  File.open(temporary, File::WRONLY | File::CREAT | File::EXCL, 0o600) { |file| file.write(body) }
  File.chmod(0o600, temporary)
  File.rename(temporary, path)
ensure
  File.delete(temporary) if defined?(temporary) && File.exist?(temporary)
end

Instance Method Details

#dataObject



172
173
174
# File 'lib/secure_db_fields/db_deployment.rb', line 172

def data
  File.binread(path)
end