Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Root cert setup #40

Merged
merged 6 commits into from
Oct 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"forwardPorts": [3000, 5432, 8200],

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

// Configure tool-specific properties.
// "customizations": {},
Expand Down
1 change: 1 addition & 0 deletions .devcontainer/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ services:
environment:
VAULT_ADDR: http://vault:8200
VAULT_TOKEN: root_token
VAULT_CREATE_ROOT: true
VAULT_ROOT_CA_MOUNT: pki
VAULT_ROOT_CA_REF: root-ca
JWT_SIGNING_KEY: jwt_secret
Expand Down
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ gem "bootsnap", require: false

# High-level app logic
gem "interactor", "~> 3.0"
gem "ostruct"

# Use the vault-ruby gem to interact with HashiCorp Vault
gem "vault"
Expand Down
2 changes: 2 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ GEM
racc (~> 1.4)
nokogiri (1.16.7-x86_64-linux)
racc (~> 1.4)
ostruct (0.6.0)
parallel (1.26.2)
parser (3.3.4.2)
ast (~> 2.4.1)
Expand Down Expand Up @@ -290,6 +291,7 @@ DEPENDENCIES
interactor (~> 3.0)
jbuilder
jwt
ostruct
puma (>= 5.0)
rails (~> 7.2.1)
rubocop-rails-omakase
Expand Down
62 changes: 44 additions & 18 deletions app/lib/clients/vault/certificate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ def issue_cert(cert_issue_request)
end

def configure_pki
if enable_ca
sign_cert
configure_ca
end
# if intermediate mount exists, assume configuration is done
return if client.sys.mounts.key?(intermediate_ca_mount.to_sym)
configure_root_ca if create_root?
enable_ca
sign_cert
configure_ca
end

private
Expand All @@ -25,6 +27,11 @@ def cert_path
"#{intermediate_ca_mount}/issue/astral"
end

def create_root?
create_root_config = Rails.configuration.astral[:vault_create_root]
!!ActiveModel::Type::Boolean.new.cast(create_root_config)
end

def root_ca_ref
Rails.configuration.astral[:vault_root_ca_ref]
end
Expand All @@ -38,36 +45,55 @@ def cert_engine_type
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
# create the intermediate mount
enable_engine(intermediate_ca_mount, cert_engine_type)
true
end

def configure_root_ca
return if client.sys.mounts.key?(root_ca_mount.to_sym)

# enable engine
enable_engine(root_ca_mount, cert_engine_type)

# generate root certificate
root_cert = client.logical.write("#{root_ca_mount}/root/generate/internal",
common_name: "astral.internal",
issuer_name: root_ca_ref,
ttl: "87600h").data[:certificate]
# save the root certificate
File.write("tmp/#{root_ca_mount}.crt", root_cert)

client.logical.write("#{root_ca_mount}/config/cluster",
path: "#{vault_address}/v1/#{root_ca_mount}",
aia_path: "#{vault_address}/v1/#{root_ca_mount}")

client.logical.write("#{root_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

def sign_cert
# Generate intermediate CSR
# 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)
# save the intermediate CSR
File.write("tmp/#{intermediate_ca_mount}.csr", intermediate_csr)

# Sign the intermediate certificate with the root CA
# 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)
# save the signed intermediate certificate
File.write("tmp/#{intermediate_ca_mount}.crt", intermediate_cert)

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

Expand Down
6 changes: 4 additions & 2 deletions config/astral.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
shared:
vault_addr: <%= ENV["VAULT_ADDR"] %>
vault_token: <%= ENV["VAULT_TOKEN"] %>
# Pre-existing root CA in Vault for signing intermediate created by astral

# Pre-existing root CA, or create new if requested
vault_create_root: <%= ENV["VAULT_CREATE_ROOT"] || "true" %>
vault_root_ca_ref: <%= ENV["VAULT_ROOT_CA_REF"] || "root-ca" %>
vault_root_ca_mount: <%= ENV["VAULT_ROOT_CA_MOUNT"] || "pki" %>
vault_root_ca_mount: <%= ENV["VAULT_ROOT_CA_MOUNT"] || "pki_root" %>

jwt_signing_key: <%= ENV["JWT_SIGNING_KEY"] %>
cert_ttl: <%= ENV["CERT_TTL"] %>
Expand Down
69 changes: 0 additions & 69 deletions lib/tasks/vault.rake

This file was deleted.

13 changes: 8 additions & 5 deletions test/integration/secrets_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,28 +20,31 @@ class SecretsTest < ActionDispatch::IntegrationTest
end

test "#show" do
create_secret
path = create_secret
# view the secret
get secret_path("top/secret/key"), headers: { "Authorization" => "Bearer #{jwt_authorized}" }
get secret_path(path), headers: { "Authorization" => "Bearer #{jwt_authorized}" }
assert_response :success
%w[ data metadata lease_id ].each do |key|
assert_includes response.parsed_body["secret"].keys, key
end
end

test "#delete" do
create_secret
path = create_secret
# delete the secret
delete destroy_secret_path("top/secret/key"), headers: { "Authorization" => "Bearer #{jwt_authorized}" }
delete destroy_secret_path(path), headers: { "Authorization" => "Bearer #{jwt_authorized}" }
assert_response :success
end

private

def create_secret
# make a path
path = "top/secret/#{SecureRandom.hex}"
# create the secret
post secrets_path, headers: { "Authorization" => "Bearer #{jwt_authorized}" },
params: { secret: { path: "top/secret/key", data: { password: "sicr3t" } } }
params: { secret: { path: path, data: { password: "sicr3t" } } }
path
end

def remove_pki_engine
Expand Down
52 changes: 30 additions & 22 deletions test/lib/clients/vault_test.rb
Original file line number Diff line number Diff line change
@@ -1,43 +1,51 @@
require "test_helper"

class VaultTest < ActiveSupport::TestCase
attr_reader :random_mount
attr_reader :intermediate_ca_mount
attr_reader :root_ca_mount

setup do
@client = Clients::Vault
@random_mount = SecureRandom.hex(4)
@root_ca_mount = SecureRandom.hex(4)
@intermediate_ca_mount = SecureRandom.hex(4)
end

teardown do
vault_client.sys.unmount(random_mount)
vault_client.sys.unmount(root_ca_mount)
vault_client.sys.unmount(intermediate_ca_mount)
end

test "#configure_kv" do
@client.stub :kv_mount, random_mount do
@client.stub :kv_mount, intermediate_ca_mount do
assert @client.configure_kv
engines = vault_client.sys.mounts
assert_equal "kv", engines[random_mount.to_sym].type
assert_equal "kv", engines[intermediate_ca_mount.to_sym].type
end
end

test "#configure_pki" do
@client.stub :intermediate_ca_mount, random_mount do
assert @client.configure_pki
engines = vault_client.sys.mounts
assert_equal "pki", engines[random_mount.to_sym].type

read_cert = vault_client.logical.read("#{random_mount}/cert/ca").data[:certificate]
assert_match "BEGIN CERTIFICATE", read_cert

cluster_config = vault_client.logical.read("#{random_mount}/config/cluster").data
assert_equal "#{vault_addr}/v1/#{random_mount}", cluster_config[:path]
assert_equal "#{vault_addr}/v1/#{random_mount}", cluster_config[:aia_path]

role_config = vault_client.logical.read("#{random_mount}/roles/astral").data
assert_not_nil role_config[:issuer_ref]
assert_equal 720.hours, role_config[:max_ttl]
assert_equal true, role_config[:allow_any_name]
end
@client.stub :root_ca_mount, root_ca_mount do
@client.stub :intermediate_ca_mount, intermediate_ca_mount do
assert @client.configure_pki

[ root_ca_mount, intermediate_ca_mount ].each do |mount|
engines = vault_client.sys.mounts
assert_equal "pki", engines[mount.to_sym].type

read_cert = vault_client.logical.read("#{mount}/cert/ca").data[:certificate]
assert_match "BEGIN CERTIFICATE", read_cert

cluster_config = vault_client.logical.read("#{mount}/config/cluster").data
assert_equal "#{vault_addr}/v1/#{mount}", cluster_config[:path]
assert_equal "#{vault_addr}/v1/#{mount}", cluster_config[:aia_path]
end

role_config = vault_client.logical.read("#{intermediate_ca_mount}/roles/astral").data
assert_not_nil role_config[:issuer_ref]
assert_equal 720.hours, role_config[:max_ttl]
assert_equal true, role_config[:allow_any_name]
end
GeorgeJahad marked this conversation as resolved.
Show resolved Hide resolved
end
end

private
Expand Down