Skip to content

Commit b63f73d

Browse files
authored
Merge pull request #2855 from cyberark/policy-factory-v2
Policy Factory (Alpha Release)
2 parents ec6fe4b + b30637d commit b63f73d

37 files changed

+3215
-7
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
1414
### Added
1515
- Telemetry support
1616
[cyberark/conjur#2854](https://github.com/cyberark/conjur/pull/2854)
17+
- Introduces support for Policy Factory, which enables resource creation
18+
through a new `factories` API.
19+
[cyberark/conjur#2855](https://github.com/cyberark/conjur/pull/2855/files)
20+
21+
## [1.19.6] - 2023-07-05
22+
23+
### Added
1724
- New flag to `conjurctl server` command called `--no-migrate` which allows for skipping
1825
the database migration step when starting the server.
1926
[cyberark/conjur#2895](https://github.com/cyberark/conjur/pull/2895)

Gemfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ gem 'openid_connect'
7676

7777
gem "anyway_config"
7878
gem 'i18n', '~> 1.8.11'
79-
79+
gem 'json_schemer'
8080
gem 'prometheus-client'
8181

8282
group :development, :test do

Gemfile.lock

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@ GEM
7575
minitest (>= 5.1)
7676
tzinfo (~> 2.0)
7777
zeitwerk (~> 2.3)
78-
addressable (2.8.0)
79-
public_suffix (>= 2.0.2, < 5.0)
78+
addressable (2.8.1)
79+
public_suffix (>= 2.0.2, < 6.0)
8080
aes_key_wrap (1.1.0)
8181
anyway_config (2.2.3)
8282
ruby-next-core (>= 0.14.0)
@@ -116,7 +116,7 @@ GEM
116116
coderay (1.1.3)
117117
command_class (0.0.2)
118118
concurrent-ruby (1.2.2)
119-
conjur-api (5.3.8.pre.194)
119+
conjur-api (5.4.0)
120120
activesupport (>= 4.2)
121121
addressable (~> 2.0)
122122
rest-client
@@ -207,6 +207,8 @@ GEM
207207
dry-core (~> 0.5, >= 0.5)
208208
dry-inflector (~> 0.1, >= 0.1.2)
209209
dry-logic (~> 1.0, >= 1.0.2)
210+
ecma-re-validator (0.4.0)
211+
regexp_parser (~> 2.2)
210212
erubi (1.12.0)
211213
event_emitter (0.2.6)
212214
eventmachine (1.2.7)
@@ -222,6 +224,7 @@ GEM
222224
globalid (1.1.0)
223225
activesupport (>= 5.0)
224226
haikunator (1.1.1)
227+
hana (1.3.7)
225228
hashdiff (1.0.1)
226229
highline (2.0.3)
227230
http (4.2.0)
@@ -230,7 +233,7 @@ GEM
230233
http-form_data (~> 2.0)
231234
http-parser (~> 1.2.0)
232235
http-accept (1.7.0)
233-
http-cookie (1.0.4)
236+
http-cookie (1.0.5)
234237
domain_name (~> 0.5)
235238
http-form_data (2.3.0)
236239
http-parser (1.2.3)
@@ -249,6 +252,11 @@ GEM
249252
activesupport (>= 4.2)
250253
aes_key_wrap
251254
bindata
255+
json_schemer (0.2.24)
256+
ecma-re-validator (~> 0.3)
257+
hana (~> 1.3)
258+
regexp_parser (~> 2.0)
259+
uri_template (~> 0.7)
252260
json_spec (1.1.5)
253261
multi_json (~> 1.0)
254262
rspec (>= 2.0, < 4.0)
@@ -324,7 +332,7 @@ GEM
324332
pry (~> 0.13.0)
325333
pry-rails (0.3.9)
326334
pry (>= 0.10.4)
327-
public_suffix (4.0.6)
335+
public_suffix (5.0.1)
328336
puma (5.6.4)
329337
nio4r (~> 2.0)
330338
racc (1.7.1)
@@ -387,6 +395,7 @@ GEM
387395
kwalify (~> 0.7.0)
388396
parser (~> 3.0.0)
389397
rainbow (>= 2.0, < 4.0)
398+
regexp_parser (2.7.0)
390399
rest-client (2.1.0)
391400
http-accept (>= 1.7.0, < 2.0)
392401
http-cookie (>= 1.0.2, < 2.0)
@@ -471,8 +480,9 @@ GEM
471480
concurrent-ruby (~> 1.0)
472481
unf (0.1.4)
473482
unf_ext
474-
unf_ext (0.0.8.1)
483+
unf_ext (0.0.8.2)
475484
unicode-display_width (1.8.0)
485+
uri_template (0.7.0)
476486
validate_email (0.1.6)
477487
activemodel (>= 3.0)
478488
mail (>= 2.2.5)
@@ -531,6 +541,7 @@ DEPENDENCIES
531541
i18n (~> 1.8.11)
532542
iso8601
533543
jbuilder (~> 2.7.0)
544+
json_schemer
534545
json_spec (~> 1.1)
535546
jwt (= 2.2.2)
536547
kubeclient
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# frozen_string_literal: true
2+
3+
require './app/domain/responses'
4+
5+
class PolicyFactoriesController < RestController
6+
include AuthorizeResource
7+
8+
before_action :current_user
9+
10+
def create
11+
response = DB::Repository::PolicyFactoryRepository.new.find(
12+
role: current_user,
13+
**relevant_params(%i[account kind version id])
14+
).bind do |factory|
15+
Factories::CreateFromPolicyFactory.new.call(
16+
account: params[:account],
17+
factory_template: factory,
18+
request_body: request.body.read,
19+
authorization: request.headers["Authorization"]
20+
)
21+
end
22+
23+
render_response(response) do
24+
render(json: response.result)
25+
end
26+
end
27+
28+
def show
29+
allowed_params = %i[account kind version id]
30+
response = DB::Repository::PolicyFactoryRepository.new.find(
31+
role: current_user,
32+
**relevant_params(allowed_params)
33+
)
34+
35+
render_response(response) do
36+
presenter = Presenter::PolicyFactories::Show.new(factory: response.result)
37+
render(json: presenter.present)
38+
end
39+
end
40+
41+
def index
42+
response = DB::Repository::PolicyFactoryRepository.new.find_all(
43+
role: current_user,
44+
account: params[:account]
45+
)
46+
render_response(response) do
47+
presenter = Presenter::PolicyFactories::Index.new(factories: response.result)
48+
render(json: presenter.present)
49+
end
50+
end
51+
52+
private
53+
54+
def render_response(response, &block)
55+
if response.success?
56+
block.call
57+
else
58+
presenter = Presenter::PolicyFactories::Error.new(response: response)
59+
render(
60+
json: presenter.present,
61+
status: response.status
62+
)
63+
end
64+
end
65+
66+
def relevant_params(allowed_params)
67+
params.permit(*allowed_params).slice(*allowed_params).to_h.symbolize_keys
68+
end
69+
end
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
require 'base64'
2+
require 'json'
3+
4+
require './app/domain/responses'
5+
6+
module DB
7+
module Repository
8+
module DataObjects
9+
PolicyFactory = Struct.new(
10+
:name,
11+
:classification,
12+
:version,
13+
:policy,
14+
:policy_branch,
15+
:schema,
16+
:description,
17+
keyword_init: true
18+
)
19+
end
20+
21+
class PolicyFactoryRepository
22+
def initialize(
23+
data_object: DataObjects::PolicyFactory,
24+
resource: ::Resource,
25+
logger: Rails.logger
26+
)
27+
@resource = resource
28+
@data_object = data_object
29+
@logger = logger
30+
@success = ::SuccessResponse
31+
@failure = ::FailureResponse
32+
end
33+
34+
def find_all(account:, role:)
35+
factories = @resource.visible_to(role).where(
36+
Sequel.like(
37+
:resource_id,
38+
"#{account}:variable:conjur/factories/%"
39+
)
40+
).all
41+
.select { |factory| role.allowed_to?(:execute, factory) }
42+
.group_by do |item|
43+
# form is: 'conjur/factories/core/v1/groups'
44+
_, _, classification, _, factory = item.resource_id.split('/')
45+
[classification, factory].join('/')
46+
end
47+
.map do |_, versions|
48+
versions.max { |a, b| factory_version(a.id) <=> factory_version(b.id) }
49+
end
50+
.map do |factory|
51+
response = secret_to_data_object(factory)
52+
response.result if response.success?
53+
end
54+
.compact
55+
56+
if factories.empty?
57+
return @failure.new(
58+
'Role does not have permission to use Factories',
59+
status: :forbidden
60+
)
61+
end
62+
63+
@success.new(factories)
64+
end
65+
66+
def find(kind:, id:, account:, role:, version: nil)
67+
factory = if version.present?
68+
@resource["#{account}:variable:conjur/factories/#{kind}/#{version}/#{id}"]
69+
else
70+
@resource.where(
71+
Sequel.like(
72+
:resource_id,
73+
"#{account}:variable:conjur/factories/#{kind}/%"
74+
)
75+
).all
76+
.select { |i| i.resource_id.split('/').last == id }
77+
.max { |a, b| factory_version(a.id) <=> factory_version(b.id) }
78+
end
79+
80+
resource_id = "#{kind}/#{version || 'v1'}/#{id}"
81+
82+
if factory.blank?
83+
@failure.new(
84+
{ resource: resource_id, message: 'Requested Policy Factory does not exist' },
85+
status: :not_found
86+
)
87+
elsif !role.allowed_to?(:execute, factory)
88+
@failure.new(
89+
{ resource: resource_id, message: 'Requested Policy Factory is not available' },
90+
status: :forbidden
91+
)
92+
else
93+
secret_to_data_object(factory)
94+
end
95+
end
96+
97+
private
98+
99+
def factory_version(factory_id)
100+
version_match = factory_id.match(%r{/v(\d+)/[\w-]+})
101+
return 0 if version_match.nil?
102+
103+
version_match[1].to_i
104+
end
105+
106+
def secret_to_data_object(variable)
107+
_, _, classification, version, id = variable.resource_id.split('/')
108+
factory = variable.secret&.value
109+
if factory
110+
decoded_factory = JSON.parse(Base64.decode64(factory))
111+
@success.new(
112+
@data_object.new(
113+
policy: Base64.decode64(decoded_factory['policy']),
114+
policy_branch: decoded_factory['policy_branch'],
115+
schema: decoded_factory['schema'],
116+
version: version,
117+
name: id,
118+
classification: classification,
119+
description: decoded_factory['schema']&.dig('description').to_s
120+
)
121+
)
122+
else
123+
@failure.new(
124+
{ resource: "#{classification}/#{version}/#{id}", message: 'Requested Policy Factory is not available' },
125+
status: :bad_request
126+
)
127+
end
128+
end
129+
end
130+
end
131+
end

0 commit comments

Comments
 (0)