Class: SecureCredentials::Store

Inherits:
Object
  • Object
show all
Defined in:
lib/secure_credentials/store.rb

Overview

Store provides read-write interface for YAML configuration files. It supports:

- both encrypted and plain files,
- both file-per-environment and multi-environment files.

It takes base path of configuration file (for example, config/secrets) and environment value. Then it tries to find the most appropriate file for this configuration in following order:

"#{base}.#{env}.yml.enc"
"#{base}.#{env}.yml"
"#{base}.yml.enc"
"#{base}.yml"

Key for decoding encoded files can be passed:

- in `key` argument;
- envvar identified by `env_key`, default is to upcased basename appended with `_KEY`
  (ex., `SECRETS_KEY`);
- in file found at `key_path`,
  by default it uses filename and replaces `.yml.enc` with `.key`
  (`secrets.production.key` for `secrets.production.yml.enc`);
- SecureCredentials.master_key.

If environment specific file is present, it’s whole content is returned. Otherwise env is used to fetch appropriate section.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path, env: nil, key: nil, key_path: nil, env_key: nil) ⇒ Store

Returns a new instance of Store.



74
75
76
77
78
79
80
81
# File 'lib/secure_credentials/store.rb', line 74

def initialize(path, env: nil, key: nil, key_path: nil, env_key: nil)
  @path = path = Pathname.new(path)
  @env = env
  @environmental, @encrypted, @filename = self.class.detect_filename(path, env)
  @key = key
  @key_path = key_path || self.class.detect_key_path_for(filename)
  @env_key = env_key || self.class.env_key_for(path)
end

Instance Attribute Details

#encryptedObject (readonly) Also known as: encrypted?

Returns the value of attribute encrypted.



70
71
72
# File 'lib/secure_credentials/store.rb', line 70

def encrypted
  @encrypted
end

#envObject (readonly)

Returns the value of attribute env.



70
71
72
# File 'lib/secure_credentials/store.rb', line 70

def env
  @env
end

#environmentalObject (readonly) Also known as: environmental?

Returns the value of attribute environmental.



70
71
72
# File 'lib/secure_credentials/store.rb', line 70

def environmental
  @environmental
end

#filenameObject (readonly)

Returns the value of attribute filename.



70
71
72
# File 'lib/secure_credentials/store.rb', line 70

def filename
  @filename
end

#pathObject (readonly)

Returns the value of attribute path.



70
71
72
# File 'lib/secure_credentials/store.rb', line 70

def path
  @path
end

Class Method Details

.detect_filename(path, env) ⇒ Object

Finds the most appropriate existing file for given path and env. Returns ‘[environmental?, encrypted?, filename]`.



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/secure_credentials/store.rb', line 35

def detect_filename(path, env)
  # Backward compatibility with original Rails implementation:
  # if filename is given with extension then we don't try to detect
  # environmental and/or encrypted variant.
  if path.basename.to_s =~ /\.yml(\.enc)?\z/i
    [false, path.basename.to_s.end_with?('.enc'), path]
  else
    stub_ext_path = Pathname.new("#{path}.stub")
    [
      [true,  true,   stub_ext_path.sub_ext(".#{env}.yml.enc")],
      [true,  false,  stub_ext_path.sub_ext(".#{env}.yml")],
      [false, true,   stub_ext_path.sub_ext('.yml.enc')],
      [false, false,  stub_ext_path.sub_ext('.yml')],
    ].find { |x| x[2].exist? }
  end
end

.detect_key_path_for(path) ⇒ Object

Looks for key file for given path replacing .yml.enc with .key. It falls back to config/master.key in Rails app if file does not exist.



54
55
56
57
58
# File 'lib/secure_credentials/store.rb', line 54

def detect_key_path_for(path)
  return unless path.to_s.end_with?('.yml.enc')
  key_path = path.sub_ext('').sub_ext('.key')
  key_path.exist? || !defined?(::Rails) ? key_path : ::Rails.root.join('config/master.key')
end

.env_key_for(path) ⇒ Object



60
61
62
# File 'lib/secure_credentials/store.rb', line 60

def env_key_for(path)
  "#{path.basename.to_s.upcase}_KEY"
end

.load_yaml(string) ⇒ Object

ERB -> YAML.safe_load with aliases support.



65
66
67
# File 'lib/secure_credentials/store.rb', line 65

def load_yaml(string)
  YAML.safe_load(ERB.new(string).result, [], [], true)
end

Instance Method Details

#change(&block) ⇒ Object

Prepares file for edition, yields filename and then saves updated file.

Raises:



101
102
103
104
105
106
107
108
# File 'lib/secure_credentials/store.rb', line 101

def change(&block)
  raise FileNotFound, "File not found for '#{path}'" unless filename && filename.exist?
  if encrypted?
    encrypted_file.change(&block)
  else
    yield filename
  end
end

#contentObject

Fetches appropriate environmental content or returns whole content in the case of single-environment file.



85
86
87
88
# File 'lib/secure_credentials/store.rb', line 85

def content
  result = environmental? ? full_content : full_content[env.to_s]
  result || {}
end

#readObject

Read file content.



91
92
93
94
95
96
97
98
# File 'lib/secure_credentials/store.rb', line 91

def read
  return '' unless filename && filename.exist?
  if encrypted?
    encrypted_file.read
  else
    filename.read
  end
end