Skip to content

Commit

Permalink
First cut on group read-only policy
Browse files Browse the repository at this point in the history
  • Loading branch information
suprjinx committed Nov 9, 2024
1 parent b3ba014 commit f9ba077
Show file tree
Hide file tree
Showing 11 changed files with 77 additions and 24 deletions.
2 changes: 1 addition & 1 deletion app/controllers/secrets_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,6 @@ def destroy
private

def params_permitted
params.require(:secret).permit(:path, data: {})
params.require(:secret).permit(:path, :groups, data: {})
end
end
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.identity, context.request.path, context.request.data)
if secret = Services::KeyValue.write(context.identity, context.request.groups, context.request.path, context.request.data)
context.secret = secret
else
context.fail!(message: "Failed to store secret")
Expand Down
2 changes: 1 addition & 1 deletion app/lib/clients/vault/certificate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ 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)
assign_policy(identity, GENERIC_CERT_POLICY_NAME)
assign_identity_policy(identity, GENERIC_CERT_POLICY_NAME)
OpenStruct.new tls_cert.data
end

Expand Down
37 changes: 26 additions & 11 deletions app/lib/clients/vault/key_value.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,22 @@ module KeyValue
extend Policy

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

def kv_write(identity, path, data)
create_kv_policy(path)
assign_policy(identity, policy_path(path))
def kv_write(identity, groups, path, data)
create_kv_policies(path)
assign_identity_policy(identity, producer_policy_path(path))
assign_groups_policy(groups, consumer_policy_path(path))
client.logical.write("#{kv_mount}/data/#{path}", data: data)
end

def kv_delete(identity, path)
verify_policy(identity, policy_path(path))
verify_policy(identity, producer_policy_path(path))
client.logical.delete("#{kv_mount}/data/#{path}")
remove_policy(identity, policy_path(path))
remove_identity_policy(identity, producer_policy_path(path))
remove_groups_policy(consumer_policy_path(path))
end

def configure_kv
Expand All @@ -36,21 +38,34 @@ def kv_engine_type
"kv-v2"
end

def create_kv_policy(path)
client.sys.put_policy(policy_path(path), kv_policy(path))
def create_kv_policies(path)
client.sys.put_policy(producer_policy_path(path), kv_producer_policy(path))
client.sys.put_policy(consumer_policy_path(path), kv_consumer_policy(path))
end

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

def kv_policy(path)
def consumer_policy_path(path)
"kv_policy/#{path}/consumer"
end

def kv_producer_policy(path)
policy = <<-EOH
path "#{path}" {
capabilities = ["create", "read", "update", "delete"]
}
EOH
end

def kv_consumer_policy(path)
policy = <<-EOH
path "#{path}" {
capabilities = ["read"]
}
EOH
end
end
end
end
14 changes: 14 additions & 0 deletions app/lib/clients/vault/oidc.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,20 @@ def get_oidc_client_config
client.logical.read("auth/oidc/config")
end

def create_oidc_role(role_name, groups, policy_name)
client.logical.write("auth/oidc/role/#{role_name}",
user_claim: "sub",
groups_claim: "groups",
bound_claims: { "groups" => groups },
policies: policy_name,
oidc_scopes: "email groups",
allowed_redirect_uris: Config[:oidc_redirect_uris])
end

def remove_oidc_role(role_name)
client.logical.delete("auth/oidc/role/#{role_name}")
end

private

def create_client_config(issuer, client_id, client_secret)
Expand Down
20 changes: 18 additions & 2 deletions app/lib/clients/vault/policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ module Clients
class Vault
module Policy
extend Entity
extend Oidc

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

def assign_policy(identity, policy_name)
def assign_identity_policy(identity, policy_name)
sub = identity.sub
email = identity.email
Domain.with_advisory_lock(sub) do
Expand All @@ -20,6 +21,10 @@ def assign_policy(identity, policy_name)
end
end

def assign_groups_policy(groups, policy_name)
create_oidc_role(make_role_name(policy_name), groups, policy_name)
end

def verify_policy(identity, policy_name)
sub = identity.sub
policies, _ = get_entity_data(sub)
Expand All @@ -28,7 +33,7 @@ def verify_policy(identity, policy_name)
end
end

def remove_policy(identity, policy_name)
def remove_identity_policy(identity, policy_name)
sub = identity.sub
Domain.with_advisory_lock(sub) do
policies, metadata = get_entity_data(sub)
Expand All @@ -38,8 +43,16 @@ def remove_policy(identity, policy_name)
client.sys.delete_policy(policy_name)
end

def remove_groups_policy(policy_name)
remove_oidc_role(make_role_name(policy_name))
end

private

def make_role_name(policy_name)
%Q(#{policy_name.gsub("/", "_")}-role)
end

def create_astral_policy
policy = <<-HCL
path "#{intermediate_ca_mount}/roles/astral" {
Expand All @@ -66,6 +79,9 @@ def create_astral_policy
path "auth/oidc/config" {
capabilities = ["read"]
}
path "auth/oidc/role/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
path "/sys/policy/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
Expand Down
5 changes: 5 additions & 0 deletions app/lib/requests/secret_request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,14 @@ class SecretRequest
include ActiveModel::Attributes

attribute :path, :string
attribute :groups, :string
attribute :data
alias_attribute :kv_path, :path

validates :path, presence: true

def groups_array
(groups || "").split(",").sort.uniq
end
end
end
4 changes: 2 additions & 2 deletions app/lib/services/key_value.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ def read(identity, path)
impl.kv_read(identity, path)
end

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

def delete(identity, path)
Expand Down
1 change: 0 additions & 1 deletion app/models/domain.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ class Domain < ApplicationRecord
encrypts :fqdn, :users, :groups
end


def groups_array
(groups || "").split(",").sort.uniq
end
Expand Down
4 changes: 4 additions & 0 deletions doc/openapi/paths/secrets.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ postSecrets:
type: string
description: "Path where the secret is stored"
example: "secret/storage/path"
gropus:
type: string
description: "Comma-separated list of groups allowed to read the secret"
example: "svc-account1,dev-group1"
data:
type: object
description: "The secret data"
Expand Down
10 changes: 5 additions & 5 deletions test/lib/clients/vault_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ 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(@identity, "testing/secret", { password: "sicr3t" })
assert_instance_of Vault::Secret, @client.kv_write(@identity, [], "testing/secret", { password: "sicr3t" })
end

test "entity methods" do
Expand All @@ -87,7 +87,7 @@ class VaultTest < ActiveSupport::TestCase
test "kv methods" do
# check kv_write
path = "test/path/#{SecureRandom.hex}"
secret = @client.kv_write(@identity, path, { data: "data" })
secret = @client.kv_write(@identity, [], path, { data: "data" })
assert_kind_of Vault::Secret, secret

# check kv_read
Expand All @@ -96,7 +96,7 @@ class VaultTest < ActiveSupport::TestCase

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

# check kv_read denied to other identity
alt_identity = Identity.new
Expand Down Expand Up @@ -146,8 +146,8 @@ class VaultTest < ActiveSupport::TestCase
assert_match /no such alias/, err.message
end

test ".assign_policy creates valid entity" do
@client.assign_policy(@identity, "test_path")
test ".assign_identity_policy creates valid entity" do
@client.assign_identity_policy(@identity, "test_path")
entity = @client.read_entity(@identity.sub)
assert entity.data[:policies].any? { |p|
p == "test_path" }
Expand Down

0 comments on commit f9ba077

Please sign in to comment.