-
Notifications
You must be signed in to change notification settings - Fork 124
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add policy factory route and controller
- Loading branch information
Showing
4 changed files
with
367 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
Feature: Policy Factory | ||
|
||
Background: | ||
Given I am the super-user | ||
And I create a new user "alice" | ||
And I create a new user "bob" | ||
And I successfully PATCH "/policies/cucumber/policy/root" with body: | ||
""" | ||
- !policy certificates | ||
- !policy-factory | ||
id: certificates | ||
base: !policy certificates | ||
template: | ||
- !variable | ||
id: <%=role.identifier%> | ||
annotations: | ||
provision/provisioner: context | ||
provision/context/parameter: value | ||
- !permit | ||
role: !user | ||
id: /<%=role.identifier%> | ||
resource: !variable | ||
id: <%=role.identifier%> | ||
privileges: [ read, execute ] | ||
- !policy nested-policy | ||
- !policy-factory | ||
id: nested-policy | ||
owner: !user alice | ||
base: !policy nested-policy | ||
template: | ||
- !host | ||
id: outer-<%=role.identifier%> | ||
owner: !user /<%=role.identifier%> | ||
annotations: | ||
outer: <%=role.identifier%> | ||
- !policy | ||
id: inner | ||
owner: !user /<%=role.identifier%> | ||
body: | ||
- !host | ||
id: inner-<%=role.identifier%> | ||
annotations: | ||
inner: <%=role.identifier%> | ||
- !policy edit-template | ||
- !policy-factory | ||
id: edit-template | ||
owner: !user alice | ||
base: !policy edit-template | ||
template: | ||
- !variable to-be-edited | ||
- !policy-factory | ||
id: root-factory | ||
template: | ||
- !variable created-in-root | ||
- !policy annotated-variables | ||
- !policy-factory | ||
id: parameterized | ||
base: !policy annotated-variables | ||
template: | ||
- !variable | ||
id: <%=role.identifier%> | ||
annotations: | ||
description: <%=params[:description]%> | ||
- !permit | ||
role: !user bob | ||
resource: !policy-factory parameterized | ||
privileges: [ read ] | ||
- !permit | ||
role: !user alice | ||
resource: !policy-factory certificates | ||
privileges: [ read, execute ] | ||
- !permit | ||
role: !user alice | ||
resource: !policy-factory parameterized | ||
privileges: [ read, execute ] | ||
""" | ||
|
||
Scenario: Dry run loading policy using a factory | ||
Given I login as "alice" | ||
|
||
When I POST "/policy_factories/cucumber/certificates?dry_run=true" | ||
Then the JSON should be: | ||
""" | ||
{ | ||
"policy_text": "---\n- !variable\n id: alice\n annotations:\n provision/provisioner: context\n provision/context/parameter: value\n- !permit\n privilege:\n - read\n - execute\n role: !user\n id: \"/alice\"\n resource: !variable\n id: alice\n", | ||
"load_to": "certificates", | ||
"dry_run": true, | ||
"response": null | ||
} | ||
""" | ||
|
||
Scenario: Nested policy within factory template | ||
Given I login as "alice" | ||
When I successfully POST "/policy_factories/cucumber/nested-policy" | ||
Then I successfully GET "/resources/cucumber/host/nested-policy/outer-alice" | ||
Then I successfully GET "/resources/cucumber/host/nested-policy/inner/inner-alice" | ||
|
||
Scenario: Load policy using a factory | ||
Given I login as "alice" | ||
And I set the "Content-Type" header to "multipart/form-data; boundary=demo" | ||
When I successfully POST "/policy_factories/cucumber/certificates" with body from file "policy-factory-context.txt" | ||
Then the JSON should be: | ||
""" | ||
{ | ||
"policy_text": "---\n- !variable\n id: alice\n annotations:\n provision/provisioner: context\n provision/context/parameter: value\n- !permit\n privilege:\n - read\n - execute\n role: !user\n id: \"/alice\"\n resource: !variable\n id: alice\n", | ||
"load_to": "certificates", | ||
"dry_run": false, | ||
"response": { | ||
"created_roles": { | ||
}, | ||
"version": 1 | ||
} | ||
} | ||
""" | ||
And I successfully GET "/secrets/cucumber/variable/certificates/alice" | ||
Then the JSON should be: | ||
""" | ||
"test value" | ||
""" | ||
|
||
Scenario: Load parameterized policy using a factory | ||
Given I login as "alice" | ||
|
||
When I POST "/policy_factories/cucumber/parameterized?description=first%20description" | ||
Then the JSON should be: | ||
""" | ||
{ | ||
"policy_text": "---\n- !variable\n id: alice\n annotations:\n description: first description\n", | ||
"load_to": "annotated-variables", | ||
"dry_run": false, | ||
"response": { | ||
"created_roles": { | ||
}, | ||
"version": 1 | ||
} | ||
} | ||
""" | ||
|
||
Scenario: Get a 404 response without read permission | ||
Given I login as "bob" | ||
When I POST "/policy_factories/cucumber/certificates" | ||
Then the HTTP response status code is 404 | ||
|
||
Scenario: Get a 403 response without execute permission | ||
Given I login as "bob" | ||
When I POST "/policy_factories/cucumber/parameterized" | ||
Then the HTTP response status code is 403 | ||
|
||
Scenario: A policy factory without a base loads into the root policy | ||
Given I POST "/policy_factories/cucumber/root-factory" | ||
And the HTTP response status code is 201 | ||
Then I successfully GET "/resources/cucumber/variable/created-in-root" | ||
|
||
Scenario: I retrieve the policy factory template through the API | ||
Given I login as "alice" | ||
When I GET "/policy_factories/cucumber/edit-template/template" | ||
Then the HTTP response status code is 200 | ||
And the JSON response should be: | ||
""" | ||
{ | ||
"body": "---\n- !variable\n id: to-be-edited\n" | ||
} | ||
""" | ||
|
||
Scenario: I update the policy factory template through the API | ||
Given I login as "alice" | ||
When I PUT "/policy_factories/cucumber/edit-template/template" with body: | ||
""" | ||
---\n- !variable replaced | ||
""" | ||
Then the HTTP response status code is 202 | ||
When I GET "/policy_factories/cucumber/edit-template/template" | ||
Then the JSON response should be: | ||
""" | ||
{ | ||
"body": "---\\n- !variable replaced" | ||
} | ||
""" | ||
|
||
Scenario: I don't have permission to retrieve the policy factory template | ||
Given I login as "bob" | ||
When I GET "/policy_factories/cucumber/edit-template/template" | ||
Then the HTTP response status code is 404 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--demo | ||
Content-Disposition: form-data; name="value" | ||
|
||
test value | ||
--demo-- |