[deploy] Feature: Setup Vault and AppSecrets Wrapper Class (#8891)
This commit is contained in:
parent
09bb730f9d
commit
0767c34379
7 changed files with 133 additions and 0 deletions
6
Envfile
6
Envfile
|
|
@ -164,6 +164,12 @@ variable :TWITCH_WEBHOOK_SECRET, :String, default: "Optional"
|
|||
# (https://api.stackexchange.com/docs)
|
||||
variable :STACK_EXCHANGE_APP_KEY, :String, default: ""
|
||||
|
||||
# Vault for storing secrets
|
||||
# (https://learn.hashicorp.com/vault)
|
||||
variable :VAULT_ADDR, :String, default: "http://127.0.0.1:8200"
|
||||
variable :VAULT_TOKEN, :String, default: ""
|
||||
variable :VAULT_SSL_VERIFY, :Boolean, default: true
|
||||
|
||||
group :production do
|
||||
variable :SECRET_KEY_BASE, :String
|
||||
|
||||
|
|
|
|||
1
Gemfile
1
Gemfile
|
|
@ -107,6 +107,7 @@ gem "twitter", "~> 7.0" # A Ruby interface to the Twitter API
|
|||
gem "uglifier", "~> 4.2" # Uglifier minifies JavaScript files
|
||||
gem "ulid", "~> 1.2" # Universally Unique Lexicographically Sortable Identifier implementation for Ruby
|
||||
gem "validate_url", "~> 1.0" # Library for validating urls in Rails
|
||||
gem "vault", "~> 0.14" # Used to store secrets
|
||||
gem "webpacker", "~> 5.1.1" # Use webpack to manage app-like JavaScript modules in Rails
|
||||
|
||||
group :development do
|
||||
|
|
|
|||
|
|
@ -837,6 +837,8 @@ GEM
|
|||
validate_url (1.0.11)
|
||||
activemodel (>= 3.0.0)
|
||||
public_suffix
|
||||
vault (0.14.0)
|
||||
aws-sigv4
|
||||
vcr (6.0.0)
|
||||
virtus (1.0.5)
|
||||
axiom-types (~> 0.1)
|
||||
|
|
@ -1023,6 +1025,7 @@ DEPENDENCIES
|
|||
uglifier (~> 4.2)
|
||||
ulid (~> 1.2)
|
||||
validate_url (~> 1.0)
|
||||
vault (~> 0.14)
|
||||
vcr (~> 6.0)
|
||||
web-console (~> 4.0)
|
||||
webdrivers (~> 4.4)
|
||||
|
|
|
|||
19
app/lib/app_secrets.rb
Normal file
19
app/lib/app_secrets.rb
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
class AppSecrets
|
||||
def self.[](key)
|
||||
result = Vault.kv(namespace).read(key)&.data&.fetch(:value) if ApplicationConfig["VAULT_TOKEN"].present?
|
||||
result ||= ApplicationConfig[key]
|
||||
|
||||
result
|
||||
rescue Vault::VaultError
|
||||
ApplicationConfig[key]
|
||||
end
|
||||
|
||||
def self.[]=(key, value)
|
||||
Vault.kv(namespace).write(key, value: value)
|
||||
end
|
||||
|
||||
def self.namespace
|
||||
ENV["VAULT_SECRET_NAMESPACE"]
|
||||
end
|
||||
private_class_method :namespace
|
||||
end
|
||||
40
config/initializers/vault.rb
Normal file
40
config/initializers/vault.rb
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
Vault.configure do |config|
|
||||
# The address of the Vault server, also read as
|
||||
config.address = ApplicationConfig["VAULT_ADDR"]
|
||||
|
||||
# The policy token to authenticate with Vault
|
||||
# Each app will get its own policy https://learn.hashicorp.com/vault/getting-started/policies#overview
|
||||
# Each policy comes with its own token to give the app access to ONLY its secrets
|
||||
config.token = ApplicationConfig["VAULT_TOKEN"]
|
||||
|
||||
# Mimic Paths for communities
|
||||
|
||||
# Optional - if using the Namespace enterprise feature
|
||||
# config.namespace = ENV["VAULT_NAMESPACE"]
|
||||
|
||||
# Proxy connection information, also read as ENV["VAULT_PROXY_(thing)"]
|
||||
# config.proxy_address = "..."
|
||||
# config.proxy_port = "..."
|
||||
# config.proxy_username = "..."
|
||||
# config.proxy_password = "..."
|
||||
|
||||
# Custom SSL PEM, also read as ENV["VAULT_SSL_CERT"]
|
||||
# config.ssl_pem_file = "/path/on/disk.pem"
|
||||
|
||||
# As an alternative to a pem file, you can provide the raw PEM string, also read in the following order of preference:
|
||||
# ENV["VAULT_SSL_PEM_CONTENTS_BASE64"] then ENV["VAULT_SSL_PEM_CONTENTS"]
|
||||
# config.ssl_pem_contents = "-----BEGIN ENCRYPTED..."
|
||||
|
||||
# Use SSL verification, also read as ENV["VAULT_SSL_VERIFY"]
|
||||
config.ssl_verify = ApplicationConfig["VAULT_SSL_VERIFY"]
|
||||
|
||||
# Timeout the connection after a certain amount of time (seconds), also read
|
||||
# as ENV["VAULT_TIMEOUT"]
|
||||
config.timeout = 30
|
||||
|
||||
# It is also possible to have finer-grained controls over the timeouts, these
|
||||
# may also be read as environment variables
|
||||
config.ssl_timeout = 5
|
||||
config.open_timeout = 5
|
||||
config.read_timeout = 30
|
||||
end
|
||||
64
spec/lib/app_secrets_spec.rb
Normal file
64
spec/lib/app_secrets_spec.rb
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
require "rails_helper"
|
||||
|
||||
RSpec.describe AppSecrets, type: :lib do
|
||||
let(:namespace) { "secret-namespace" }
|
||||
let(:key) { "SECRET_KEY" }
|
||||
let(:secret_stub) { instance_double("Vault::Kv", data: { value: 1 }) }
|
||||
let(:vault_stub) { instance_double("Vault::Kv", read: secret_stub) }
|
||||
|
||||
before do
|
||||
allow(described_class).to receive(:namespace).and_return(namespace)
|
||||
allow(Vault).to receive(:kv) { vault_stub }
|
||||
allow(ApplicationConfig).to receive(:[])
|
||||
end
|
||||
|
||||
describe "[]" do
|
||||
context "with VAULT_TOKEN present" do
|
||||
before do
|
||||
allow(ApplicationConfig).to receive(:[]).with("VAULT_TOKEN").and_return("present")
|
||||
end
|
||||
|
||||
it "fetches keys from Vault" do
|
||||
described_class[key]
|
||||
expect(Vault).to have_received(:kv).with(namespace)
|
||||
expect(vault_stub).to have_received(:read).with(key)
|
||||
end
|
||||
|
||||
it "fetches keys from ApplicationConfig if not in Vault" do
|
||||
allow(Vault).to receive(:kv) { instance_double("Vault::Kv", read: nil) }
|
||||
|
||||
described_class[key]
|
||||
expect(ApplicationConfig).to have_received(:[]).with(key)
|
||||
end
|
||||
|
||||
it "fetches keys from ApplicationConfig if Vault raises an error" do
|
||||
allow(Vault).to receive(:kv).and_raise(Vault::VaultError)
|
||||
|
||||
described_class[key]
|
||||
expect(ApplicationConfig).to have_received(:[]).with(key)
|
||||
end
|
||||
end
|
||||
|
||||
context "without VAULT_TOKEN present" do
|
||||
before { allow(ApplicationConfig).to receive(:[]).with("VAULT_TOKEN").and_return("") }
|
||||
|
||||
it "fetches keys from ApplicationConfig" do
|
||||
allow(Vault).to receive(:kv) { instance_double("Vault::Kv", read: nil) }
|
||||
|
||||
described_class[key]
|
||||
expect(ApplicationConfig).to have_received(:[]).with(key)
|
||||
expect(Vault).not_to have_received(:kv).with(namespace)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "[]=" do
|
||||
it "sets keys in Vault" do
|
||||
write_stub = instance_double("Vault::Kv", write: nil)
|
||||
allow(Vault).to receive(:kv) { write_stub }
|
||||
|
||||
described_class[key] = "secret-value"
|
||||
expect(write_stub).to have_received(:write).with(key, value: "secret-value")
|
||||
end
|
||||
end
|
||||
end
|
||||
BIN
vendor/cache/vault-0.14.0.gem
vendored
Normal file
BIN
vendor/cache/vault-0.14.0.gem
vendored
Normal file
Binary file not shown.
Loading…
Add table
Reference in a new issue