Skip to content

Commit

Permalink
Bring intermediate CA setup into the app (#35)
Browse files Browse the repository at this point in the history
* Application now configures vault engines for certs and KV at startup
  • Loading branch information
suprjinx authored Sep 24, 2024
1 parent dec0172 commit 6958511
Show file tree
Hide file tree
Showing 11 changed files with 234 additions and 110 deletions.
2 changes: 1 addition & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"forwardPorts": [3000, 5432, 8200],

// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "bundle install && rake db:setup && rake vault:setup",
"postCreateCommand": "bundle install && rake vault:setup && rake db:setup",

// Configure tool-specific properties.
// "customizations": {},
Expand Down
23 changes: 4 additions & 19 deletions .devcontainer/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,13 @@ services:
- 3000:3000
# Overrides default command so things don't shut down after the process ends.
command: sleep infinity
# Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function.
networks:
astral:
ipv4_address: "10.1.10.200"
environment:
VAULT_ADDR: http://10.1.10.100:8200
VAULT_ADDR: http://vault:8200
VAULT_TOKEN: root_token
VAULT_ROOT_CA_MOUNT: pki
VAULT_ROOT_CA_REF: root-ca
JWT_SIGNING_KEY: jwt_secret
APP_REGISTRY_ADDR: http://10.1.10.150:8800
APP_REGISTRY_ADDR: http://app_registry:8800
APP_REGISTRY_TOKEN: app_reg_token

vault:
Expand All @@ -30,9 +28,6 @@ services:
environment:
VAULT_DEV_ROOT_TOKEN_ID: root_token
VAULT_DEV_LISTEN_ADDRESS: 0.0.0.0:8200
networks:
astral:
ipv4_address: "10.1.10.100"

app_registry:
image: node:latest
Expand All @@ -41,16 +36,6 @@ services:
- 8800:8800
volumes:
- .:/data
networks:
astral:
ipv4_address: "10.1.10.150"
command: >
sh -c "npm install -g [email protected] &&
json-server /data/app_reg_db.json --routes /data/app_reg_routes.json --port 8800 --host 0.0.0.0"
networks:
astral:
ipam:
driver: default
config:
- subnet: "10.1.10.0/24"
2 changes: 1 addition & 1 deletion app/interactors/read_secret.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ def call
if secret = Services::KeyValue.read(context.request.path)
context.secret = secret
else
context.fail!(message: "Failed to read secret")
context.fail!(message: "Failed to read secret: #{context.request.path}")
end
ensure
audit_log
Expand Down
43 changes: 12 additions & 31 deletions app/lib/clients/vault.rb
Original file line number Diff line number Diff line change
@@ -1,48 +1,29 @@
module Clients
class Vault
class << self
def issue_cert(cert_issue_request)
opts = cert_issue_request.attributes
# Generate the TLS certificate using the intermediate CA
tls_cert = client.logical.write(Rails.configuration.astral[:vault_cert_path], opts)
OpenStruct.new tls_cert.data
end

def kv_read(path)
client.kv(kv_mount).read(path)
end

def kv_write(path, data)
enable_engine(kv_mount, "kv-v2")
client.logical.write("#{kv_mount}/data/#{path}", data: data)
end

def kv_delete(path)
client.logical.delete("#{kv_mount}/data/#{path}")
end

private

def client
::Vault::Client.new(
address: Rails.configuration.astral[:vault_addr],
token: Rails.configuration.astral[:vault_token]
address: vault_address,
token: vault_token
)
end

def enable_engine(mount, type)
# create the engine mount if not present already
unless client.sys.mounts.key?(mount.to_sym)
client.sys.mount(mount, type, "#{type} secrets engine")
end
rescue ::Vault::HTTPError => e
Rails.logger.error "Error enabling #{type} engine: #{e}"
def vault_address
Rails.configuration.astral[:vault_addr]
end

def vault_token
Rails.configuration.astral[:vault_token]
end

def kv_mount
Rails.configuration.astral[:vault_kv_mount]
def enable_engine(mount, type)
client.sys.mount(mount, type, "#{type} secrets engine")
end
end
end

require_relative "vault/key_value"
require_relative "vault/certificate"
end
96 changes: 96 additions & 0 deletions app/lib/clients/vault/certificate.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
module Clients
class Vault
class << self
def issue_cert(cert_issue_request)
opts = cert_issue_request.attributes
# Generate the TLS certificate using the intermediate CA
tls_cert = client.logical.write(cert_path, opts)
OpenStruct.new tls_cert.data
end

def configure_pki
if enable_ca
sign_cert
configure_ca
end
end

private

def intermediate_ca_mount
"pki_astral"
end

def cert_path
"#{intermediate_ca_mount}/issue/astral"
end

def root_ca_ref
Rails.configuration.astral[:vault_root_ca_ref]
end

def root_ca_mount
Rails.configuration.astral[:vault_root_ca_mount]
end

def cert_engine_type
"pki"
end

def enable_ca
# if mount exists, assume configuration is done
if client.sys.mounts.key?(intermediate_ca_mount.to_sym)
return false
end

# create the mount
enable_engine(intermediate_ca_mount, cert_engine_type)
true
end

def sign_cert
# Generate intermediate CSR
intermediate_csr = client.logical.write("#{intermediate_ca_mount}/intermediate/generate/internal",
common_name: "astral.internal Intermediate Authority",
issuer_name: "astral-intermediate").data[:csr]

# Save the intermediate CSR
File.write("tmp/pki_intermediate.csr", intermediate_csr)

# Sign the intermediate certificate with the root CA
intermediate_cert = client.logical.write("#{root_ca_mount}/root/sign-intermediate",
issuer_ref: root_ca_ref,
csr: intermediate_csr,
format: "pem_bundle",
ttl: "43800h").data[:certificate]

# Save the signed intermediate certificate
File.write("tmp/intermediate.cert.pem", intermediate_cert)

# Set the signed intermediate certificate
client.logical.write("#{intermediate_ca_mount}/intermediate/set-signed", certificate: intermediate_cert)
end

def configure_ca
# Configure the intermediate CA
client.logical.write("#{intermediate_ca_mount}/config/cluster",
path: "#{vault_address}/v1/#{intermediate_ca_mount}",
aia_path: "#{vault_address}/v1/#{intermediate_ca_mount}")

# Configure the role for issuing certs
issuer_ref = client.logical.read("#{intermediate_ca_mount}/config/issuers").data[:default]
client.logical.write("#{intermediate_ca_mount}/roles/astral",
issuer_ref: issuer_ref,
allow_any_name: true,
max_ttl: "720h",
no_store: false)

client.logical.write("#{intermediate_ca_mount}/config/urls",
issuing_certificates: "{{cluster_aia_path}}/issuer/{{issuer_id}}/der",
crl_distribution_points: "{{cluster_aia_path}}/issuer/{{issuer_id}}/crl/der",
ocsp_servers: "{{cluster_path}}/ocsp",
enable_templating: true)
end
end
end
end
34 changes: 34 additions & 0 deletions app/lib/clients/vault/key_value.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
module Clients
class Vault
class << self
def kv_read(path)
client.kv(kv_mount).read(path)
end

def kv_write(path, data)
configure_kv
client.logical.write("#{kv_mount}/data/#{path}", data: data)
end

def kv_delete(path)
client.logical.delete("#{kv_mount}/data/#{path}")
end

def configure_kv
unless client.sys.mounts.key?(kv_mount.to_sym)
enable_engine(kv_mount, kv_engine_type)
end
end

private

def kv_mount
"kv_astral"
end

def kv_engine_type
"kv-v2"
end
end
end
end
5 changes: 5 additions & 0 deletions config/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,10 @@ class Application < Rails::Application

# Application configs from config/astral.yml
config.astral = config_for :astral

config.after_initialize do
Clients::Vault.configure_kv
Clients::Vault.configure_pki
end
end
end
8 changes: 6 additions & 2 deletions config/astral.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
shared:
vault_addr: <%= ENV["VAULT_ADDR"] %>
vault_token: <%= ENV["VAULT_TOKEN"] %>
vault_cert_path: "pki_int/issue/learn"
vault_kv_mount: <%= ENV["VAULT_KV_MOUNT"] || "astralkv" %>
# Pre-existing root CA in Vault for signing intermediate created by astral
vault_root_ca_ref: <%= ENV["VAULT_ROOT_CA_REF"] || "root-ca" %>
vault_root_ca_mount: <%= ENV["VAULT_ROOT_CA_MOUNT"] || "pki" %>

jwt_signing_key: <%= ENV["JWT_SIGNING_KEY"] %>
cert_ttl: <%= ENV["CERT_TTL"] %>

app_registry_addr: <%= ENV["APP_REGISTRY_ADDR"] %>
app_registry_token: <%= ENV["APP_REGISTRY_TOKEN"] %>
app_registry_ca_file: <%= ENV["APP_REGISTRY_CA_FILE"] %>
app_registry_client_cert: <%= ENV["APP_REGISTRY_CLIENT_CERT"] %>
app_registry_client_key: <%= ENV["APP_REGISTRY_CLIENT_KEY"] %>

audit_log_file: <%= ENV["AUDIT_LOG_FILE"] || "#{Rails.root.join('log')}/astral-audit.log" %>

test:
Expand Down
Loading

0 comments on commit 6958511

Please sign in to comment.