diff --git a/app/interactors/obtain_cert.rb b/app/interactors/obtain_cert.rb index e0c8383..8592e86 100644 --- a/app/interactors/obtain_cert.rb +++ b/app/interactors/obtain_cert.rb @@ -1,6 +1,6 @@ class ObtainCert < ApplicationInteractor def call - if cert = Services::Certificate.issue_cert(context.request) + if cert = Services::Certificate.issue_cert(context.identity, context.request) context.cert = cert else context.fail!(message: "Failed to issue certificate") diff --git a/app/lib/clients/vault/certificate.rb b/app/lib/clients/vault/certificate.rb index f3bc31a..d215c72 100644 --- a/app/lib/clients/vault/certificate.rb +++ b/app/lib/clients/vault/certificate.rb @@ -1,10 +1,11 @@ module Clients class Vault module Certificate - def issue_cert(cert_issue_request) + 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) OpenStruct.new tls_cert.data end @@ -15,8 +16,20 @@ def configure_pki enable_ca sign_cert configure_ca + 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 @@ -117,6 +130,32 @@ def configure_ca ocsp_servers: "{{cluster_path}}/ocsp", 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 + + def generic_cert_policy + policy = <<-EOH + + path "#{cert_path}" { + capabilities = ["create", "update"] + } + + path "#{intermediate_ca_mount}/revoke-with-key" { + capabilities = ["update"] + } + EOH + end end end end diff --git a/app/lib/clients/vault/entity.rb b/app/lib/clients/vault/entity.rb index 8e2e89c..25b4faa 100644 --- a/app/lib/clients/vault/entity.rb +++ b/app/lib/clients/vault/entity.rb @@ -1,10 +1,11 @@ module Clients class Vault module Entity - def put_entity(name, policies) + def put_entity(name, policies, metadata = {}) client.logical.write("identity/entity", name: name, - policies: policies) + policies: policies, + metadata: metadata) end def read_entity(name) diff --git a/app/lib/services/certificate.rb b/app/lib/services/certificate.rb index d0f3b3a..929944d 100644 --- a/app/lib/services/certificate.rb +++ b/app/lib/services/certificate.rb @@ -1,8 +1,8 @@ module Services class Certificate class << self - def issue_cert(cert_issue_request) - impl.issue_cert(cert_issue_request) + def issue_cert(identity, cert_issue_request) + impl.issue_cert(identity, cert_issue_request) end private diff --git a/app/lib/utils/oidc_provider.rb b/app/lib/utils/oidc_provider.rb index 9c8c539..0a88ddf 100644 --- a/app/lib/utils/oidc_provider.rb +++ b/app/lib/utils/oidc_provider.rb @@ -19,6 +19,7 @@ def configure def get_client_info app = vault_client.logical.read(WEBAPP_NAME) + raise "oidc provider not configured." if app.nil? @client_id = app.data[:client_id] @client_secret = app.data[:client_secret] [ @client_id, @client_secret ] diff --git a/app/models/identity.rb b/app/models/identity.rb index 20c7491..ffdd080 100644 --- a/app/models/identity.rb +++ b/app/models/identity.rb @@ -9,5 +9,6 @@ class Identity attribute :groups, array: :string, default: [] alias_attribute :sub, :subject + alias_attribute :email, :subject alias_attribute :roles, :groups end diff --git a/config/astral.yml b/config/astral.yml index adc2a9c..a52ebef 100644 --- a/config/astral.yml +++ b/config/astral.yml @@ -49,12 +49,13 @@ shared: initial_user_name: test initial_user_password: test - initial_user_email: test@example.com + initial_user_email: john.doe@example.com test: cert_ttl: <%= 24.hours.in_seconds %> development: + cert_ttl: <%= 24.hours.in_seconds %> production: vault_create_root: false diff --git a/test/interactors/obtain_cert_test.rb b/test/interactors/obtain_cert_test.rb index 7327b67..32086aa 100644 --- a/test/interactors/obtain_cert_test.rb +++ b/test/interactors/obtain_cert_test.rb @@ -8,10 +8,11 @@ def setup test ".call success" do request = Requests::CertIssueRequest.new + identity = Identity.new mock = Minitest::Mock.new - mock.expect :call, @cert, [ request ] + mock.expect :call, @cert, [ identity, request ] Services::Certificate.stub :issue_cert, mock do - context = @interactor.call(request: request) + context = @interactor.call(identity: identity, request: request) assert context.success? assert_equal @cert, context.cert end @@ -19,10 +20,11 @@ def setup test ".call failure" do request = Requests::CertIssueRequest.new + identity = Identity.new mock = Minitest::Mock.new - mock.expect :call, nil, [ request ] + mock.expect :call, nil, [ identity, request ] Services::Certificate.stub :issue_cert, mock do - context = @interactor.call(request: request) + context = @interactor.call({ identity: identity, request: request }) assert context.failure? assert_nil context.cert end diff --git a/test/lib/clients/vault_test.rb b/test/lib/clients/vault_test.rb index 51ca0a8..ae6f534 100644 --- a/test/lib/clients/vault_test.rb +++ b/test/lib/clients/vault_test.rb @@ -18,6 +18,8 @@ class VaultTest < ActiveSupport::TestCase @policies = SecureRandom.hex(4) @entity_name = SecureRandom.hex(4) @alias_name = SecureRandom.hex(4) + @identity = Identity.new + @identity.sub = SecureRandom.hex(4) end teardown do @@ -110,6 +112,15 @@ class VaultTest < ActiveSupport::TestCase assert_match /no such alias/, err.message end + test "#config_user creates valid entity" do + @client.config_user(@identity) + entity = @client.read_entity(@identity.sub) + assert entity.data[:policies].any? { |p| + p == @client::Certificate::GENERIC_CERT_POLICY_NAME } + assert entity.data[:aliases].any? { |a| + a[:mount_type] == "oidc" && a[:name] == @identity.sub } + end + private def vault_client