From 093c4f7e63b5ecc44715c57b89ee8c4263590745 Mon Sep 17 00:00:00 2001 From: Geoffrey Wilson Date: Mon, 4 Nov 2024 14:04:11 -0500 Subject: [PATCH] KV policy creation (#70) * Add better auth error propogation from interactor to controller; added interactor unit test * init * reorg * user_config * removed data * basic policy creation working * removed extraneous policies * changed intial email * moved policy creation * fixed test * added test * rubocop * removed user_config file * cleanup * add identity to interactor calls * move policy/entity methods to matching modules * fix client unit test * Simplify policy/entity usage * remove config_user from Cert (using assign_policy now); fix test * Add test of kv_write; verify policy creation * add kv_delete test * PR changes --------- Co-authored-by: George Jahad --- app/interactors/delete_secret.rb | 2 +- app/interactors/read_secret.rb | 2 +- app/interactors/write_secret.rb | 2 +- app/lib/clients/vault/certificate.rb | 26 ++++----------------- app/lib/clients/vault/entity.rb | 9 ++++++++ app/lib/clients/vault/key_value.rb | 27 +++++++++++++++++++--- app/lib/clients/vault/policy.rb | 11 +++++++++ app/lib/services/key_value.rb | 12 +++++----- test/lib/clients/vault_test.rb | 34 +++++++++++++++++++++++----- 9 files changed, 86 insertions(+), 39 deletions(-) diff --git a/app/interactors/delete_secret.rb b/app/interactors/delete_secret.rb index e864b29..7d346bc 100644 --- a/app/interactors/delete_secret.rb +++ b/app/interactors/delete_secret.rb @@ -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 diff --git a/app/interactors/read_secret.rb b/app/interactors/read_secret.rb index b549276..f3fe466 100644 --- a/app/interactors/read_secret.rb +++ b/app/interactors/read_secret.rb @@ -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}") diff --git a/app/interactors/write_secret.rb b/app/interactors/write_secret.rb index 7d3560a..ec4ea4c 100644 --- a/app/interactors/write_secret.rb +++ b/app/interactors/write_secret.rb @@ -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") diff --git a/app/lib/clients/vault/certificate.rb b/app/lib/clients/vault/certificate.rb index d215c72..5f82ac5 100644 --- a/app/lib/clients/vault/certificate.rb +++ b/app/lib/clients/vault/certificate.rb @@ -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 @@ -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 @@ -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 diff --git a/app/lib/clients/vault/entity.rb b/app/lib/clients/vault/entity.rb index 25b4faa..6216d31 100644 --- a/app/lib/clients/vault/entity.rb +++ b/app/lib/clients/vault/entity.rb @@ -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 diff --git a/app/lib/clients/vault/key_value.rb b/app/lib/clients/vault/key_value.rb index dee6718..ac02d10 100644 --- a/app/lib/clients/vault/key_value.rb +++ b/app/lib/clients/vault/key_value.rb @@ -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 @@ -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 diff --git a/app/lib/clients/vault/policy.rb b/app/lib/clients/vault/policy.rb index d2b12d0..c3dd0ea 100644 --- a/app/lib/clients/vault/policy.rb +++ b/app/lib/clients/vault/policy.rb @@ -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 diff --git a/app/lib/services/key_value.rb b/app/lib/services/key_value.rb index d2bca9a..9bf2ad4 100644 --- a/app/lib/services/key_value.rb +++ b/app/lib/services/key_value.rb @@ -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 diff --git a/test/lib/clients/vault_test.rb b/test/lib/clients/vault_test.rb index ae6f534..cd23027 100644 --- a/test/lib/clients/vault_test.rb +++ b/test/lib/clients/vault_test.rb @@ -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 @@ -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) @@ -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