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