Module: Onboardbase

Defined in:
lib/onboardbase.rb,
lib/onboardbase/version.rb

Constant Summary collapse

VERSION =
'1.2.1'

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.envObject

Returns the value of attribute env.



11
12
13
# File 'lib/onboardbase.rb', line 11

def env
  @env
end

.is_devObject

Returns the value of attribute is_dev.



11
12
13
# File 'lib/onboardbase.rb', line 11

def is_dev
  @is_dev
end

Class Method Details

.aes256_cbc_decrypt(key, data, iv) ⇒ Object



168
169
170
171
172
173
174
175
176
# File 'lib/onboardbase.rb', line 168

def aes256_cbc_decrypt(key, data, iv)
  key = Digest::SHA256.digest(key) if(key.kind_of?(String) && 32 != key.bytesize)
  iv = Digest::MD5.digest(iv) if(iv.kind_of?(String) && 16 != iv.bytesize)
  aes = OpenSSL::Cipher.new('AES-256-CBC')
  aes.decrypt
  aes.key = key
  aes.iv = iv
  aes.update(data) + aes.final
end

.apiURLObject



46
47
48
49
# File 'lib/onboardbase.rb', line 46

def apiURL
  return "https://devapi.onboardbase.com/graphql" if self.is_dev
  "https://api.onboardbase.com/graphql"
end

.b64enc(data) ⇒ Object



42
43
44
# File 'lib/onboardbase.rb', line 42

def b64enc(data)
  Base64.encode64(data).gsub(/\n/, '')
end

.bytes_to_key(data, salt, output = 48) ⇒ Object



157
158
159
160
161
162
163
164
165
166
# File 'lib/onboardbase.rb', line 157

def bytes_to_key(data, salt, output=48)
  merged = data + salt
  key = Digest::MD5.digest(merged)
  final_key = key
  while final_key.length < output
    key = Digest::MD5.digest(key + merged)
    final_key =  final_key + key
  end
  final_key[0..output-1]
end

.config {|_self| ... } ⇒ Object

Yields:

  • (_self)

Yield Parameters:

  • _self (Onboardbase)

    the object that the method was called on



58
59
60
# File 'lib/onboardbase.rb', line 58

def config
  yield self
end

.config_exists?(directory) ⇒ Boolean

Returns:

  • (Boolean)


85
86
87
# File 'lib/onboardbase.rb', line 85

def config_exists?(directory)
  return File.exist?(directory)
end

.configurationObject



51
52
53
# File 'lib/onboardbase.rb', line 51

def configuration
  @configuration ||= {}
end

.content_pathObject



255
256
257
# File 'lib/onboardbase.rb', line 255

def content_path
  ENV["RAILS_ENV"] ? "config/credentials/#{ENV["RAILS_ENV"]}.yml.enc" : "config/credentials.yml.enc"
end

.decrypt(password, iv, secretdata) ⇒ Object



33
34
35
36
37
38
39
40
# File 'lib/onboardbase.rb', line 33

def decrypt(password, iv, secretdata)
  secretdata = Base64::decode64(secretdata)
  decipher = OpenSSL::Cipher::Cipher.new('aes-256-cbc')
  decipher.decrypt
  decipher.key = password
  decipher.iv = iv if iv != nil
  decipher.update(secretdata) + decipher.final
end

.encrypt(password, iv, cleardata) ⇒ Object



22
23
24
25
26
27
28
29
30
31
# File 'lib/onboardbase.rb', line 22

def encrypt(password, iv, cleardata)
  cipher = OpenSSL::Cipher.new('AES-256-CBC')
  cipher.encrypt  # set cipher to be encryption mode
  cipher.key = password
  cipher.iv  = iv
  encrypted = ''
  encrypted << cipher.update(cleardata)
  encrypted << cipher.final
  b64enc(encrypted)
end

.getEnvironmentFallbackDirObject



80
81
82
83
# File 'lib/onboardbase.rb', line 80

def getEnvironmentFallbackDir
  environment = self.configuration['setup']['environment']
  "#{self.getProjectFallbackDir}_#{environment}"
end

.getFallbackDirObject



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

def getFallbackDir
  "#{self.getOnboardbaseDir}/fallback"
end

.getOnboardbaseDirObject



66
67
68
# File 'lib/onboardbase.rb', line 66

def getOnboardbaseDir
  "#{Dir.home}/.onboardbase"
end

.getProject?(data) ⇒ Boolean

Returns:

  • (Boolean)


144
145
146
147
148
# File 'lib/onboardbase.rb', line 144

def getProject?(data)
  project = data["list"][0]
  return project if project != nil
  false
end

.getProjectFallbackDirObject



75
76
77
78
# File 'lib/onboardbase.rb', line 75

def getProjectFallbackDir
  project = self.configuration['setup']['project']
  "#{self.getFallbackDir}/#{project}"
end

.getSecrets?(project) ⇒ Boolean

Returns:

  • (Boolean)


151
152
153
154
155
# File 'lib/onboardbase.rb', line 151

def getSecrets?(project)
  env = project["publicEnvironments"]["list"][0]
  return JSON.parse(env["key"]) if env != nil
  false
end

.getWorkingDirectoryObject



62
63
64
# File 'lib/onboardbase.rb', line 62

def getWorkingDirectory
    Dir.pwd
end

.hashSecrets?(secretsArr) ⇒ Boolean

Returns:

  • (Boolean)


220
221
222
223
224
225
226
# File 'lib/onboardbase.rb', line 220

def hashSecrets?(secretsArr)
  secretsHash = Hash.new
  secretsArr.each do |secret|
    secretsHash["#{secret["key"]}"] = "#{secret["value"]}"
  end
  secretsHash
end

.initializeObject



54
55
56
# File 'lib/onboardbase.rb', line 54

def initialize
  super
end

.key_pathObject



259
260
261
# File 'lib/onboardbase.rb', line 259

def key_path
  ENV["RAILS_ENV"] ? "config/credentials/#{ENV["RAILS_ENV"]}.key" : "config/master.key"
end

.loadAsCredentialsObject



263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
# File 'lib/onboardbase.rb', line 263

def loadAsCredentials
  # Traditionally fetch secrets into ENV
  secrets = self.overrideWithLocal(self.loadSecrets)
  # Load rails encryption module
  require "active_support/encrypted_configuration"
  credentials = ActiveSupport::EncryptedConfiguration.new(
      config_path: self.content_path,
      key_path: self.key_path,
      env_key: "RAILS_MASTER_KEY",
      raise_if_missing_key: true
    )

  # secrets hash
  encsecrets = YAML.load(credentials.read)
  secrets.keys.sort.each do |key|
    encsecrets[key.to_s] = secrets[key.to_s].to_s
  end
  # Append all environment variable
  credentials.write(encsecrets.to_yaml)
end

.loadConfigObject



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/onboardbase.rb', line 90

def loadConfig
  configPath = self.getWorkingDirectory + '/onboardbase.yaml'
  unless self.config_exists?(configPath)
    puts "Please create onboardbase.yaml in the root of the project at: " + configPath
    exit 1
  end
  config = YAML.load_file(configPath)
  if (config['api_key'] == nil)
    puts "Your onboardbase.yaml file does not have an api_key"
    exit 1
  end

  if (config['passcode'] == nil)
    puts "Your onboardbase.yaml file does not have a passcode"
    exit 1
  end
  @configuration = config
end

.loadSecretsObject



284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
# File 'lib/onboardbase.rb', line 284

def loadSecrets
  self.loadConfig
  response = self.parseResponse?(self.makeRequest)
  if response[:error]
    puts "Unable to fetch secrets with the specified api key, reading from fallback file"
    secrets = self.readFallback
  else
    project = self.getProject?(response)
    projectSecrets = self.getSecrets?(project)
    parsedSecrets = self.parseSecrets(projectSecrets)
    secrets = self.hashSecrets?(parsedSecrets)
  end
  finalEnvs = self.setEnv(secrets)
  self.storeToFallback?(finalEnvs)
  secrets
end

.makeRequestObject



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/onboardbase.rb', line 109

def makeRequest
  url = self.apiURL
  headers = {
    KEY: self.configuration['api_key'],
  }
    body = {
      query: %{
        query {
          generalPublicProjects(filterOptions: { title: "#{self .configuration['setup']['project']}", disableCustomSelect: true }) {
            list {
              id
              title
              publicEnvironments(filterOptions: { title: "#{self .configuration['setup']['environment']}" }) {
                list {
                  id
                  key
                  title
                }
              }
            }
          }
        }
      }
    }
  response = HTTParty.post(url, headers: headers, body: body)
  JSON.parse(response.body)
end

.overrideWithLocal(secrets) ⇒ Object



204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/onboardbase.rb', line 204

def overrideWithLocal(secrets)
  # Overried local secrets
  configSecrets = self.configuration["secrets"]
  unless configSecrets
    configSecrets = { "local" => {} }
  end
  localSecrets = configSecrets["local"]
  unless localSecrets
    localSecrets = {}
  end
  localSecrets.keys.sort.each do |key|
    secrets[key.to_s] = "#{configSecrets["local"][key.to_s]}"
  end
  secrets
end

.parseResponse?(response) ⇒ Boolean

Returns:

  • (Boolean)


137
138
139
140
141
142
# File 'lib/onboardbase.rb', line 137

def parseResponse?(response)
  error = response["errors"]
  data = response["data"]
  return data["generalPublicProjects"] if error == nil
  {:error=> true, :message => error[0]["message"] }
end

.parseSecrets(secrets) ⇒ Object



178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/onboardbase.rb', line 178

def parseSecrets(secrets)
    secrets.each_with_index do |secret, i|
      secret = Base64.decode64(secret)
      unless secret[0..7] == 'Salted__'
        puts "Invalid encrypted data"
        exit(1)
      end
      salt = secret[8..15]
      key_iv = bytes_to_key(self.configuration["passcode"], salt, 48)
      key = key_iv[0..31]
      iv = key_iv[32..key_iv.length-1]
      parsedSecret = aes256_cbc_decrypt(key, secret[16..secret.length-1], iv)
      secrets[i] = JSON.parse(parsedSecret)
    end
    secrets
end

.readFallbackObject



240
241
242
243
244
245
246
247
248
249
250
251
252
253
# File 'lib/onboardbase.rb', line 240

def readFallback
  Dir.mkdir(self.getOnboardbaseDir) unless File.exists?(self.getOnboardbaseDir)
  Dir.mkdir(self.getFallbackDir) unless File.exists?(self.getFallbackDir)

  if !File.exist?(self.getEnvironmentFallbackDir) || File.read(self.getEnvironmentFallbackDir).length <= 0
    puts "No valid fallback for #{self .configuration['setup']['project']} project using #{self .configuration['setup']['environment']} environment"
    return JSON.parse("{}") # Graceful failure, ensure the application continues to run without secrets.
  end
  data  = File.read(self.getEnvironmentFallbackDir)
  password = MachineID.ID?
  cipher = Gibberish::AES::CBC.new(password)
  decoded_data = cipher.decrypt(data)
  JSON.parse(decoded_data)
end

.setEnv(secretsHash) ⇒ Object



195
196
197
198
199
200
201
202
# File 'lib/onboardbase.rb', line 195

def setEnv(secretsHash)
  secretsHash.keys.sort.each do |key|
    ENV[key.to_s] = "#{secretsHash[key.to_s]}"
  end

  self.overrideWithLocal(ENV)
  ENV.to_hash
end

.storeToFallback?(secrets) ⇒ Boolean

Returns:

  • (Boolean)


228
229
230
231
232
233
234
235
236
237
# File 'lib/onboardbase.rb', line 228

def storeToFallback?(secrets)
  Dir.mkdir(self.getOnboardbaseDir) unless File.exists?(self.getOnboardbaseDir)
  Dir.mkdir(self.getFallbackDir) unless File.exists?(self.getFallbackDir)

  password = MachineID.ID?
  cipher = Gibberish::AES::CBC.new(password)
  cipher_text = cipher.encrypt(JSON.generate(secrets))
  data = cipher_text
  File.write(self.getEnvironmentFallbackDir, data, nil , mode: 'w')
end