conjur-asset-dsl2 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/.dockerignore +2 -0
  3. data/.gitignore +14 -0
  4. data/.project +18 -0
  5. data/.rspec +1 -0
  6. data/.travis.yml +4 -0
  7. data/CHANGELOG +1 -0
  8. data/Dockerfile.dev +19 -0
  9. data/Gemfile +4 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +248 -0
  12. data/Rakefile +18 -0
  13. data/backup.tar +0 -0
  14. data/bin/console +14 -0
  15. data/bin/setup +7 -0
  16. data/conjur-asset-dsl2.gemspec +32 -0
  17. data/jenkins.sh +36 -0
  18. data/lib/conjur/command/dsl2.rb +175 -0
  19. data/lib/conjur/dsl2/executor/base.rb +50 -0
  20. data/lib/conjur/dsl2/executor/create.rb +117 -0
  21. data/lib/conjur/dsl2/executor/deny.rb +13 -0
  22. data/lib/conjur/dsl2/executor/give.rb +12 -0
  23. data/lib/conjur/dsl2/executor/grant.rb +13 -0
  24. data/lib/conjur/dsl2/executor/permit.rb +16 -0
  25. data/lib/conjur/dsl2/executor/retire.rb +7 -0
  26. data/lib/conjur/dsl2/executor/revoke.rb +11 -0
  27. data/lib/conjur/dsl2/executor/update.rb +31 -0
  28. data/lib/conjur/dsl2/executor.rb +99 -0
  29. data/lib/conjur/dsl2/invalid.rb +12 -0
  30. data/lib/conjur/dsl2/plan.rb +49 -0
  31. data/lib/conjur/dsl2/planner/base.rb +215 -0
  32. data/lib/conjur/dsl2/planner/grants.rb +85 -0
  33. data/lib/conjur/dsl2/planner/permissions.rb +80 -0
  34. data/lib/conjur/dsl2/planner/record.rb +102 -0
  35. data/lib/conjur/dsl2/planner.rb +38 -0
  36. data/lib/conjur/dsl2/ruby/loader.rb +263 -0
  37. data/lib/conjur/dsl2/types/base.rb +376 -0
  38. data/lib/conjur/dsl2/types/create.rb +15 -0
  39. data/lib/conjur/dsl2/types/deny.rb +17 -0
  40. data/lib/conjur/dsl2/types/give.rb +14 -0
  41. data/lib/conjur/dsl2/types/grant.rb +24 -0
  42. data/lib/conjur/dsl2/types/member.rb +14 -0
  43. data/lib/conjur/dsl2/types/permit.rb +22 -0
  44. data/lib/conjur/dsl2/types/policy.rb +129 -0
  45. data/lib/conjur/dsl2/types/records.rb +243 -0
  46. data/lib/conjur/dsl2/types/retire.rb +14 -0
  47. data/lib/conjur/dsl2/types/revoke.rb +14 -0
  48. data/lib/conjur/dsl2/types/update.rb +16 -0
  49. data/lib/conjur/dsl2/yaml/handler.rb +400 -0
  50. data/lib/conjur/dsl2/yaml/loader.rb +29 -0
  51. data/lib/conjur-asset-dsl2-version.rb +7 -0
  52. data/lib/conjur-asset-dsl2.rb +27 -0
  53. data/syntax.md +147 -0
  54. metadata +237 -0
@@ -0,0 +1,50 @@
1
+ module Conjur::DSL2
2
+ module Executor
3
+ # Builds a list of execution actions for a statement. The statement
4
+ # is an object from Conjur::DSL2::Types. Each execution action is
5
+ # an HTTP method, a request path, and request parameters.
6
+ class Base
7
+ attr_reader :statement, :actions, :default_account
8
+
9
+ def initialize statement, actions, default_account
10
+ @statement = statement
11
+ @actions = actions
12
+ @default_account = default_account
13
+ end
14
+
15
+ def action obj
16
+ @actions.push obj
17
+ end
18
+
19
+ def execute
20
+ raise "execute not implemented in #{self.class.name}"
21
+ end
22
+
23
+ def resource_path record = nil
24
+ record ||= self.statement
25
+ [ "authz", record.account || default_account, "resources", record.resource_kind, record.id ].join('/')
26
+ end
27
+
28
+ def role_path record = nil
29
+ record ||= self.statement
30
+ [ "authz", record.account || default_account, "roles", record.role_kind, record.id ].join('/')
31
+ end
32
+ end
33
+
34
+ module Annotate
35
+ def annotate
36
+ Array(annotate_record.annotations).each do |k,v|
37
+ action({
38
+ 'method' => 'put',
39
+ 'path' => update_annotation_path,
40
+ 'parameters' => { "name" => k, "value" => v }
41
+ })
42
+ end
43
+ end
44
+
45
+ def update_annotation_path
46
+ [ "authz", annotate_record.account || default_account, "annotations", annotate_record.resource_kind, annotate_record.id ].join('/')
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,117 @@
1
+ module Conjur::DSL2::Executor
2
+ # Abstract base class for creating a new record.
3
+ class Create < Base
4
+ def record
5
+ statement.record
6
+ end
7
+
8
+ def account
9
+ record.account || default_account
10
+ end
11
+ end
12
+
13
+ # Generic 'create' implementation which POSTs to a resources URL.
14
+ class CreateRecord < Create
15
+ include Annotate
16
+
17
+ def execute
18
+ action({
19
+ 'method' => 'post',
20
+ 'path' => create_path,
21
+ 'parameters' => create_parameters
22
+ })
23
+ annotate
24
+ end
25
+
26
+ def annotate_record
27
+ record
28
+ end
29
+
30
+ def create_path
31
+ [ kind_path ].join('/')
32
+ end
33
+
34
+ def kind_path
35
+ record.resource_kind.pluralize
36
+ end
37
+
38
+ # Each record is assumed to have an 'id' attribute required for creation.
39
+ # In addition, other create parameters can be specified by the +custom_attribute_names+
40
+ # method on the record.
41
+ def create_parameters
42
+ {
43
+ record.id_attribute => record.id
44
+ }.tap do |params|
45
+ custom_attrs = record.custom_attribute_names.inject({}) do |memo, attr|
46
+ value = record.send(attr)
47
+ memo[attr.to_s] = value if value
48
+ memo
49
+ end
50
+ params.merge! custom_attrs
51
+ params["ownerid"] = record.owner.roleid(record.owner.account || default_account) if record.owner
52
+ end
53
+ end
54
+ end
55
+
56
+ # When creating a host factory, the +roleid+ and +layer+ are required.
57
+ class CreateHostFactory < CreateRecord
58
+ def create_parameters
59
+ super.tap do |params|
60
+ params['roleid'] = record.role.roleid(default_account)
61
+ params['layers'] = Array(record.layers).map(&:id)
62
+ end
63
+ end
64
+ end
65
+
66
+ # When creating a variable, set default values for the +mime_type+ and +kind+.
67
+ class CreateVariable < CreateRecord
68
+ def create_parameters
69
+ super.tap do |params|
70
+ params['mime_type'] ||= 'text/plain'
71
+ params['kind'] ||= 'secret'
72
+ end
73
+ end
74
+ end
75
+
76
+ # When creating a raw Role or Resource, the owner of the new record is specified by
77
+ # the +acting_as+ parameter.
78
+ module ActingAs
79
+ def acting_as_parameters
80
+ {}.tap do |params|
81
+ params["acting_as"] = record.owner.roleid(default_account) if record.owner
82
+ end
83
+ end
84
+ end
85
+
86
+ # Create a new Resource with a PUT request to the resource path.
87
+ class CreateResource < Create
88
+ include ActingAs
89
+ include Annotate
90
+
91
+ def execute
92
+ action({
93
+ 'method' => 'put',
94
+ 'path' => resource_path(statement.record),
95
+ 'parameters' => acting_as_parameters
96
+ })
97
+ annotate
98
+ end
99
+
100
+ def annotate_record
101
+ statement.record
102
+ end
103
+ end
104
+
105
+ # Create a new Role with a PUT request to the role path.
106
+ class CreateRole < Create
107
+ include ActingAs
108
+
109
+ def execute
110
+ action({
111
+ 'method' => 'put',
112
+ 'path' => role_path(statement.record),
113
+ 'parameters' => acting_as_parameters
114
+ })
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,13 @@
1
+ module Conjur::DSL2::Executor
2
+ # Deny a privilege with a POST request to the +deny+ url of the resource, with the privilege
3
+ # and role as parameters.
4
+ class Deny < Base
5
+ def execute
6
+ action({
7
+ 'method' => 'post',
8
+ 'path' => "#{resource_path(statement.resource)}?deny",
9
+ 'parameters' => { "privilege" => statement.privilege, "role" => statement.role.roleid(default_account) }
10
+ })
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,12 @@
1
+ module Conjur::DSL2::Executor
2
+ # Change the owner of a resource with a PUT request to the resource path, specifying the new owner.
3
+ class Give < Base
4
+ def execute
5
+ action({
6
+ 'method' => 'put',
7
+ 'path' => resource_path(statement.resource),
8
+ 'parameters' => { "owner" => statement.owner.roleid(default_account) }
9
+ })
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,13 @@
1
+ module Conjur::DSL2::Executor
2
+ class Grant < Base
3
+ def execute
4
+ parameters = { "member" => statement.member.role.roleid(default_account) }
5
+ parameters['admin_option'] = statement.member.admin unless statement.member.admin.nil?
6
+ action({
7
+ 'method' => 'put',
8
+ 'path' => "authz/#{statement.role.account || default_account}/roles/#{statement.role.role_kind}/#{statement.role.id}?members",
9
+ 'parameters' => parameters
10
+ })
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,16 @@
1
+ module Conjur::DSL2::Executor
2
+ # Permit a privilege with a POST request to the +permit+ url of the resource, with the privilege
3
+ # and role as parameters. +grant_option+ is also provided if it is explicitly stated on the Permit
4
+ # record.
5
+ class Permit < Base
6
+ def execute
7
+ parameters = { "privilege" => statement.privilege, "role" => statement.role.role.roleid(default_account) }
8
+ parameters['grant_option'] = admin unless statement.role.admin.nil?
9
+ action({
10
+ 'method' => 'post',
11
+ 'path' => "#{resource_path(statement.resource)}?permit",
12
+ 'parameters' => parameters
13
+ })
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,7 @@
1
+ module Conjur::DSL2::Executor
2
+ class Retire < Base
3
+ def execute
4
+ raise "Retire is not implemented"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,11 @@
1
+ module Conjur::DSL2::Executor
2
+ class Revoke < Base
3
+ def execute
4
+ action({
5
+ 'method' => 'delete',
6
+ 'path' => "#{role_path(statement.role)}?members",
7
+ 'parameters' => { "member" => statement.member.roleid(default_account)}
8
+ })
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,31 @@
1
+ module Conjur::DSL2::Executor
2
+ class Update < Base
3
+ include Annotate
4
+
5
+ def execute
6
+ statement.record.custom_attribute_names.each do |attr|
7
+ value = statement.record.send(attr)
8
+ action({
9
+ 'method' => 'put',
10
+ 'path' => update_path,
11
+ 'parameters' => { attr.to_s => value }
12
+ })
13
+ end
14
+
15
+ annotate
16
+ end
17
+
18
+ def kind_path
19
+ statement.record.resource_kind.pluralize
20
+ end
21
+
22
+ def update_path
23
+ require 'cgi'
24
+ [ kind_path, CGI.escape(statement.record.id) ].join('/')
25
+ end
26
+
27
+ def annotate_record
28
+ statement.record
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,99 @@
1
+ module Conjur
2
+ module DSL2
3
+ module Executor
4
+ end
5
+ end
6
+ end
7
+
8
+ require 'conjur/dsl2/executor/base'
9
+ require 'conjur/dsl2/executor/create'
10
+ require 'conjur/dsl2/executor/give'
11
+ require 'conjur/dsl2/executor/grant'
12
+ require 'conjur/dsl2/executor/revoke'
13
+ require 'conjur/dsl2/executor/permit'
14
+ require 'conjur/dsl2/executor/deny'
15
+ require 'conjur/dsl2/executor/retire'
16
+ require 'conjur/dsl2/executor/update'
17
+
18
+ module Conjur
19
+ module DSL2
20
+ module Executor
21
+ class << self
22
+ def class_for action
23
+ if action.is_a?(Conjur::DSL2::Types::Create)
24
+ class_name = action.record.class.name.split("::")[-1]
25
+ begin
26
+ Conjur::DSL2::Executor.const_get([ "Create", class_name ].join)
27
+ rescue NameError
28
+ Conjur::DSL2::Executor::CreateRecord
29
+ end
30
+ else
31
+ class_name = action.class.name.split("::")[-1]
32
+ Conjur::DSL2::Executor.const_get(class_name)
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ class HTTPExecutor
39
+ def initialize api
40
+ @api = api
41
+ end
42
+
43
+ def execute actions
44
+ require 'net/https'
45
+ uri = URI.parse(Conjur.configuration.appliance_url)
46
+ @base_path = uri.path
47
+ Net::HTTP.start uri.host, uri.port, use_ssl: true do |http|
48
+ @http = http
49
+ actions.each do |step|
50
+ invoke step
51
+ end
52
+ end
53
+ end
54
+
55
+ protected
56
+
57
+ def invoke step
58
+ send step['method'], step['path'], step['parameters']
59
+ end
60
+
61
+ def create path, parameters
62
+ request = Net::HTTP::Post.new [ @base_path, path ].join('/')
63
+ request.set_form_data parameters
64
+ send_request request
65
+ end
66
+
67
+ alias post create
68
+
69
+ def update path, parameters
70
+ request = Net::HTTP::Put.new [ @base_path, path ].join('/')
71
+ request.set_form_data parameters
72
+ send_request request
73
+ end
74
+
75
+ alias put update
76
+
77
+ def delete path, parameters
78
+ uri = URI.parse([ @base_path, path ].join('/'))
79
+ uri.query = [uri.query, parameters.map{|k,v| [ k, URI.escape(v) ].join('=')}.join("&")].compact.join('&')
80
+ request = Net::HTTP::Delete.new [ uri.path, '?', uri.query ].join
81
+
82
+ send_request request
83
+ end
84
+
85
+ def send_request request
86
+ # $stderr.puts "#{request.method.upcase} #{request.path} #{request.body}"
87
+ require 'base64'
88
+ request['Authorization'] = "Token token=\"#{Base64.strict_encode64 @api.token.to_json}\""
89
+ response = @http.request request
90
+ # $stderr.puts response.code
91
+ if response.code.to_i >= 300
92
+ $stderr.puts "#{request.method.upcase} #{request.path} #{request.body} failed with error #{response.code}:"
93
+ # $stderr.puts "Request failed with error #{response.code}:"
94
+ $stderr.puts response.body
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,12 @@
1
+ module Conjur
2
+ module DSL2
3
+ class Invalid < Exception
4
+ attr_reader :mark
5
+
6
+ def initialize message, filename, mark
7
+ super [ "Error at line #{mark.line}, column #{mark.column} in #{filename}", message ].join(' : ')
8
+ @mark = mark
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,49 @@
1
+ module Conjur
2
+ module DSL2
3
+ class Plan
4
+ attr_reader :actions, :policy, :roles_created, :resources_created
5
+ attr_accessor :namespace, :ownerid
6
+
7
+
8
+ def initialize namespace = nil
9
+ @namespace = namespace
10
+ @actions = []
11
+ @policy = nil
12
+ @roles_created = Set.new
13
+ @resources_created = Set.new
14
+ end
15
+
16
+ def scoped_id id
17
+ id = id.id if id.respond_to?(:id)
18
+
19
+ # id is nil means it should have the same id as the policy
20
+ id = '' if id.nil?
21
+
22
+ if id[0] == '/'
23
+ id[1..-1]
24
+ else
25
+
26
+ tokens = []
27
+ tokens.push @namespace if @namespace
28
+ tokens.push @policy.id if @policy
29
+
30
+ if id.start_with?(tokens.join('/') + '/')
31
+ id
32
+ else
33
+ tokens.push id unless id.empty?
34
+ tokens.join('/')
35
+ end
36
+ end
37
+ end
38
+
39
+ def policy= policy
40
+ raise "Plan policy is already specified" if @policy && policy
41
+ @policy = policy
42
+ end
43
+
44
+ def action a
45
+ @actions.push a
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,215 @@
1
+ module Conjur
2
+ module DSL2
3
+ module Planner
4
+ class Base
5
+
6
+ attr_reader :record, :api
7
+ attr_accessor :plan
8
+
9
+
10
+ def initialize record, api
11
+ @record = record
12
+ @api = api
13
+ end
14
+
15
+ def action a
16
+ @plan.action a
17
+ end
18
+
19
+ def scoped_id id
20
+ @plan.scoped_id id
21
+ end
22
+
23
+ def scoped_roleid record
24
+ record = record.roleid(default_account) unless record.kind_of?(String)
25
+ account, kind, id = record.split(':', 3)
26
+ [ account, kind, scoped_id(id) ].join(":")
27
+ end
28
+
29
+ def scoped_resourceid record
30
+ record = record.resourceid(default_account) unless record.kind_of?(String)
31
+ account, kind, id = record.split(':', 3)
32
+ [ account, kind, scoped_id(id) ].join(":")
33
+ end
34
+
35
+ def account
36
+ (record.account rescue nil) || default_account
37
+ end
38
+
39
+ def default_account
40
+ Conjur.configuration.account
41
+ end
42
+
43
+ def role_record fullid
44
+ account, kind, id = fullid.split(':', 3)
45
+ if kind == '@'
46
+ Conjur::DSL2::Types::ManagedRole.build fullid, default_account
47
+ else
48
+ if record_class = record_type(kind)
49
+ record_class.new.tap do |record|
50
+ record.account = account unless account == default_account
51
+ unless record.is_a?(Conjur::DSL2::Types::Variable)
52
+ record.kind = kind if record.respond_to?(:kind=)
53
+ end
54
+ record.id = id
55
+ end
56
+ else
57
+ Conjur::DSL2::Types::Role.new(fullid, default_account: default_account)
58
+ end
59
+ end
60
+ end
61
+
62
+ def record_type kind
63
+ begin
64
+ Conjur::DSL2::Types.const_get(kind.classify)
65
+ rescue NameError
66
+ nil
67
+ end
68
+ end
69
+
70
+ alias resource_record role_record
71
+
72
+ def resource
73
+ api.resource(scoped_resourceid record)
74
+ end
75
+
76
+ def role
77
+ api.role(scoped_roleid record)
78
+ end
79
+
80
+ # Sort in canonical order -- basically, a `Record` or `Create` comes before everything
81
+ # else. So the base class's sort just places those before us, and anything else gets 0.
82
+ def <=> other
83
+ other.kind_of?(Conjur::DSL2::Planner::ActsAsRecord) ? 1 : 0
84
+ end
85
+
86
+ def resource_exists? resource
87
+ resource_id = resource.kind_of?(String) ? resource : scoped_resourceid(resource)
88
+ (plan.resources_created.include?(resource_id) || api.resource(resource_id).exists?)
89
+ end
90
+
91
+ def role_exists? role
92
+ role_id = role.kind_of?(String) ? role : scoped_roleid(role)
93
+ # I believe it's correct to assume manged roles exist?
94
+ return true if role_id.split(':',2).last.start_with?('@')
95
+
96
+ plan.roles_created.include?(role_id) || api.role(role_id).exists?
97
+ end
98
+
99
+ def error message
100
+ # For now raise it, we can think about trying to recover down the road
101
+ raise message
102
+ end
103
+
104
+ def trace message
105
+ if trace_enabled?
106
+ $stderr.puts "[trace #{record}] #{message}"
107
+ end
108
+ end
109
+
110
+ def trace_enabled?
111
+ ENV["DSL_PLANNER_TRACE"] || !!@trace_enabled
112
+ end
113
+
114
+ def trace_enabled= enabled
115
+ @trace_enabled = enabled
116
+ end
117
+
118
+
119
+ def update_record
120
+ update = Conjur::DSL2::Types::Update.new
121
+ update.record = record
122
+ record.id = scoped_id(record)
123
+
124
+ changed = false
125
+ record.custom_attribute_names.each do |attr|
126
+ existing_value = object.attributes[attr]
127
+ new_value = record.send(attr)
128
+ if new_value
129
+ if new_value == existing_value
130
+ record.send "#{attr}=", nil
131
+ else
132
+ raise "Cannot modify immutable attribute '#{record.resource_kind}.#{attr}'" if record.immutable_attribute_names.member?(attr)
133
+ changed = true
134
+ end
135
+ end
136
+ end
137
+
138
+ if record.resource?
139
+ existing = resource.exists? ? resource.annotations : {}
140
+ current = record.annotations.kind_of?(::Array) ? record.annotations[0] : record.annotations
141
+ (record.annotations||{}).keys.each do |attr|
142
+ existing_value = existing[attr]
143
+ new_value = record.annotations[attr]
144
+ if new_value == existing_value
145
+ record.annotations.delete attr
146
+ else
147
+ changed = true
148
+ end
149
+ end
150
+
151
+ if record.owner && resource.owner != scoped_roleid(record.owner)
152
+ give = Conjur::DSL2::Types::Give.new
153
+ give.resource = Conjur::DSL2::Types::Resource.new(record.resourceid(default_account), default_account: default_account)
154
+ give.owner = Conjur::DSL2::Types::Role.new(scoped_roleid(record.owner), default_account: default_account)
155
+ action give
156
+
157
+ if record.role?
158
+ grant = Conjur::DSL2::Types::Grant.new
159
+ grant.role = Conjur::DSL2::Types::Role.new(record.roleid(default_account), default_account: default_account)
160
+ grant.member = Conjur::DSL2::Types::Member.new
161
+ grant.member.role = Conjur::DSL2::Types::Role.new(scoped_roleid(record.owner), default_account: default_account)
162
+ grant.member.admin = true
163
+ action grant
164
+ end
165
+ end
166
+ end
167
+
168
+ action update if changed
169
+ end
170
+
171
+ def create_record
172
+ create = Conjur::DSL2::Types::Create.new
173
+ create.record = record
174
+ record.id = scoped_id(record)
175
+ if record.owner
176
+ record.owner = Conjur::DSL2::Types::Role.new(scoped_roleid(record.owner), default_account: default_account)
177
+ elsif plan.ownerid
178
+ record.owner = Conjur::DSL2::Types::Role.new(plan.ownerid, default_account: default_account)
179
+ end
180
+
181
+ if record.resource?
182
+ existing = resource.exists? ? resource.annotations : {}
183
+ # And this is why we don't name a class Array.
184
+ current = record.annotations.kind_of?(::Array) ? record.annotations[0] : record.annotations
185
+ (current||{}).keys.each do |attr|
186
+ existing_value = existing[attr]
187
+ new_value = current[attr]
188
+ if new_value == existing_value
189
+ current.delete attr
190
+ end
191
+ end
192
+ end
193
+
194
+ plan.roles_created.add(record.roleid(account)) if record.role?
195
+ plan.resources_created.add(record.resourceid(account)) if record.resource?
196
+ action create
197
+ end
198
+ end
199
+
200
+ class Array < Base
201
+
202
+ def do_plan
203
+ planners = record.map do |item|
204
+ Planner.planner_for(item, api)
205
+ end.sort
206
+
207
+ planners.each do |planner|
208
+ planner.plan = self.plan
209
+ planner.do_plan
210
+ end
211
+ end
212
+ end
213
+ end
214
+ end
215
+ end