Skip to content

Commit

Permalink
Merge branch 'main' of github.com:G-Research/astral
Browse files Browse the repository at this point in the history
  • Loading branch information
suprjinx committed Nov 5, 2024
2 parents aa81e34 + 093c4f7 commit 17cd743
Show file tree
Hide file tree
Showing 13 changed files with 118 additions and 40 deletions.
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,30 @@ UPPER_CASE). Environment vars will override any values in the config
file. Per-environment settings in the config file(development, test,
production) will override the shared values for that type.

## Database encryption
The local database can be encrypted, if needed, but requires a bit of setup
and careful retention of a master key. Note that there are performance impacts.

1. First, create encryption keys for the database:
```
rails db:encryption:init
```
Copy the output to your clipboard.

2. Next, create a `credentials.yml.enc` file:
```
EDITOR=vi rails credentials:edit
```
Paste the db encryption key data into this file, save, and exit.

NB, the credentials file is decoded by a key placed in
`config/master.key`. Be sure to save this file (it is .gitignored)!

3. Finally, set the following Astral configuration to 'true':
```
db_encryption: true
```

## mTLS connections
Astral can be run as an SSL service and can communicate with Vault via SSL.
Just set the following values in `config/astral.yml` (or environment) to
Expand Down
2 changes: 1 addition & 1 deletion app/interactors/delete_secret.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
class DeleteSecret < ApplicationInteractor
def call
Services::KeyValue.delete(context.request.path)
Services::KeyValue.delete(context.identity, context.request.path)
ensure
audit_log
end
Expand Down
2 changes: 1 addition & 1 deletion app/interactors/read_secret.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
class ReadSecret < ApplicationInteractor
def call
if secret = Services::KeyValue.read(context.request.path)
if secret = Services::KeyValue.read(context.identity, context.request.path)
context.secret = secret
else
context.fail!(message: "Failed to read secret: #{context.request.path}")
Expand Down
2 changes: 1 addition & 1 deletion app/interactors/write_secret.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
class WriteSecret < ApplicationInteractor
def call
if secret = Services::KeyValue.write(context.request.path, context.request.data)
if secret = Services::KeyValue.write(context.identity, context.request.path, context.request.data)
context.secret = secret
else
context.fail!(message: "Failed to store secret")
Expand Down
26 changes: 5 additions & 21 deletions app/lib/clients/vault/certificate.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
module Clients
class Vault
module Certificate
extend Policy

GENERIC_CERT_POLICY_NAME = "astral-generic-cert-policy"

def issue_cert(identity, cert_issue_request)
opts = cert_issue_request.attributes
# Generate the TLS certificate using the intermediate CA
tls_cert = client.logical.write(cert_path, opts)
config_user(identity)
assign_policy(identity, GENERIC_CERT_POLICY_NAME)
OpenStruct.new tls_cert.data
end

Expand All @@ -19,17 +23,6 @@ def configure_pki
create_generic_cert_policy
end

def config_user(identity)
sub = identity.sub
email = identity.email
policies, metadata = get_entity_data(sub)
policies.append(Certificate::GENERIC_CERT_POLICY_NAME).to_set.to_a
put_entity(sub, policies, metadata)
put_entity_alias(sub, email, "oidc")
end

GENERIC_CERT_POLICY_NAME = "astral-generic-cert-policy"

private

def intermediate_ca_mount
Expand Down Expand Up @@ -131,15 +124,6 @@ def configure_ca
enable_templating: true)
end

def get_entity_data(sub)
entity = read_entity(sub)
if entity.nil?
[ [], nil ]
else
[ entity.data[:policies], entity.data[:metadata] ]
end
end

def create_generic_cert_policy
client.sys.put_policy(GENERIC_CERT_POLICY_NAME, generic_cert_policy)
end
Expand Down
9 changes: 9 additions & 0 deletions app/lib/clients/vault/entity.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ def read_entity(name)
def delete_entity(name)
client.logical.delete("identity/entity/name/#{name}")
end

def get_entity_data(sub)
entity = read_entity(sub)
if entity.nil?
[ [], nil ]
else
[ entity.data[:policies], entity.data[:metadata] ]
end
end
end
end
end
27 changes: 24 additions & 3 deletions app/lib/clients/vault/key_value.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
module Clients
class Vault
module KeyValue
def kv_read(path)
extend Policy

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

def kv_write(path, data)
def kv_write(identity, path, data)
create_kv_policy(path)
assign_policy(identity, policy_path(path))
client.logical.write("#{kv_mount}/data/#{path}", data: data)
end

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

Expand All @@ -28,6 +32,23 @@ def kv_mount
def kv_engine_type
"kv-v2"
end


def create_kv_policy(path)
client.sys.put_policy(policy_path(path), kv_policy(path))
end

def policy_path(path)
"kv_policy/#{path}"
end

def kv_policy(path)
policy = <<-EOH
path "#{path}" {
capabilities = ["create", "read", "update", "delete"]
}
EOH
end
end
end
end
11 changes: 11 additions & 0 deletions app/lib/clients/vault/policy.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
module Clients
class Vault
module Policy
extend Entity

def rotate_token
create_astral_policy
token = create_astral_token
Clients::Vault.token = token
end

def assign_policy(identity, policy_name)
sub = identity.sub
email = identity.email
policies, metadata = get_entity_data(sub)
policies.append(policy_name).uniq!
put_entity(sub, policies, metadata)
put_entity_alias(sub, email, "oidc")
end

private

def create_astral_policy
Expand Down
12 changes: 6 additions & 6 deletions app/lib/services/key_value.rb
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
module Services
class KeyValue
class << self
def read(path)
impl.kv_read(path)
def read(identity, path)
impl.kv_read(identity, path)
end

def write(path, data)
impl.kv_write(path, data)
def write(identity, path, data)
impl.kv_write(identity, path, data)
end

def delete(path)
impl.kv_delete(path)
def delete(identity, path)
impl.kv_delete(identity, path)
end

private
Expand Down
5 changes: 5 additions & 0 deletions app/models/domain.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
class Domain < ApplicationRecord
validates :fqdn, presence: true

if Config[:db_encryption]
encrypts :fqdn, :users, :groups
end


def groups_array
(groups || "").split(",").sort.uniq
end
Expand Down
3 changes: 3 additions & 0 deletions config/astral.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# Astral configuration
# Note that values can be supplied here or as environment vars (UPPER_CASE).
shared:
# Set to true and follow setup guide for encrypted sql database fields
db_encryption: false

vault_token:
vault_addr:
# if VAULT_ADDR is https with self-signed cert, need to provide
Expand Down
1 change: 0 additions & 1 deletion config/credentials.yml.enc

This file was deleted.

34 changes: 28 additions & 6 deletions test/lib/clients/vault_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,10 @@ class VaultTest < ActiveSupport::TestCase
# now has a new token
assert_not_equal vault_token, @client.token
# ensure we can write with the new token
assert_instance_of Vault::Secret, @client.kv_write("testing/secret", { password: "sicr3t" })
assert_instance_of Vault::Secret, @client.kv_write(@identity, "testing/secret", { password: "sicr3t" })
end

test "#entity" do
test "entity methods" do
entity = @client.read_entity(@entity_name)
assert_nil entity

Expand All @@ -84,7 +84,29 @@ class VaultTest < ActiveSupport::TestCase
assert_nil entity
end

test "#entity_alias" do
test "kv methods" do
# check kv_write
path = "test/path/#{SecureRandom.hex}"
secret = @client.kv_write(@identity, path, { data: "data" })
assert_kind_of Vault::Secret, secret

# check kv_read
read_secret = @client.kv_read(@identity, path)
assert_kind_of Vault::Secret, read_secret

# check policy is created
entity = @client.read_entity(@identity.sub)
assert_equal "kv_policy/#{path}", entity.data[:policies][0]

# check kv_delete
del_secret = @client.kv_delete(@identity, path)
assert del_secret
read_secret = @client.kv_read(@identity, path)
assert_nil read_secret
end


test "entity_alias methods" do
# confirm no entity yet
err = assert_raises RuntimeError do
@client.read_entity_alias(@entity_name, @alias_name)
Expand Down Expand Up @@ -112,11 +134,11 @@ class VaultTest < ActiveSupport::TestCase
assert_match /no such alias/, err.message
end

test "#config_user creates valid entity" do
@client.config_user(@identity)
test ".assign_policy creates valid entity" do
@client.assign_policy(@identity, "test_path")
entity = @client.read_entity(@identity.sub)
assert entity.data[:policies].any? { |p|
p == @client::Certificate::GENERIC_CERT_POLICY_NAME }
p == "test_path" }
assert entity.data[:aliases].any? { |a|
a[:mount_type] == "oidc" && a[:name] == @identity.sub }
end
Expand Down

0 comments on commit 17cd743

Please sign in to comment.