canvas-lms/lib/canvas/vault.rb

84 lines
2.8 KiB
Ruby

#
# Copyright (C) 2020 - present Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
require 'vault'
module Canvas::Vault
CACHE_KEY_PREFIX = 'vault/'.freeze
class MissingVaultProfile < StandardError; end
class << self
def read(path)
# we're going to override this anyway, just want it to use the fetch path.
default_expiry = 30.minutes
default_race_condition_ttl = Setting.get("vault_cache_race_condition_ttl", 60).to_i.seconds
cache_key = CACHE_KEY_PREFIX + path
fetched_lease_value = nil
cached_data = LocalCache.fetch(cache_key, expires_in: default_expiry, race_condition_ttl: default_race_condition_ttl) do
vault_resp = api_client.logical.read(path)
raise(MissingVaultProfile, "nil credentials found for #{path}") if vault_resp.nil?
fetched_lease_value = vault_resp.lease_duration || vault_resp.data[:ttl] || 10.minutes
vault_resp.data
end
unless fetched_lease_value.nil?
# we actually talked to vault and got a new record, let's update the expiration information
# so actually be sensitive to the data in the lease
cache_ttl = fetched_lease_value / 2
LocalCache.write(cache_key, cached_data, expires_in: cache_ttl)
end
return cached_data
rescue => exception
Canvas::Errors.capture_exception(:vault, exception)
stale_value = LocalCache.fetch_without_expiration(CACHE_KEY_PREFIX + path)
return stale_value if stale_value.present?
# if we can't serve any stale value, we're better erroring than handing back nil
raise
end
def api_client
return Canvas::Vault::FileClient.get_client if addr == "file"
Vault::Client.new(address: addr, token: token)
end
def kv_mount
config[:kv_mount]
end
private
def addr
if config[:addr_path]
File.read(config[:addr_path]).chomp
elsif config[:addr]
config[:addr]
end
end
def token
# We deliberately want to read this token every time, as it may be refreshed in the background
if config[:token_path]
File.read(config[:token_path]).chomp
elsif config[:token]
config[:token]
end
end
def config
ConfigFile.load('vault').try(:symbolize_keys) || {}
end
end
end