Skip to content
Draft
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
6 changes: 5 additions & 1 deletion app/controllers/concerns/find_resource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ module FindResource
extend ActiveSupport::Concern

def resource_id
[ params[:account], params[:kind], params[:identifier] ].join(":")
[ params[:account], resource_kind, params[:identifier] ].join(":")
end

def resource_kind
params[:kind]
end

protected
Expand Down
159 changes: 159 additions & 0 deletions app/controllers/policy_factories_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
# frozen_string_literal: true

# This controller is responsible for creating host records using
# host factory tokens for authorization.
class PolicyFactoriesController < ApplicationController
include FindResource
include AuthorizeResource

RenderContext = Struct.new(:role, :params) do
def get_binding
binding
end
end

def create_policy
authorize :execute

factory = ::PolicyFactory[resource_id]

template = Conjur::PolicyParser::YAML::Loader.load(factory.template)

context = RenderContext.new(current_user, params)

template = update_array(template, context)

policy_text = template.to_yaml

response = load_policy(factory.base_policy, policy_text, policy_context) unless dry_run?

response = {
policy_text: policy_text,
load_to: factory.base_policy.identifier,
dry_run: dry_run?,
response: response
}
render json: response, status: :created
end

def update_record(record, context)
fields = record.class.fields.keys

if record.is_a?(Conjur::PolicyParser::Types::Policy)
fields << 'body'
end

fields.each do |name|
record_value = record.send(name)

if record_value.class < Conjur::PolicyParser::Types::Base
update_record(record_value, context)
elsif record_value.is_a?(Array)
update_array(record_value, context)
elsif record_value.is_a?(Hash)
update_hash(record_value, context)
elsif record_value.is_a?(String)
rendered_value = ERB.new(record_value).result(context.get_binding)
record.send("#{name}=", rendered_value)
end
end

record
end

def update_array(arr, context)
arr.map! do |item|
if item.class < Conjur::PolicyParser::Types::Base
update_record(item, context)
elsif item.is_a?(Array)
update_array(item, context)
elsif item.is_a?(Hash)
update_hash(item, context)
elsif item.is_a?(String)
ERB.new(item).result(context.get_binding)
else
item
end
end

arr
end

def update_hash(hsh, context)
hsh.each do |k, val|
if val.class < Conjur::PolicyParser::Types::Base
update_record(val, context)
elsif val.is_a?(Array)
update_array(val, context)
elsif val.is_a?(Hash)
update_hash(val, context)
elsif val.is_a?(String)
hsh[k] = ERB.new(val).result(context.get_binding)
end
end
end

def get_template
authorize :read

factory = ::PolicyFactory[resource_id]

response = {
body: factory.template
}

render json: response
end

def update_template
authorize :update

factory = ::PolicyFactory[resource_id]

factory.template = request.body.read
factory.save

response = {
body: factory.template
}

render json: response, status: :accepted
end

protected

def dry_run?
params[:dry_run].present?
end

def resource_kind
'policy_factory'
end

def load_policy(load_to, policy_text, policy_context)
policy_version = PolicyVersion.new(
role: current_user,
policy: load_to,
policy_text: policy_text,
client_ip: request.ip
)
policy_version.delete_permitted = false
policy = policy_version.save

policy_action = Loader::CreatePolicy.from_policy(policy, context: policy_context)
policy_action.call

created_roles = policy_action.new_roles.select do |role|
%w(user host).member?(role.kind)
end.inject({}) do |memo, role|
credentials = Credentials[role: role] || Credentials.create(role: role)
memo[role.id] = { id: role.id, api_key: credentials.api_key }
memo
end

{
created_roles: created_roles,
version: policy_version.version
}
end
end
6 changes: 6 additions & 0 deletions app/models/audit/subject.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,11 @@ def type
ownership == 't' ? :owner : :member
end
end

class PolicyFactory < Subject
field :role_id
to_h {{ role: role_id }}
to_s { format "policy_factory %s", role_id }
end
end
end
5 changes: 3 additions & 2 deletions app/models/loader/orchestrate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,16 @@ class Orchestrate

attr_reader :policy_version, :create_records, :delete_records, :new_roles, :schemata

TABLES = %i[roles role_memberships resources permissions annotations]
TABLES = %i(roles role_memberships resources permissions annotations policy_factories)

# Columns to compare across schemata to find exact duplicates.
TABLE_EQUIVALENCE_COLUMNS = {
roles: [ :role_id ],
resources: [ :resource_id, :owner_id ],
role_memberships: [ :role_id, :member_id, :admin_option, :ownership ],
permissions: [ :resource_id, :privilege, :role_id ],
annotations: [ :resource_id, :name, :value ]
annotations: [ :resource_id, :name, :value ],
policy_factories: [ :role_id, :base_policy_id, :template ]
}

def initialize(
Expand Down
31 changes: 31 additions & 0 deletions app/models/loader/types.rb
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,37 @@ def create!
end
end

class PolicyFactory < Record
def_delegators :@policy_object, :base, :template

def create!
super

base_policy_id = find_resourceid(base_resource_id)

::PolicyFactory.create(
role_id: roleid,
policy_id: policy_id,
base_policy_id: base_policy_id,
template: template.to_yaml
)
end

def identifier
self.roleid.split(':', 3)[2]
end

def verify; end

private

def base_resource_id
# If no base policy Id is provided, the template is loaded
# into the root policy.
base&.resourceid || "#{policy_object.account}:policy:root"
end
end

# Deletions

class Deletion < Types::Base
Expand Down
11 changes: 11 additions & 0 deletions app/models/policy_factory.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

class PolicyFactory < Sequel::Model
include HasId

unrestrict_primary_key

one_to_one :role, class: :Role
many_to_one :base_policy, class: :Resource
end

11 changes: 11 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,17 @@ def matches?(request)
get "/public_keys/:account/:kind/*identifier" => 'public_keys#show'

post "/ca/:account/:service_id/sign" => 'certificate_authority#sign'

# Policy Factory routes
scope '/policy_factories/:account/*identifier' do
# The `/template` routes need to be listed before create policy, so
# that `create_policy` doesn't attempt to include `/template` in the
# policy factory ID.
get '/template' => 'policy_factories#get_template'
put '/template' => 'policy_factories#update_template'

post '/' => 'policy_factories#create_policy'
end
end

post "/host_factories/hosts" => 'host_factories#create_host'
Expand Down
Loading