Skip to content

Commit e110e85

Browse files
committed
feat: config-driven key sources — env, file, and keychain discovery
Keys in cyphera.json can now use 'source' instead of 'material': - source: env — read key from environment variable - source: file — read key from file path - source: aws-kms, gcp-kms, azure-kv, vault — delegate to keychain Code never changes — going from dev to prod is a config file change.
1 parent 78ff92f commit e110e85

1 file changed

Lines changed: 41 additions & 2 deletions

File tree

lib/cyphera/cyphera.rb

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,15 @@ def initialize(config)
6363
@keys = {}
6464

6565
(config['keys'] || {}).each do |name, val|
66-
material = val.is_a?(String) ? val : val['material']
67-
@keys[name] = [material].pack('H*')
66+
if val.is_a?(String)
67+
@keys[name] = [val].pack('H*')
68+
elsif val['material']
69+
@keys[name] = [val['material']].pack('H*')
70+
elsif val['source']
71+
@keys[name] = self.class.resolve_key_source(name, val)
72+
else
73+
raise ArgumentError, "Key '#{name}' must have either 'material' or 'source'"
74+
end
6875
end
6976

7077
(config['policies'] || {}).each do |name, pol|
@@ -103,6 +110,38 @@ def resolve_key(key_ref)
103110
@keys.fetch(key_ref) { raise ArgumentError, "Unknown key: #{key_ref}" }
104111
end
105112

113+
CLOUD_SOURCES = %w[aws-kms gcp-kms azure-kv vault].freeze
114+
115+
def self.resolve_key_source(name, config)
116+
source = config['source']
117+
118+
case source
119+
when 'env'
120+
var_name = config['var'] or raise ArgumentError, "Key '#{name}': source 'env' requires 'var' field"
121+
val = ENV[var_name] or raise ArgumentError, "Key '#{name}': environment variable '#{var_name}' is not set"
122+
encoding = config['encoding'] || 'hex'
123+
return encoding == 'base64' ? val.unpack1('m') : [val].pack('H*')
124+
when 'file'
125+
path = config['path'] or raise ArgumentError, "Key '#{name}': source 'file' requires 'path' field"
126+
raw = File.read(path).strip
127+
encoding = config['encoding'] || (path.end_with?('.b64', '.base64') ? 'base64' : 'hex')
128+
return encoding == 'base64' ? raw.unpack1('m') : [raw].pack('H*')
129+
end
130+
131+
if CLOUD_SOURCES.include?(source)
132+
begin
133+
require 'cyphera-keychain'
134+
return CypheraKeychain.resolve(source, config)
135+
rescue LoadError
136+
raise LoadError,
137+
"Key '#{name}' requires source '#{source}' but cyphera-keychain is not installed.\n" \
138+
"Install it: gem install cyphera-keychain"
139+
end
140+
end
141+
142+
raise ArgumentError, "Key '#{name}': unknown source '#{source}'. Valid: env, file, #{CLOUD_SOURCES.join(', ')}"
143+
end
144+
106145
def resolve_alphabet(name)
107146
return ALPHABETS['alphanumeric'] if name.nil? || name.empty?
108147
ALPHABETS[name] || name

0 commit comments

Comments
 (0)